
1. 从一次内部渗透测试的发现说起那天下午我正在对一个内部业务系统做常规的安全审计。目标是一个用PHP写的后台管理界面功能看起来平平无奇无非是上传个图片、查看个日志文件。我随手在文件上传的地方尝试传了一个.php后缀的文件系统很“智能”地给我弹了个提示“仅允许上传jpg, png格式”。好吧前端校验加后端白名单常规操作。我换了个思路看看有没有文件包含或者参数传递的地方。在查看“系统日志”的功能里URL长这样/admin/logs.php?filesystem_20231027.log。一个file参数直接拼接进了include()或者file_get_contents()里——这个味道太熟悉了。我尝试把参数改成../../../../etc/passwd页面返回了一片空白或者是一个文件不存在的错误。看来有基础的目录穿越防护或者对路径做了处理。这时候我想起了PHP里那些“神奇”的包装器也就是我们常说的PHP伪协议。我在参数里输入了php://filter/readconvert.base64-encode/resourcelogs.php。回车之后页面没有显示日志内容而是返回了一长串毫无规律的Base64编码字符串。解码之后我看到了logs.php这个文件的完整源代码。心跳瞬间加速我知道门已经打开了。这不仅仅是读取了一个文件。通过伪协议我能够读取应用目录下任何PHP脚本的源码包括数据库配置文件config.php、核心业务逻辑文件甚至是安装脚本。一旦源码在手分析漏洞、寻找数据库连接凭证、发现新的攻击面就变得轻而易举。更关键的是某些伪协议如php://input在与特定函数结合时能够直接导致命令执行让攻击者从读取文件权限“升级”到在服务器上执行任意命令的至高权限。今天我就把这个过程中的技术细节、原理、利用手法和防御思路掰开揉碎了讲清楚。无论你是开发、运维还是安全研究员理解PHP伪协议都是加固Web防线或进行安全评估的必修课。2. PHP伪协议文件读取的“任意门”PHP伪协议官方名称是“PHP包装器”PHP Wrappers。你可以把它理解为PHP为访问各种资源不仅是本地文件系统而提供的一套统一接口。当allow_url_fopen或allow_url_include配置开启时不幸的是很多老旧或配置不当的环境默认就是开启的PHP会把符合特定格式的字符串如php://...,file://...,http://...当作一个资源流来打开而不是普通的文件路径字符串。2.1 为什么说它是“任意门”在文件包含漏洞Local File Inclusion, LFI的利用中攻击者通常的目标是读取敏感文件如/etc/passwd、/proc/self/environ或者Web目录外的源码。传统的路径遍历../../../可能会被realpath()函数过滤或者受限于PHP的open_basedir限制。但PHP伪协议提供了一种“曲线救国”甚至“降维打击”的方式。核心原因在于对于PHP解释器来说include、require、file_get_contents()、fopen()等函数在处理php://filter这类协议时其内部逻辑是“打开一个由PHP Filter协议处理过的数据流”。这个数据流的内容可以是一个本地文件的编码转换结果。安全防护代码往往只检查路径字符串中是否包含../、是否以/开头或者是否在允许的目录内但它们很少也很难去深度解析一个协议字符串内部指向的最终资源。这就造成了防护的盲区。2.2 核心利用协议php://filter 详解php://filter是文件读取利用中最常用、最强大的伪协议。它的基本语法是php://filter/read过滤器链/resource目标文件过滤器链可以是一个或多个过滤器用|管道符连接对资源的数据流进行处理。目标文件你想要读取的实际文件路径。为什么它能绕过很多防护因为它不直接“包含”或“执行”目标文件而是将目标文件的内容经过过滤器处理后以数据流的形式返回。对于include()函数如果它包含了一个经过convert.base64-encode过滤器处理后的文件流PHP会先解码这个Base64流得到原始文件内容然后再将其作为PHP代码执行如果文件是PHP脚本。但更多时候我们利用file_get_contents()或highlight_file()等函数来读取并显示经过编码后的内容从而避免代码执行直接获取源码。常用过滤器组合与实战场景直接读取源码Base64编码php://filter/readconvert.base64-encode/resourceindex.php场景页面会直接输出一串Base64。这是最稳妥的方式因为Base64编码只包含64个字符几乎能无损地通过Web输出避免二进制数据或特殊字符被浏览器误解或截断。实操拿到字符串后用base64 -d命令Linux或在线工具解码即可。读取源码其他编码php://filter/readconvert.iconv.utf-8.utf-16/resourceconfig.php场景有时目标环境可能禁用了convert.base64-encode过滤器或者输出有长度限制。可以尝试其他编码转换如UTF-8到UTF-16虽然输出是乱码但解码后也能恢复。注意iconv过滤器需要PHP编译时支持iconv扩展。多重过滤器链php://filter/readstring.rot13|convert.base64-encode/resource/etc/passwd场景纯粹为了演示过滤器可以串联。数据会先经过ROT13编码再Base64编码。实际利用中较少这么用但有助于理解过滤器的工作方式。写入文件配合文件包含 虽然标题聚焦“读取”但php://filter的write链在特定LFI转RCE远程代码执行的场景下极其关键。例如通过php://filter/writestring.rot13/resourceshell.php结合file_put_contents()或某些包含漏洞的特定利用手法如日志注入、Session文件包含可以将恶意代码写入一个临时文件然后再去包含它。这属于更高级的利用链。注意php://filter的利用前提是目标文件可读。如果文件权限是000或者被上层目录权限严格限制伪协议也无能为力。它的强大在于绕过路径检查而非绕过文件系统权限。2.3 其他相关伪协议速览file://访问本地文件系统。这是默认协议。file:///etc/passwd和直接写/etc/passwd在allow_url_fopenOff时效果一样。但当allow_url_fopenOn时明确使用file://协议有时能绕过一些简单的字符串匹配防护比如防护代码只检查了/etc/passwd这个字符串但没检查file:///etc/passwd。php://input访问请求的原始数据POST数据。这是实现命令执行的“关键先生”我们下一章详细讲。data://数据流封装器允许在URL中包含数据。例如data://text/plain,?php phpinfo();?。它的利用通常需要allow_url_includeOn且配置更为严格现代PHP版本默认是Off因此实际利用场景比php://filter少。zip://,phar://用于访问压缩包内的文件。在文件上传漏洞中如果服务器允许上传zip文件并存在文件包含点可以利用它来包含zip包内的PHP脚本从而实现代码执行。phar://更强大它能够触发PHP对象的反序列化是另一条重要的攻击链。3. 从文件读取到命令执行的致命跃迁如果攻击止步于文件读取那危害是数据泄露。但结合php://input等协议攻击者可以完成从信息收集到系统控制权的夺取。这个跃迁的核心在于找到能够执行PHP代码的“入口”并将伪协议作为传递恶意代码的载体。3.1 php://input 与命令执行函数php://input是一个只读流它允许你读取原始POST数据。当它与include()、require()函数结合并且allow_url_include设置为On时攻击者可以将POST body中的内容作为PHP代码来执行。一个典型的利用场景假设存在一个脆弱的文件包含点include($_GET[page] . .php);攻击者可以构造如下请求GET /vuln.php?pagephp://input HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded Content-Length: 18 ?php system(id);?如果服务器配置允许allow_url_includeOnPHP会去读取php://input流的内容即?php system(id);?并将其作为PHP文件来解析执行从而输出当前进程的用户信息。哪些函数是“高危”的任何能将传入参数作为PHP代码执行的函数与伪协议结合都是高危的include(),require(),include_once(),require_once()文件包含函数。file_get_contents() 当用于包含并执行时虽然不常见但在某些特定代码逻辑下可能。passthru(),system(),exec(),shell_exec(),反引号这些是明确的命令执行函数。但它们本身不解析伪协议。攻击链通常是先通过伪协议文件包含写入一个Webshell或者利用包含漏洞临时执行代码再通过这些函数调用系统命令。 例如先通过php://input执行file_put_contents(shell.php, ?php eval($_POST[cmd]);?)写入一个Webshell然后再访问shell.php?cmdsystem(whoami);。3.2 利用链构造一个完整的攻击模拟让我们还原一个更贴近真实漏洞的场景。假设一个应用有如下代码 (view.php)?php // 从用户输入获取文件名并包含它 $file $_GET[file]; if (file_exists($file)) { include($file); } else { echo File not found.; } ?第一步信息收集使用php://filter攻击者访问/view.php?filephp://filter/readconvert.base64-encode/resourceview.php成功读取到view.php的源码。分析后发现这里存在一个直接包含用户输入的文件包含漏洞且没有有效的过滤。第二步探测配置使用php://input攻击者尝试探测allow_url_include是否开启POST /view.php?filephp://input HTTP/1.1 ... ?php phpinfo();?如果页面返回了PHP信息配置页并且在Core部分看到allow_url_include On则证明可以利用。第三步命令执行写入Webshell由于可以直接执行代码攻击者选择写入一个持久的后门POST /view.php?filephp://input HTTP/1.1 ... ?php file_put_contents(/var/www/html/backdoor.php, ?php eval($_REQUEST[c]);?); echo Done;?访问/backdoor.php?csystem(ls -la /);即可列出根目录。第四步权限提升与横向移动通过Webshell执行whoami、id查看当前用户权限。如果是Web服务用户如www-data,apache,nobody则尝试查找具有SUID权限的可执行文件、检查/home目录下其他用户的文件、读取/etc/shadow需要root等进行内网横向移动或提权。3.3 绕过技巧与受限环境下的利用现实中的漏洞利用很少一帆风顺。防护手段和受限环境是常态。1. 过滤了php://字符串怎么办大小写绕过PHP://inputPhP://Filter。PHP的协议处理器对大小写不敏感。URL编码php:%2F%2Finputphp://filter中的/可以编码为%2F。多重编码对编码后的字符串再次编码。利用其他协议如果allow_url_include开启尝试data://text/plain,?php system(ls);?。或者利用file://协议配合路径遍历先读取关键配置文件寻找其他突破口。2.allow_url_include和allow_url_fopen都为 Off这是最安全的状态php://input和data://协议用于代码执行的路基本被堵死。但php://filter和file://可能依然可用因为它们用于读取本地文件不严格依赖这两个配置尤其是file://。攻击者依然可以读取源码从中寻找数据库密码config.php,.env文件。其他硬编码的敏感信息。新的、未受保护的文件包含点或命令注入点。.git目录泄露通过php://filter/readconvert.base64-encode/resource../../../../.git/config从而下载整个源码进行白盒审计。3. 开启了open_basedir限制open_basedir将PHP可操作的文件限制在指定目录内。php://filter无法绕过open_basedir对文件系统访问的限制。如果resource指定的文件在open_basedir之外读取会失败。但是攻击者仍然可以读取限制目录内的所有文件这通常已经包含了Web应用的全部源码和上传目录危害巨大。4. 防御之道从开发到运维的全链路加固理解了攻击原理防御就有了清晰的思路。防御不是单点而是一个从代码编写、框架选择到服务器配置的完整链条。4.1 安全编码杜绝漏洞源头1. 绝对不要直接包含用户输入这是铁律。任何将用户可控变量$_GET,$_POST,$_COOKIE,$_REQUEST直接传递给include、require、file_get_contents用于包含时等函数的行为都是极度危险的。错误示例$page $_GET[page]; include($page . .php);正确做法使用白名单映射$allowed_pages [home, news, about]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include($page . .php); } else { include(404.php); }如果功能上必须动态包含文件也应基于数据库ID或其他不可控的索引值来映射而非文件名本身。2. 严格校验文件路径如果业务必须处理文件路径使用basename()函数获取文件名部分去除目录路径。使用realpath()函数解析绝对路径并与允许的基准目录进行比较。$base_dir /var/www/html/uploads/; $user_file $_GET[file]; $real_path realpath($base_dir . $user_file); // 检查解析后的真实路径是否以基准目录开头 if ($real_path strpos($real_path, $base_dir) 0) { // 安全可以读取 $content file_get_contents($real_path); } else { die(非法访问); }3. 对命令执行函数使用参数化或严格过滤system()、exec()等函数应尽可能避免使用。如果必须用如执行系统命令绝不能将用户输入直接拼接进命令。使用 escapeshellarg() 或 escapeshellcmd()进行转义。更优方案使用PHP提供的特定库函数替代系统命令。例如用scandir()代替system(ls)用file()代替exec(cat file)。4.2 服务器配置收紧最后一道防线代码层面的防御是根本但服务器配置是兜底的安全网。1. 修改 php.ini 关键配置这是最有效、最直接的防御措施。; 禁止包含远程URL和伪协议中的input/data流 allow_url_include Off ; 禁止打开远程URL影响http://, ftp://等对php://filter/file://影响有限但建议关闭 allow_url_fopen Off ; 启用安全模式已废弃但老版本可参考其思想或严格设置open_basedir open_basedir /var/www/html/:/tmp/将allow_url_include设置为Off可以彻底阻断通过php://input和data://协议执行代码的可能性。allow_url_fopen设置为Off也能增加攻击难度。2. 配置 open_basedir将PHP脚本可访问的文件系统限制在Web根目录和必要的临时目录如/tmp。这能有效防止攻击者读取/etc/passwd、/proc等系统敏感文件。注意open_basedir不是万能的。它无法防止攻击者读取Web目录内的所有源码也无法防止通过Web漏洞进行的其他攻击如SQL注入。它是一道重要的横向移动屏障。3. 禁用危险函数在php.ini中通过disable_functions指令禁用不必要的危险函数。disable_functions system,exec,shell_exec,passthru,proc_open,popen,pcntl_exec,assert,dl,...禁用system、exec等函数即使攻击者通过其他漏洞如文件包含执行了代码获得了PHP代码执行能力也无法直接调用系统命令极大地增加了攻击成本。4. 最小权限原则运行PHP-FPM或Apache进程的用户如www-data应该是非特权用户。确保Web目录下的文件权限设置正确。配置文件如config.php应设置为640所有者可读写用户组可读其他无权限且所有者是部署用户而非Web服务用户。这样即使源码被读取攻击者也无法修改。定期更新PHP版本和依赖库修复已知漏洞。4.3 安全审计与WAF防护1. 代码审计在项目上线前或定期进行代码安全审计重点关注文件包含、文件读取、命令执行、反序列化等高风险函数的使用点。可以使用RIPS、Fortify等静态代码分析工具辅助。2. Web应用防火墙WAF部署WAF可以在网络层拦截常见的攻击payload。WAF规则库通常会包含对php://、../、etc/passwd等字符串的检测。但要注意WAF可能被编码、变形等技术绕过它应作为纵深防御的一环而非唯一依赖。3. 入侵检测与日志监控监控Web访问日志关注异常的、包含大量特殊字符如../,php://,base64的请求。监控系统命令执行日志关注由Web服务用户发起的异常命令。使用文件完整性监控FIM工具监控Web目录下是否被创建了新的、可疑的PHP文件如shell.php,backdoor.php。5. 实战排查当怀疑遭遇伪协议攻击时如果你是一个运维人员突然发现服务器负载异常、出现了陌生文件或者日志里出现了奇怪的请求如何快速排查是否遭遇了此类攻击第一步检查访问日志立即查看Web服务器Nginx/Apache的访问日志。搜索以下特征php://filter、convert.base64-encode、resource等关键字。php://input出现在GET参数中这本身就很可疑。对同一个PHP脚本的请求参数中出现了../、etc/passwd、config.php等敏感路径。短时间内来自同一IP的大量、异常的请求。# 例如在nginx日志中快速筛查 grep -E (php://|base64-encode|\.\./|etc/passwd) /var/log/nginx/access.log | head -20第二步检查文件系统查看Web根目录及子目录下最近是否有新的、名称可疑的PHP文件被创建如1.php,x.php,shell.php,admin.php等。find /var/www/html -name *.php -mtime -1 # 查找一天内修改过的php文件检查/tmp目录下是否有可疑的session文件或临时文件。攻击者有时会利用php://filter写入临时文件再包含。第三步检查进程与网络连接使用top或htop查看是否有异常进程占用大量CPU或内存。使用netstat -antp或ss -antp查看是否有由Web服务用户如www-data建立的异常外网连接。第四步分析被上传的文件如果系统有文件上传功能检查上传目录看是否有被上传的、伪装成图片的Webshell通过文件头或内容检查。攻击者可能先上传一个包含恶意代码的图片再通过文件包含漏洞配合php://filter来包含执行。第五步应急响应隔离如果确认被入侵立即将服务器从网络隔离拔网线或修改安全组。取证备份完整的日志、可疑文件、进程快照。不要直接在受感染的服务器上进行分析或删除文件避免破坏证据。清除在备份后彻底清除Webshell、异常用户、后门进程。如果无法彻底清除建议备份数据后重置系统。溯源与加固分析漏洞根源是哪个文件、哪行代码导致的问题按照前面提到的防御措施修复代码并加固服务器配置。监控修复后加强监控观察是否仍有异常活动。PHP伪协议的安全问题本质上是“功能被滥用”的典型例子。它本身是PHP提供的一个强大特性但在不当使用和松懈配置下就成了攻击者手中的利器。对于开发者牢记“不要信任任何用户输入”对于运维者牢记“最小权限、默认拒绝”对于安全人员理解这些协议的工作原理才能更好地进行攻击测试与防御部署。安全是一个持续的过程没有一劳永逸的银弹只有对细节的不断关注和对最佳实践的坚持。