资讯动态

PHP绝对路径泄露全解析:从报错到绕过与防御

发布时间:2026/10/16 18:20:17 来源:尧图企业网站定制
1. 从标题说起为什么“绝对路径”这件事值得单独拎出来讲“PHP爆绝对路径方法”这个标题乍一看像是某个安全圈子里流传的“技巧合集”但如果你真的在PHP开发或代码审计一线待过就会发现它背后牵扯的东西远比字面意思要多。所谓“爆绝对路径”说白了就是想办法让PHP把服务器上的真实物理路径暴露出来——比如/var/www/html/project/src/Controller/UserController.php这种完整路径。很多人第一反应是“这有什么用”但实际场景里路径泄露往往是后续一系列问题的起点日志注入、文件包含、模板注入、反序列化链构造很多时候你缺的就是那一个准确的路径。我写这篇东西的出发点很简单网上关于PHP路径泄露的讨论要么太碎要么太“黑盒”要么就是一堆复制粘贴的payload列表没人告诉你为什么这个payload能生效、在什么版本下生效、被过滤了该怎么绕。所以我想从一个开发者和审计者的双重视角把这件事从头到尾捋一遍。不管你是刚接触代码审计的新手还是写了几年业务代码想补补安全底子的老手这篇内容应该都能让你拿到一些能直接用的东西。需要提前说明的是本文讨论的所有方法都基于一个前提你拥有对目标环境的合法测试授权或者是在自己的本地环境里做研究。未经授权的测试不在讨论范围内这是底线。2. 路径泄露的本质PHP到底在什么情况下会“说漏嘴”2.1 报错信息是第一大来源PHP最经典的信息泄露渠道就是报错。当脚本执行出错时如果display_errors开着PHP会把错误信息直接输出到页面上其中就包含出错文件的绝对路径。比如你访问一个不存在的控制器方法框架抛出的异常堆栈里往往带着完整的文件路径。这里的关键配置项有三个display_errors控制是否将错误输出到标准输出error_reporting控制哪些级别的错误被报告log_errors控制是否将错误写入日志文件在开发环境里display_errors通常是On这本身没问题。问题在于很多项目上线后忘了关或者因为调试需要临时打开后忘了恢复。我见过不少线上站点随便构造一个异常就能拿到/home/xxx/www/xxx这样的路径。2.2 异常堆栈比普通报错更“大方”普通报错可能只告诉你“文件不存在”但未捕获的异常堆栈会逐层列出调用链每一层都带着文件路径和行号。以常见的框架为例一个典型的堆栈长这样#0 /var/www/app/vendor/framework/src/Router.php(128): App\Controller\UserController-index() #1 /var/www/app/public/index.php(45): Framework\Router-dispatch()这种信息量对于审计者来说几乎是“地图”。即使display_errors关了如果框架自己捕获异常后把堆栈写进了响应体有些调试组件会这么做照样会泄露。2.3 为什么路径本身就有价值单独一个路径看起来人畜无害但把它放到攻击链里就不一样了。举几个实际场景文件包含你知道/var/www/app/config/database.php存在就可以尝试用include或require去加载它配合截断或伪协议读取内容。日志投毒知道日志路径/var/log/nginx/access.log就可以往日志里写入PHP代码再通过文件包含执行。反序列化构造POP链时需要知道类的文件路径来触发自动加载路径错了链就断了。模板注入某些模板引擎的缓存文件路径可预测知道根路径就能算出缓存文件位置。所以“爆路径”从来不是终点它是很多后续操作的起点。3. 常见方法逐一拆解从最粗暴到最隐蔽3.1 直接触发报错最简单也最容易被忽略最直接的方法就是让PHP报一个错。比如访问一个不存在的文件curl http://target/index.php?file../../../../etc/passwd如果代码里是include $_GET[file]而文件不存在PHP会输出Warning: include(../../../../etc/passwd): failed to open stream: No such file or directory in /var/www/html/index.php on line 3路径就出来了。这个方法的前提是display_errors开着。如果关了页面可能只显示空白或者一个自定义错误页。实操心得很多人试一次没出来就放弃了其实可以多换几个参数名和路径深度。有些WAF只拦特定模式换个写法就过了。另外include和require的报错信息略有不同require会直接Fatal error信息更完整。3.2 利用类型错误和参数错误PHP 7之后类型声明严格了很多传错类型会抛TypeError堆栈里带路径。比如一个函数声明了function foo(int $id)你传个数组进去curl http://target/api/user?id[]1如果框架没有全局捕获这个错误堆栈就会打出来。这个方法的好处是不依赖文件包含点只要有一个能传参的入口就行。3.3 通过phpinfo()和调试页面如果目标环境里残留了phpinfo.php或者某个调试路由没关那路径就是白送的。phpinfo()里_SERVER[SCRIPT_FILENAME]和_SERVER[DOCUMENT_ROOT]直接告诉你绝对路径。即使没有phpinfo()有些框架的调试工具栏比如某些开发模式下才开启的组件也会在响应头或页面底部输出路径信息。3.4 报错日志与访问日志的间接泄露有时候页面上不显示错误但错误被写进了日志文件而日志文件恰好可以通过Web访问。比如/logs/error.log直接暴露在Web根目录下。这种情况我见过不止一次尤其是那些把日志目录设在public下面的项目。访问日志本身也可能泄露路径比如404页面的默认响应里有时会带DocumentRoot信息取决于服务器配置。3.5 通过Composer自动加载报错现代PHP项目基本都用Composer。当自动加载找不到类时Composer会抛出异常异常信息里包含它尝试查找的路径列表。比如Class App\Service\Foo not found in /var/www/app/vendor/composer/ClassLoader.php:444虽然这里给的是ClassLoader的路径但结合堆栈和项目结构往往能推断出项目根目录。3.6 利用__FILE__和__DIR__的间接输出有些代码会把__FILE__或__DIR__写到响应里比如调试用的日志、版本信息、缓存key等。如果你能控制某个参数让它被拼接进输出就可能读到路径。这类方法比较依赖具体代码但一旦命中就很稳定。4. 当报错被关掉之后进阶思路与绕过手段4.1 关闭display_errors后的信息渠道很多生产环境会把display_errors设为Off这时候直接报错看不到东西。但信息不一定完全消失只是换了渠道HTTP响应头某些框架在异常时会设置特定的头比如X-Debug-Token配合调试接口能拿到堆栈。JSON错误响应API项目通常返回JSON格式的错误里面可能带file和line字段。自定义错误页有些错误页会“贴心”地显示技术细节尤其是那些从开发环境直接部署上去没改的。4.2 通过日志包含间接读取如果目标存在文件包含点但你不能直接包含远程文件可以考虑包含本地日志。前提是你知道日志路径。而日志路径往往可以通过报错、默认配置、或者常见部署习惯猜出来。比如/var/log/nginx/access.log/var/log/apache2/error.log/var/www/app/runtime/log/202401/01.log猜路径这件事经验比技术更重要。不同面板、不同框架、不同部署方式路径习惯差别很大。4.3 利用PHP伪协议探测php://filter可以在不执行代码的情况下读取文件内容但前提是你知道文件路径。反过来如果你有一个文件包含点可以用它来“确认”某个路径是否存在curl http://target/index.php?filephp://filter/convert.base64-encode/resource/var/www/html/config.php如果路径不对报错信息里可能带出当前脚本的路径。这算是一种“用包含来爆路径”的思路。4.4 通过临时文件和Session路径PHP的Session文件默认存在/tmp或/var/lib/php/sessions临时文件在/tmp。如果你能通过phpinfo()或者报错知道session.save_path就能进一步利用。有些文件包含漏洞就是通过包含Session文件来执行的。4.5 版本差异带来的行为变化PHP 5.x、7.x、8.x在报错格式、错误级别、类型系统上都有差异。比如PHP 8对未定义变量的处理更严格会抛Warning而不是Notice信息格式也不同。做审计的时候先确定目标PHP版本再选择对应的方法能少走很多弯路。5. 实操环境搭建与完整复现流程5.1 本地环境准备我建议用Docker搭一个干净的PHP环境来复现这样不会污染本机也方便切换版本docker run -d --name php-test -p 8080:80 \ -v $(pwd)/src:/var/www/html \ php:7.4-apache然后把测试代码放进去。下面是一个典型的“漏洞代码”示例?php // index.php $file $_GET[file] ?? home.php; include $file;这个代码同时存在文件包含和路径泄露两个问题。5.2 触发报错并观察输出访问curl http://localhost:8080/index.php?filenotexist.php你会看到类似Warning: include(notexist.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 3路径/var/www/html/index.php就出来了。5.3 关闭display_errors再试修改php.inidisplay_errors Off log_errors On error_log /var/log/php_errors.log重启容器后再访问页面上什么都没有了。但如果你能访问/var/log/php_errors.log比如通过另一个文件包含点就能读到同样的信息。5.4 用异常堆栈代替普通报错把代码改成?php function test(int $id) { return $id; } test($_GET[id] ?? []);访问?id[]1PHP 7会抛出TypeError堆栈里带路径。这个方法在display_errors开启时有效关闭后如果异常被全局捕获并记录仍然可能通过日志泄露。5.5 参数计算与路径推断假设你拿到了/var/www/html/index.php那么项目根目录很可能是/var/www/html配置文件可能在/var/www/html/config或/var/www/html/application/config日志可能在/var/www/html/runtime/log或/var/www/logs这些推断不是瞎猜而是基于大量项目的部署习惯总结出来的。你可以把它当成一个“路径字典”来用。6. 常见问题与排查技巧实录6.1 为什么我触发了报错却看不到路径可能的原因有几个现象可能原因排查方法页面空白display_errorsOff尝试通过日志包含读取显示自定义错误页框架全局异常处理找调试模式入口或API错误格式只显示“服务器错误”Web服务器拦截检查响应头和状态码报错但路径被截断输出缓冲或WAF换参数、换编码、分段读取6.2 路径出来了但包含不了文件有时候你知道了路径但include还是失败。常见原因open_basedir限制了访问范围文件权限不允许Web用户读取路径里有特殊字符被转义目标文件不是PHP文件包含后直接输出源码这其实也是好事6.3 如何判断路径是否“有用”不是所有路径都值得深挖。优先级大概是配置文件路径数据库密码、密钥日志文件路径可投毒上传目录路径可传shell框架核心文件路径可构造反序列化普通业务代码路径信息量较小6.4 避坑经验别在同一个点上死磕我见过有人对着一个不回显的报错试了几十种payload其实换个入口可能早就出来了。路径泄露的入口往往不止一个多看看其他参数、其他路由、其他HTTP方法。有时候POST不行GET就行有时候需要特定的Content-Type。6.5 关于“爆路径”的边界最后提醒一句路径泄露本身通常不算高危漏洞但它经常是高危漏洞的“助攻”。在写报告的时候不要单独把它列为一个严重问题而是把它和后续利用链结合起来描述这样更有说服力也更符合实际风险。7. 防御视角怎么让自己的项目不“爆路径”7.1 生产环境必须关掉display_errors这是最基本的一条。在php.ini里设display_errors Off log_errors On然后在Web服务器层面再确认一遍防止.htaccess或php-fpm池配置覆盖。7.2 自定义错误页和异常处理框架层面统一捕获异常返回通用错误信息不要把堆栈、文件路径、SQL语句暴露给用户。Laravel、Symfony这些框架默认在生产模式下就是这么做的但前提是APP_DEBUG要设为false。7.3 限制open_basedir在php.ini或虚拟主机配置里设置open_basedir /var/www/html:/tmp这样即使有文件包含漏洞也跳不出指定目录。7.4 日志目录不要放在Web根目录下这是一个低级但常见的错误。日志、缓存、配置文件都应该放在Web根目录之外通过权限控制来保护。7.5 定期做代码审计和配置检查路径泄露往往不是单一问题而是配置疏忽和代码习惯共同作用的结果。定期用自动化工具扫一遍再人工复核关键入口能挡掉大部分低级问题。8. 一些个人体会和后续可以玩的方向我在实际项目里遇到路径泄露的场景十有八九不是因为什么高深的技术而是因为一个没关的调试开关、一个没删的测试文件、或者一个图省事直接把日志目录设在public下的决定。技术本身不复杂复杂的是人——是开发周期里的妥协、是上线前的疏忽、是“先这样后面再改”的侥幸。如果你想把这块吃透我建议下一步可以研究这几个方向一是不同框架的异常处理机制差异二是PHP 8新特性带来的报错变化三是如何把路径泄露和文件包含、反序列化、模板注入串成完整的利用链。这些内容展开来每一个都能写很长后面有机会再单独聊。最后分享一个小技巧在本地做测试的时候把error_reporting设成E_ALL然后把所有错误都往日志里写再用tail -f盯着日志看。这样你能直观地看到每一次请求触发了哪些错误、带了哪些路径信息。看得多了自然就有感觉了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑