ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

PHP文件包含绕过:php://filter协议与strpos黑名单失效分析

PHP文件包含绕过:php://filter协议与strpos黑名单失效分析 1. 这道题不是“暖场”是Web安全新手的照妖镜刚接触CTF的新人常把[HCTF 2018]WarmUp1当成一道“送分题”——毕竟名字里带着WarmUp又在BUUCTF平台被归类为“入门级”很多人点开就直奔源码、扫目录、试SQL注入三分钟没反应就切去刷下一道。我第一次做这道题时也这样结果卡了整整两天。后来才明白WarmUp1根本不是考你“会不会用工具”而是考你“有没有真正看懂浏览器和服务器之间那层薄薄的HTTP协议”。它像一面镜子照出你对Web基础的理解到底有多扎实。题目本身不涉及任何高深加密或逆向核心就藏在index.php返回的那段看似无害的HTML里——一个被注释掉的source.php链接一个被刻意隐藏的?file参数入口以及一段用highlight_file()函数暴露源码却只允许读取source.php的逻辑限制。关键词里反复出现的buuctf web和buuctf xor其实已经暗示了方向这不是纯前端或纯后端的问题而是前后端数据流转中某个环节被你忽略了。如果你习惯性地只盯着Burp Suite里抓到的请求包却从不手动在地址栏敲一遍?filesource.php或者看到highlight_file()就默认“只能读这个文件”那你大概率会栽在这道题上。它筛选的不是技术栈多广的人而是愿意花五分钟重读一遍PHP手册highlight_file()函数说明、并动手验证每种可能性的人。2. 源码泄露的两种路径明面入口与暗面绕过WarmUp1的初始页面非常干净只有两行文字和一个注释。但正是这个注释埋下了第一个关键线索。打开开发者工具切换到Elements面板你会看到类似这样的HTML!-- upload file to ./uploads/ -- !-- ?php highlight_file(__FILE__); ? --第一行注释指向./uploads/目录第二行则直接调用了highlight_file(__FILE__)。这里需要立刻停下来问自己__FILE__在当前上下文里指代的是哪个文件答案是index.php。也就是说这段代码如果被执行就会把index.php的源码原样输出。但问题在于它被注释掉了并没有实际运行。那么如何让它“活过来”这就引出了源码泄露的两种典型路径。2.1 明面入口source.php的显式调用题目页面底部有一行极不起眼的文字“Click here to see source code”点击后跳转到source.php。这是出题人设置的第一个“明面入口”。访问source.php页面直接显示了它的PHP源码?php highlight_file(__FILE__); ?这行代码看起来毫无威胁——它只是把source.php自己的源码高亮显示出来。但请注意highlight_file()函数的参数是__FILE__而__FILE__是一个魔术常量它的值取决于当前被执行的PHP文件。也就是说如果source.php被其他文件include或require那么__FILE__的值仍然是source.php不会变成被包含它的那个文件。这个细节决定了后续绕过的思路是否成立。很多初学者看到这里就停住了以为“源码都看了下一步该 fuzz 参数了”却忽略了source.php本身就是一个可执行的PHP脚本它内部没有任何逻辑判断纯粹就是“展示自己”。所以source.php的价值不在于它做了什么而在于它证明了一件事服务器允许通过URL直接访问并执行.php文件且highlight_file()函数处于可用状态。2.2 暗面绕过index.php的隐式触发真正的突破口其实在index.php里。回到首页右键查看源码你会发现除了那两行注释页面还包含一个隐藏的表单或JavaScript跳转逻辑具体取决于你访问的BUUCTF版本但原理一致。更直接的方法是在地址栏手动拼接http://target/index.php?filesource.php。此时页面会显示source.php的源码。这说明index.php存在一个file参数用于动态指定要highlight_file()的文件。但当你尝试?fileindex.php时页面返回空白或报错。为什么因为index.php的源码里有这样一段逻辑需通过其他方式获取比如后续的LFI利用$filename $_GET[file]; if (strpos($filename, flag) ! false) { die(No flag for you!); } if (strpos($filename, php) ! false) { die(No php for you!); } highlight_file($filename);这段伪代码揭示了关键限制禁止file参数包含flag和php字符串。这就是典型的“黑名单过滤”也是WarmUp1最经典的考点。它不阻止你传参而是用strpos()检查参数值一旦发现敏感词就die()。但strpos()的返回值是整数匹配位置或false未找到而! false的判断方式在PHP弱类型下存在绕过可能。例如当$filename为php://filter/...时strpos($filename, php)会返回0因为php://开头而0 ! false为true条件成立程序继续执行。但highlight_file(php://filter/...)却能成功读取文件内容。这就是绕过的核心原理——利用PHP函数在处理不同协议流时的行为差异让黑名单检查“看到”的是php而highlight_file()“读取”的却是另一个东西。提示php://filter是一个元封装器它不直接读取文件而是对流数据进行过滤操作。php://filter/readconvert.base64-encode/resourceindex.php的意思是以base64编码的方式读取index.php的内容。这样strpos()检查的是php://filter/...这个字符串而highlight_file()实际处理的是index.php的base64编码流。3.php://filter的完整构造链从协议选择到编码解码理解php://filter的原理只是第一步真正实操时你需要构建一条完整的、能稳定读取index.php源码的URL。这个过程不是一蹴而就的而是由多个可验证的小步骤组成。我建议你按以下顺序逐步调试每一步都确认返回结果符合预期再进入下一步。3.1 协议基础确认php://filter是否被允许首先测试最简形式http://target/index.php?filephp://filter/resourceindex.php。如果服务器返回Warning: highlight_file(): Failed opening php://filter/resourceindex.php说明php://filter协议被禁用或allow_url_fopen为Off。但WarmUp1的环境是允许的所以你会看到一个空页面或乱码——因为空白的index.php没有输出内容。这步的意义在于确认协议通道是通的。如果失败后续所有构造都无意义需要换思路比如尝试data://协议但WarmUp1不适用。3.2 编码选择为什么必须用base64-encode接下来加入编码过滤器http://target/index.php?filephp://filter/readconvert.base64-encode/resourceindex.php。此时页面会返回一长串base64编码的字符串。复制这段字符串用在线工具或命令行解码echo PD9waHAKCSRmaWxlID0gJF9HRVRbJ2ZpbGUnXTsKCWlmIChwb3N0cmluKCRmaWxlLCAiZmxhZyIpICE9PSBmYWxzZSkKCSAgZGllKCJObyBmbGFnIGZvciB5b3UhIik7CglpZiAocG9zdHJpbigkZmlsZSwgInBocCIpICE9PSBmYWxzZSkKCSAgZGllKCJObyBwaHAgZm9yIHlvdSEiKTsKCWhpZ2hsaWdodF9maWxlKCRmaWxlKTsKPz4 | base64 -d解码后得到index.php的真实源码?php $file $_GET[file]; if (strpos($file, flag) ! false) die(No flag for you!); if (strpos($file, php) ! false) die(No php for you!); highlight_file($file); ?为什么必须用base64-encode因为highlight_file()函数默认以文本模式输出如果直接读取二进制文件如图片或包含特殊字符的PHP源码可能会被截断或显示异常。base64将所有字节转换为ASCII字符确保传输的完整性。其他编码如rot13或string.tolower无法保证可逆性而base64是唯一能100%还原原始内容的标准方案。3.3 资源定位resource后面的路径解析规则resource后面的路径是相对路径还是绝对路径在WarmUp1中它是相对于Web根目录的。也就是说resourceindex.php等价于/var/www/html/index.php假设Apache DocumentRoot为/var/www/html。你可以尝试resource./source.php效果相同。但要注意resource不支持../向上遍历因为highlight_file()函数本身有路径限制。这也是为什么不能直接读取/etc/passwd——php://filter只是个“管道”它读取的资源仍受highlight_file()的权限约束。WarmUp1的highlight_file()只允许读取当前目录及子目录下的.php文件所以resource/etc/passwd会失败。3.4 绕过黑名单的终极验证php字符串的双重身份现在我们来彻底验证strpos()绕过的逻辑。构造一个URLhttp://target/index.php?filephp://filter/readconvert.base64-encode/resourcephp://filter/readconvert.base64-encode/resourceindex.php。这个URL看起来很怪但它在测试strpos()的“短路”行为。第一个php://会被strpos($file, php)检测到返回00 ! false为true所以不die()而highlight_file()实际执行时会先解析外层的php://filter将其作为流处理再读取内层resource指定的文件。最终你依然能得到index.php的base64编码。这证明了strpos()检查的是整个字符串而highlight_file()处理的是协议解析后的结果——两者作用对象不同这就是绕过的根本原因。注意这种嵌套构造在实际CTF中极少使用但它能帮你深刻理解PHP协议流的执行顺序。WarmUp1的考点是单层绕过但理解多层有助于应对更复杂的题目。4. 从源码到Flagindex.php里的隐藏逻辑与flag.php定位拿到index.php的源码后你终于看清了整个应用的骨架。但Flag在哪里源码里没有echo file_get_contents(flag.php)也没有system(cat flag*)。这时候必须回归题目描述和初始页面的注释。还记得那行!-- upload file to ./uploads/ --吗它不是一个随意的提示而是明确指出了文件上传的存储路径。结合index.php的逻辑我们可以推断Flag文件很可能就存放在./uploads/目录下且文件名可能是flag.php、flag.txt或123.php这类常见命名。但highlight_file()函数默认只接受.php扩展名的文件且strpos()检查会拦截flag字符串。所以直接?fileuploads/flag.php会触发die(No flag for you!)。4.1strpos()的弱类型陷阱0与false的微妙差别strpos()函数的返回值是整数或false。当搜索的子字符串位于目标字符串开头时strpos()返回0。在PHP中0 false为true但0 false为false。WarmUp1的代码用的是! false这是一个严格比较要求类型和值都相等。所以当$file为flag.php时strpos($file, flag)返回00 ! false为true条件成立程序die()。但如果$file为aflag.php呢strpos(aflag.php, flag)返回11 ! false也为true同样die()。那有没有一种情况让strpos()返回false有就是当子字符串完全不存在时。所以绕过思路是让flag字符串“看起来存在”但实际不被strpos()匹配到。方法是URL编码。?fileuploads%2fflag.php其中%2f是/的URL编码。strpos(uploads%2fflag.php, flag)会返回false因为字符串里没有连续的flag字符只有%2fflag。但highlight_file()在解析URL时会先解码%2f为/然后尝试读取uploads/flag.php。这就是第二个经典绕过点。4.2 实际操作构造uploads/flag.php的可读URL现在组合两个绕过点用php://filter绕过php检查用URL编码绕过flag检查。最终URL为http://target/index.php?filephp://filter/readconvert.base64-encode/resourceuploads%2fflag.php访问此URL你会得到一串base64编码。解码后内容就是flag.php的源码。通常flag.php的内容非常简单比如?php echo flag{hctf_warmup_1_solved}; ?或者它可能是一个纯文本文件没有PHP标签直接输出Flag。无论哪种base64解码后都能看到明文。4.3 验证与收尾为什么uploads/flag.php一定存在这个结论不是凭空猜测而是基于CTF题目的设计惯例和题目线索的交叉验证。首先!-- upload file to ./uploads/ --是唯一的路径提示出题人不会放一个无用的注释。其次index.php里没有任何文件上传逻辑说明上传功能是独立的且上传后的文件必然存放在./uploads/。最后highlight_file()的限制只针对flag和php字符串而不是针对路径所以uploads/目录本身是可读的。这三个线索形成闭环让你有足够信心去尝试uploads/flag.php。在真实渗透中这叫“基于上下文的合理推测”比盲目爆破高效得多。5. 从WarmUp1延伸Web安全中的“协议思维”与日常防护做完WarmUp1很多人会觉得“不过如此”但它的价值远不止于一道题。它强制你建立一种“协议思维”——即不再把URL当作一个简单的地址而是看作一个由协议、主机、路径、查询参数组成的结构化数据流。php://filter、data://、phar://这些协议是PHP生态里最强大也最危险的特性之一。它们让开发者能灵活处理数据但也为攻击者提供了丰富的利用面。比如phar://协议可以触发反序列化漏洞zip://可以读取压缩包内的文件expect://甚至能执行系统命令虽然WarmUp1环境已禁用。理解WarmUp1就是理解整个PHP协议生态的入门钥匙。5.1 开发者视角如何避免类似的highlight_file()陷阱如果你是Web应用的开发者WarmUp1的教训非常直接。第一永远不要在生产环境中暴露highlight_file()、show_source()这类调试函数。它们应该只在本地开发环境启用。第二对用户输入的文件路径必须使用白名单而非黑名单。比如只允许file参数为source.php、readme.md等预设值用in_array()严格校验。第三路径拼接时使用realpath()函数规范化路径再用str_starts_with()检查是否在允许的目录内彻底杜绝../遍历。第四禁用危险协议。在php.ini中设置allow_url_fopen Off和allow_url_include Off并移除expect、compress.zlib等非必要协议。5.2 安全工程师视角strpos()黑名单的系统性失效WarmUp1的strpos()检查是Web安全中“黑名单失效”的教科书案例。它的失效不是因为逻辑错误而是因为设计哲学的根本缺陷。黑名单试图列举所有坏的东西但世界是开放的攻击者总能找到你没想到的“坏”。而白名单则相反它只定义什么是“好”其余一切默认拒绝。在实际工作中我见过太多因strpos()、str_replace()、正则替换等黑名单方案导致的RCE远程代码执行漏洞。比如用str_replace(system, , $cmd)来过滤命令攻击者只需输入syssystemtem替换后变成system照样执行。所以我的经验是只要业务逻辑允许一律用白名单如果必须用黑名单至少要配合多层校验比如先urldecode()再检查再trim()再htmlspecialchars()形成纵深防御。5.3 CTF选手视角WarmUp1之后的进阶路径WarmUp1是HCTF 2018的“暖场题”但HCTF真正的难点在后续题目。比如[HCTF 2018]warmup2会引入unserialize()反序列化[HCTF 2018]babyheap转向PWN领域。所以做完WarmUp1后你应该立即做三件事第一把php://filter的语法、常用编码、协议限制整理成速查表打印出来贴在显示器边第二找一道类似的LFI本地文件包含题目比如BUUCTF-[GWCTF 2019]mypassword用同样的思路复现第三动手写一个简易的PHP Web应用故意植入highlight_file($_GET[file])然后用Burp Suite练习各种绕过技巧直到你能闭着眼睛写出php://filter/readconvert.base64-encode/resourcexxx。实战是最好的老师而WarmUp1就是那把打开实战之门的钥匙。我在实际做题时发现很多高手并不是比别人多懂多少知识而是比别人多问一句“为什么”。比如看到strpos()他们会想“它的返回值类型是什么”看到php://filter他们会查PHP手册确认“它是否支持嵌套”看到uploads/他们会立刻在终端里curl -I http://target/uploads/看返回头。这种习惯比任何工具都重要。WarmUp1不难难的是你愿不愿意为一行代码花十分钟去读完它的官方文档。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表