今天将聚焦于 PHP 中另一类高危操作——文件相关函数。当file_get_contents、unlink等函数的参数被攻击者控制时,轻则泄露任意文件内容,重则删除关键系统文件。学会如何从代码层面审计这些漏洞,并掌握防御方法。
第十二周·周四:文件操作漏洞审计——unlink、fopen、fwrite 等
🎯 今日学习目标(达成效果)
- 能识别PHP 代码中常见的危险文件操作函数:
file_get_contents、fopen、file_put_contents、fwrite、unlink、copy、rename、include(复习); - 能审计文件操作函数中的路径参数是否来源于用户输入,并判断是否经过充分的白名单或路径限制;
- 能独立发现任意文件读取、任意文件写入、任意文件删除漏洞,并构造 payload 验证漏洞(如读取
../../etc/passwd); - 能利用文件写入漏洞写入一句话木马,并理解其危害不亚于文件上传漏洞;
- 能提出防御方案:路径白名单、禁用
../、固定扩展名、使用安全 API(如basename提取纯文件名)、最小权限原则。
📘 一、文件操作漏洞的本质
当 Web 应用使用用户输入来构造文件路径,并进行读、写、删、复制等操作时,如果没有对路径进行严格限制,攻击者就可以通过路径穿越(../)跳出预定目录,访问或修改服务器上的任意文件。
三类典型漏洞:
- 任意文件读取:读取配置文件、密码文件、源码等。
- 任意文件写入:写入木马、修改系统配置、篡改日志等。
- 任意文件删除:破坏应用数据、删除系统关键文件(可能配合条件竞争)。
共同根源:路径参数可控 + 缺乏严格的路径校验。
📘 二、危险函数详解与审计要点
1.file_get_contents—— 读取文件内容
$content=file_get_contents($path);- 审计关键:
$path是否包含外部变量?是否允许../或绝对路径? - 攻击示例:
?file=../../../etc/passwd或?file=php://filter/...(结合伪协议)。 - 搜索正则:
file_get_contents\s*\(\s*\$(?!this|_)查找可能由变量控制的调用。
2.fopen/fwrite—— 打开文件并写入
$fp=fopen($filename,'w');fwrite($fp,$data);fclose($fp);- 审计关键:
$filename是否可控?$data是否可控(如果都能控,可写 shell)。 - 注意:
fopen模式为'w'或'a'时可能创建新文件。
3.file_put_contents—— 直接写入文件
file_put_contents($filename,$data);- 这是写入漏洞最常见的形式,审计方式与
fopen类似。
4.unlink—— 删除文件
unlink($file);- 审计关键:
$file是否可控?路径是否允许穿越? - 攻击演示:
?file=../../config.php可能导致配置文件被删除。
5.copy/rename—— 复制与重命名
copy($source,$dest);rename($old,$new);- 如果源和目的均可控,攻击者可复制敏感文件到可访问目录,或移动文件实现权限绕过。
6.include/require—— 文件包含(复习)
- 同样属于文件操作,审计时重点关注变量是否进入包含路径,可导致 LFI/RFI。
审计原则:任何用于文件路径的外部参数,如果没有被严格限定在固定目录内或使用白名单,就应标记为潜在漏洞。
✍️ 三、动手实践:审计一个存在文件读取漏洞的功能
实践 1:搭建任意文件读取靶场
创建file_read.php:
<?php$template=$_GET['template']??'default.html';$path='templates/'.$template;if(file_exists($path)){echofile_get_contents($path);}else{echo'Template not found.';}?>审计分析:
- 输入源:
$_GET['template']完全可控。 - 路径构造:使用字符串拼接,未过滤
../。 - 危险函数:
file_get_contents,且file_exists仅检查存在性,不防穿越。 - 利用:
?template=../../etc/passwd可直接读取系统文件。
防御方案:
- 移除路径中的
../和..\(但需注意编码绕过)。 - 使用
basename()仅提取文件名,忽略目录部分:$template=basename($_GET['template']); - 结合白名单,仅允许特定文件名。
实践 2:搭建任意文件写入靶场
创建save_config.php:
<?php$filename=$_POST['config_name']??'app.conf';$content=$_POST['config_data']??'';$path='configs/'.$filename;file_put_contents($path,$content);echo'Config saved.';?>漏洞:攻击者可上传config_name=../../var/www/html/shell.php,内容为<?php @eval($_POST['cmd']);?>,写入 Webshell。
防御:
- 强制扩展名:
$filename = uniqid() . '.conf';完全忽略用户提供的文件名。 - 路径仅允许在指定目录内,并且去除
../。
实践 3:审计一个简单的文件删除功能
创建delete_file.php:
<?php$file=$_GET['file'];$path='uploads/'.$file;if(file_exists($path)){unlink($path);echo'Deleted.';}?>利用:?file=../../config.php删除配置文件。如果删除的是.htaccess,还可能引发后续攻击。
防御:
- 检查文件名是否包含
..。 - 使用
realpath并验证解析后的路径是否仍位于允许目录内。 - 删除前记录日志,并限制可删除的文件类型。
✍️ 四、动手实践四:在 DVWA 或自建项目中审计文件操作
1. DVWA File Inclusion 源码审计
- 打开
dvwa/vulnerabilities/fi/source/low.php:$file=$_GET['page'];include($file); - 审计:直接包含用户输入,典型的文件包含漏洞,也可用于读取文件(通过
php://filter)。
2. 审计一个简单 CMS 的文件操作点
使用 Seay 搜索unlink、file_get_contents、file_put_contents等关键字,查看是否有参数直接来自$_GET、$_POST且未过滤。例如 BlueCMS 中的/admin/tpl_manage.php可能存在删除模板文件的功能,若$tpl_name来自 GET 且未过滤,则可删除任意文件。
记录:在审计报告中写明路径、参数、危险函数及利用思路。
📝 五、课后测试题与解析
测试题 1:以下代码是否存在漏洞?如果存在,是什么类型?如何利用?
$file=$_GET['file'];$content=file_get_contents('data/'.$file);echo$content;参考答案:
存在任意文件读取漏洞。$file参数直接来自用户,攻击者可通过../穿越目录,如?file=../../etc/passwd,读取系统文件。另外可能利用 PHP 伪协议(如php://filter)读取 PHP 文件源码。修复:使用basename()过滤,或白名单限制文件名。
测试题 2:在审计文件删除功能时,即使使用realpath验证路径,还需要注意什么?
参考答案:
- 竞争条件:在
realpath检查与unlink执行之间存在时间窗口,攻击者可能通过并发请求替换文件链接,删除意料之外的文件。需要配合文件锁或原子操作。 - 权限检查:
realpath只解析路径,不检查文件所有权或权限,仍需确保 Web 进程不删除系统关键文件,应限制可删除的目录范围。 - 逻辑绕过:即使路径合法,如果删除操作可被滥用删除用户资产(如删除其他用户的头像),也属于越权漏洞,需要验证当前用户对文件的所有权。
✅ 今日学习效果自检清单
- 我能列出至少 5 个 PHP 文件操作函数,并说出它们在什么条件下会产生漏洞
- 我独立编写了包含任意文件读取、写入、删除漏洞的代码,并成功利用
- 我使用 Seay 在靶场代码中搜索了文件操作函数,并人工确认了潜在漏洞
- 我掌握了路径穿越的防御手段(
basename、realpath、白名单、禁用../) - 我理解了文件写入漏洞与文件上传漏洞的相似性,以及各自的防御重点
⚠️ 阶段避坑重点
- 不要只过滤
../:攻击者可以使用....//、..\/、URL 编码%2e%2e%2f等绕过简单替换。必须使用basename()或realpath()等可靠方法。 file_exists不是安全函数:它只检查文件是否存在,不防御路径穿越,审计时不能因为前面有file_exists就认为安全。- 写入权限控制:即使路径被限制,如果 Web 目录可写,写入的木马依然可执行。文件存储目录应在 Web 根目录之外,或禁止脚本执行。
- 注意文件包含与文件读取的区别:包含函数(
include)会执行代码,读取函数(file_get_contents)仅获取内容,但防御思路相似。 - 审计时要关注框架封装:现代框架通常使用
Storage或Filesystem抽象,直接搜索函数可能遗漏,需了解框架的文件操作 API 并进行追踪。
今天你完成了文件操作漏洞的审计训练,这是代码审计中与 SQL 注入同样常见的漏洞类型。明天我们将进入更危险的命令/代码执行点审计,追踪eval、system等函数的参数,理解它们如何在代码中留下后门。请保留今天的审计环境,命令执行审计常用相似的方法论。