文件包含漏洞总结
最后更新时间:
文章总字数:
预计阅读时间:
文件包含漏洞总结
之前对文件包含漏洞的理解一直浅薄地停留在include+php伪协议,正好今天复现CISCN2022 backdoor的时候又遇到了session包含,于是就来系统学习总结一下。
基础概念与漏洞成因
以PHP为例,常用的文件包含函数有以下四种
include(),require(),include_once(),require_once()
require():找不到被包含的文件会产生致命错误,并停止脚本运行
include():找不到被包含的文件只会产生警告,脚本继续执行
require_once()与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
include_once()与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
而四种中最常用的是**include()**,文件包含漏洞产生的最根本原因就是include()函数并不在意被包含的文件是什么类型,只要文件的数据里面有php代码就会自动解析。也就是说只要有地方能让我们写php语句进去,然后包含该文件就能执行代码。
文件包含漏洞利用
在ctf比赛中最常出现的是本地的文件包含,即LFI。
任意敏感文件读取
windows:
1 | C:\boot.ini //查看系统版本 |
linux:
1 | /root/.ssh/authorized_keys |
可以使用绝对路径读取,也可以使用../来跳出目录限制读取,至于直接读到flag还是别想了
本地文件包含漏洞利用
配合文件上传,直接上传一个webshell.jpg(白名单检测下),再通过include将这个文件包含进来就可以获得webshell。值得注意的是一般这样的webshell写法是:
1
2
3
fwrite(fopen("shell.php","w"),'<?php eval($_POST[123]);?>);
?>如果目标文件夹可以支持写操作最好这样,否则webshell可能功能有点问题,但是一般都不支持直接往上面写文件(笑),于是也只能写正常的一句话马。
包含Apache日志文件
条件是:日志文件知道存储目录或者可读,但是一般情况下日志文件并不会放在默认路径,需要读取服务器配置文件.conf或者通过phpinfo的值
当用户发起请求的时候,不管成功与否,服务器都会把该请求写入access.log,但是写的时候经过了url编码,所以我们要先传参,然后bp截包改回原来的,否则就算包含进来php语句不会被解析。
包含SESSION文件
在phpinfo里找到session文件存放的位置,或者猜一猜常见的位置:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID(最常见)
/tmp/sessions/sess_PHPSESSID
session文件格式:sess_[phpsessid],而phpsessid在发送的请求的cookie字段中可以看到。通过控制cookie我们可以控制session_start时访问的session文件,从而对特定的文件内容进行序列化和反序列化。
包含临时文件
通过条件竞争。当我们找不到用于触发RCE的有效文件时,如果可以访问phpinfo(它可以告诉我们临时文件的随机生成的文件名及其位置)(且如果服务器后端的处理逻辑是先上传再检测,而不是先检测再上传),我们可能可以包含一个临时文件来利用它升级为RCE。
利用方式:
在给PHP发送POST数据包时,如果数据包里包含文件区块,无论你访问的代码中有没有处理文件上传的逻辑,PHP都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符]),文件名可以在$_FILES变量中找到。这个临时文件,在请求结束后就会被删除。
同时,因为phpinfo页面会将当前请求上下文中所有变量都打印出来,所以我们如果向phpinfo页面发送包含文件区块的数据包,则即可在返回包里找到$_FILES变量的内容,自然也包含临时文件名。
然而,我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,再将这个文件名发送给文件包含漏洞页面,进行getshell。在第一个请求结束时,临时文件就被删除了,第二个请求自然也就无法进行包含。
于是这时候就要利用到条件竞争,在服务器把我们的临时文件删除之前把他包含进来,从而解析执行代码。
具体利用流程(了解,事实上多使用工具和脚本)
1、发送包含了webshell的上传数据包给phpinfo页面,这个数据包的header、get等位置需要塞满垃圾数据
2、因为phpinfo页面会将所有数据都打印出来,1中的垃圾数据会将整个phpinfo页面撑得非常大
3、php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接
4、所以,我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包
5、此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除
6、利用这个时间差,第二个数据包,也就是文件包含漏洞的利用,即可成功包含临时文件,最终getshell
简单来说,就是抓时间差,简单利用的话可以一边使用bp重复上传文件,一边用python脚本不断访问上传了的文件,访问成功就成功了。
利用php伪协议来攻击
file://协议:用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响,XXE注入漏洞中就有使用过file://协议
php://协议:最常用的是filter和input,filter可以读源码,记得base64加密,而input可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行
ZIP://协议:没怎么用到过,只记得用zlib和zlib2可以绕过phar开头的检测(都是压缩包)
- zip://中只能传入绝对路径。
- 要用#分割压缩包和压缩包里的内容,并且#要用url编码成%23(即下述POC中#要用%23替换)
- 只需要是zip的压缩包即可,后缀名可以任意更改。
- 相同的类型还有zlib://和bzip2://
data://协议,可以写入,和input类似
1
2
3data://text/plain,<?php phpinfo();?>
//如果此处对特殊字符进行了过滤,我们还可以通过base64编码后再输入:
data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
参考链接