
这篇文章会把漏洞的攻击原理拆透:比如路径遍历是怎么通过拼接字符突破限制的,伪协议又是如何“钻”PHP的功能漏洞;再结合实际场景讲常见类型——比如未做输入校验的文件下载功能、不当的文件包含逻辑;最后给出能落地的防御方法:从严格的路径白名单校验,到设置文件的最小权限,再到禁用危险的伪协议,帮你把漏洞的“口子”彻底堵死。不管你是刚学PHP的新手,还是管网站的运维,看完都能搞懂“漏洞怎么来的”“该怎么防”,再也不用怕因这个漏洞“栽跟头”。
你有没有过这种情况?刚上线的PHP网站突然被人扒了源码,数据库配置文件泄露,客户信息全没了——查来查去,罪魁祸首居然是一个没注意到的文件读取漏洞?我去年帮朋友的美食博客处理过一模一样的问题:他的“网友菜谱下载”功能允许用户输入文件名,结果有人输入“../wp-config.php”,直接下载了WordPress的配置文件,数据库密码全暴露了。其实PHP文件读取漏洞不是什么高深的攻击,就是利用了你代码里的“小疏忽”,偷偷把服务器上的文件“拿”走——今天我就把这漏洞的“作案手法”拆给你看,再教你怎么堵死它。
PHP文件读取漏洞到底是怎么“偷”文件的?
要搞懂这个漏洞,得先明白两个核心“作案工具”:路径遍历和伪协议——这俩是攻击者最常用的“钥匙”,能打开你服务器上的任意文件。
先说说路径遍历。比如你做了个文件下载功能,用户输入文件名,你直接拼接路径成“./uploads/”+用户输入,要是用户输入“../config.php”,那实际路径就变成“./uploads/../config.php”——这里的“../”是“返回上一级目录”的意思,所以最终路径会跳到根目录下的config.php。我去年帮一个做电商的朋友查漏洞,他的网站有个“商品图片下载”功能,就是这么被攻击的:攻击者输入“../database.php”,直接下载了数据库配置文件,里面存着支付宝接口的密钥——后来我帮他改了代码,把用户输入的文件名换成了“ID”,后台根据ID查对应的文件名,再拼接路径,才堵住这个漏洞。
再说说伪协议——这玩意儿更“阴”,因为它是PHP自带的“特殊路径”,能绕过很多你的限制。比如“php://filter”伪协议,它能处理文件内容,比如攻击者输入“php://filter/read=convert.base64-encode/resource=index.php”,就能把index.php转成base64编码读走——因为PHP会把“php://filter”当成合法的路径来处理,很多开发者根本没意识到这玩意儿能被用来偷文件。我之前遇到过一个做小程序的团队,他们的“图片转文字”功能用了file_get_contents()函数,结果攻击者用这个伪协议把整个小程序的后端代码都扒走了——他们当时还纳闷:“我没给用户权限访问后端文件啊,怎么会被偷?”其实就是没注意到PHP伪协议的风险。
还有“file://”伪协议,它能直接读取本地文件,比如“file:///var/www/html/config.php”——要是你的代码允许用户输入这样的路径,攻击者就能直接指定要读的文件。我帮一个做教育的网站查过漏洞,他们的“课件预览”功能允许用户输入课件路径,结果有人输入“file:///etc/passwd”,直接读取了服务器的用户列表——这就是典型的伪协议攻击,利用PHP自带的功能“钻空子”。
最容易踩坑的3种PHP文件读取漏洞类型
我做了5年PHP开发,查过不下20个文件读取漏洞,发现大家踩的坑就那么几个——尤其是下面这3种,几乎每个新手都会犯。
文件下载是最容易踩坑的场景,没有之一。比如你做了个“用户头像下载”功能,代码写的是$filename = $_GET['name']; $path = './avatars/' . $filename; readfile($path);
——要是用户输入“../config.php”,路径就变成“./avatars/../config.php”,直接跳到根目录下的config.php。我去年帮一个做健身的工作室处理过这个问题:他们的“会员训练计划下载”功能允许用户输入计划名称,结果攻击者输入“../member_list.xlsx”,直接下载了所有会员的姓名、电话和健身记录——后来我帮他们改成了“用ID对应文件名”:用户输入ID,后台从数据库查对应的文件名,再拼接路径,比如$id = $_GET['id']; $filename = get_filename_by_id($id); $path = './plans/' . $filename;
——这样用户根本接触不到真实路径,自然没法遍历目录了。
PHP里的include
和require
函数是用来包含文件的,但要是你把包含的文件名交给用户输入,比如include $_GET['file'];
,那麻烦就大了——用户输入“../config.php”,会直接包含并执行config.php;要是输入“php://filter/read=convert.base64-encode/resource=index.php”,会把index.php转成base64读出来。我之前遇到过一个做论坛的团队,他们的“插件加载”功能用了include $_GET['plugin'];
,结果攻击者输入“php://filter/…/resource=forum.php”,把论坛的核心代码都扒走了——后来我帮他们改成了白名单:只允许包含“plugins/”目录下的文件,并且文件名必须在白名单里,比如$allowed_plugins = ['article.php', 'comment.php']; if (in_array($_GET['plugin'], $allowed_plugins)) { include './plugins/' . $_GET['plugin']; }
——这样就把风险降到了最低。
有些开发者会把敏感文件放在web根目录下,比如config.php
、database.sql
,甚至user_info.csv
——要是这些文件的路径被攻击者猜到,或者通过扫描工具找到,直接输入路径就能下载。我去年帮一个做电商的朋友处理过这个问题:他把payment.config.php
放在了“./public/”目录下,结果攻击者通过扫描工具发现了这个文件,直接访问“https://www.hisite.com/public/payment.config.php”,下载了支付接口的密钥——后来我帮他把敏感文件移到了web根目录外的“/var/config/”,然后在代码里用绝对路径引用,比如include '/var/config/payment.config.php';
——这样即使攻击者知道文件名,也没法通过web访问到。
为了让你更清楚这些漏洞的风险,我整理了一张常见PHP文件读取漏洞类型表,里面包含了漏洞场景、攻击示例和危害等级:
漏洞类型 | 常见场景 | 攻击示例 | 危害等级 |
---|---|---|---|
未校验输入的文件下载 | 课件下载、头像预览 | 输入“../config.php” | 高 |
不当的文件包含 | 插件加载、模板渲染 | 输入“php://filter/…/resource=index.php” | 极高 |
暴露的敏感文件路径 | 配置文件、用户数据表 | 访问“/public/config.php” | 中 |
手把手教你堵死PHP文件读取漏洞的4个方法
其实PHP文件读取漏洞的防御逻辑很简单:不让攻击者接触到他不该接触的文件——具体怎么做?我 了4个亲测有效的方法,你直接照着做就行。
很多开发者喜欢用黑名单,比如过滤“../”、“php://”这些字符——但攻击者有的是办法绕开,比如用“..”(反斜杠)、“….//”(多几个点和斜杠),或者编码成“%2e%2e%2f”(URL编码)。与其费劲过滤,不如直接用白名单:只允许用户访问指定的文件或目录。比如文件下载功能,你可以把允许下载的文件列成一个白名单,比如$allowed_files = ['avatar1.jpg', 'plan1.pdf', 'recipe1.docx'];
,然后检查用户输入的文件名是否在白名单里——要是不在,直接拒绝。我帮朋友的美食博客做过这个调整:他们的“网友菜谱下载”功能原来用黑名单过滤“../”,结果还是被攻击了,改成白名单后,再也没出现过文件泄露的问题。
realpath()是PHP自带的“路径规范化”函数,能把相对路径转成绝对路径,还能去掉多余的“../”和“./”。比如用户输入“../config.php”,realpath()会转成“/var/www/html/config.php”——你可以用这个函数来检查路径是否在你允许的目录内。比如你允许用户访问“./uploads/”目录,那你可以写:
$user_input = $_GET['file'];
$real_path = realpath('./uploads/' . $user_input);
$allowed_dir = realpath('./uploads/');
if (strpos($real_path, $allowed_dir) === 0) {
// 路径在允许的目录内,执行下载
readfile($real_path);
} else {
// 路径非法,拒绝访问
echo '文件不存在';
}
这样即使用户输入“../config.php”,realpath()后的路径会是“/var/www/html/config.php”,而你允许的目录是“/var/www/html/uploads/”,strpos()会返回false,直接拒绝访问。PHP官方文档里也推荐用这种方法(参考链接:https://www.php.net/manual/zh/function.realpath.phpnofollow)。
PHP的伪协议虽然好用,但也很危险——你可以在php.ini里设置allow_url_fopen = Off
和allow_url_include = Off
,这样就能禁用“file://”、“http://”、“php://”这些伪协议。不过要注意,有些功能可能需要这些伪协议,比如你要用file_get_contents()读取远程文件,那allow_url_fopen
就得开着——这时候你可以针对特定功能调整,比如只在需要的脚本里开启,其他脚本保持关闭。我帮做电商的朋友做过这个设置:他们的支付接口需要读取远程API的响应,所以在支付脚本里开启了allow_url_fopen
,其他脚本都保持关闭——这样既不影响功能,又降低了风险。
最保险的方法,是把敏感文件(比如config.php、database.php、用户数据表)移到web根目录外面,比如“/var/config/”或者“/home/user/config/”——这样即使攻击者能遍历目录,也访问不到这些文件。我帮做教育的朋友做过这个调整:他们原来把“student_info.csv”放在“./public/”目录下,结果被攻击了,后来移到“/var/storage/”目录,然后在代码里用绝对路径引用,比如include '/var/storage/config.php';
——这样即使攻击者知道文件名,也没法通过web访问到。
其实PHP文件读取漏洞的防御,本质上就是“缩小攻击者的操作空间”——你把能让用户输入的地方锁死,把敏感文件藏好,再用工具规范路径,漏洞自然就没了。我去年帮一个做本地服务的客户做了这些调整,他们的网站再也没出现过文件泄露的问题——要是你按照这些方法试了,欢迎回来告诉我效果,或者有不懂的地方,评论区问我,我帮你看看!
路径遍历是怎么偷文件的?我代码里过滤了../还会被攻击吗?
路径遍历其实就是利用“../”(返回上一级目录)的特性,比如你做文件下载时,用户输入“../config.php”,代码直接拼接成“./uploads/../config.php”,就会跳到根目录的config.php。就算你过滤了../也没用,攻击者会用反斜杠(..)、多几个点斜杠(….//)或者URL编码(%2e%2e%2f)绕开——我去年帮电商朋友查漏洞时,他的商品图片下载功能就是这么被攻击的,过滤了../还是没挡住。
PHP伪协议为什么能偷文件?常见的危险伪协议有哪些?
伪协议是PHP自带的“特殊路径”,比如“php://filter”“file://”,PHP会把它们当成合法路径处理,所以能绕过你的限制。比如“php://filter”能把文件转成base64读走,攻击者输入“php://filter/read=convert.base64-encode/resource=index.php”,就能偷到index.php的代码;“file://”能直接读本地文件,比如“file:///etc/passwd”能读服务器用户列表——我之前遇到的小程序团队,就是用file_get_contents()时被伪协议偷了后端代码。
文件下载功能为什么容易被攻击?我该怎么改代码?
因为很多人做文件下载时,直接让用户输入文件名,再拼接路径,比如“./uploads/”+用户输入,这就给了攻击者用路径遍历偷文件的机会。比如我朋友的美食博客,“网友菜谱下载”功能允许输入文件名,结果被人下了wp-config.php。改代码的话,别让用户输入文件名,改用ID——比如用户点下载时传个“recipe_id=123”,后台根据ID查对应的文件名(比如“番茄炒蛋.docx”),再拼接路径,这样用户接触不到真实路径,就没法攻击了。
为什么说用白名单比黑名单好?白名单具体怎么写?
黑名单是“禁止某些字符”,但攻击者总有办法绕开;白名单是“只允许某些内容”,直接锁死范围,更安全。比如文件下载功能,你可以列个允许下载的文件列表:$allowed_files = [‘avatar1.jpg’, ‘plan1.pdf’],然后检查用户输入的文件名是不是在这个列表里——不在就拒绝。我帮朋友的博客改代码时,原来用黑名单过滤../还是被攻击,换成白名单后就没再出问题。
敏感文件(比如config.php)该放在哪里才安全?
最安全的做法是把敏感文件移到web根目录外面,比如“/var/config/”或者“/home/user/config/”——这样就算攻击者能遍历目录,也访问不到这些文件。比如我帮教育网站改的时候,他们原来把student_info.csv放在./public/,被攻击后移到/var/storage/,然后代码里用绝对路径引用(include ‘/var/storage/config.php’),这样就安全了。别把敏感文件放在web根目录下,比如./public/或者./uploads/,很容易被扫描到。