这是一道非常经典的 PHP 代码审计类 CTF 题目。该代码的核心在于文件包含漏洞 (LFI) 以及对特定关键词的黑名单绕过。
以下是详细的解题思路分析:
- 代码分析
代码逻辑非常简单直接:
获取参数:通过 $_GET[‘file’] 获取用户输入。
过滤机制:使用 str_replace(“php”, “???”,file)将输入中的字符串php替换为???。核心功能:执行include(file) 将输入中的字符串 php 替换为 ???。 核心功能:执行 include(file)将输入中的字符串php替换为???。核心功能:执行include(file)。
关键点:
由于 php 被过滤了,直接使用常见的伪协议(如 php://filter)会失效,因为 php:// 会变成 ???😕/,导致协议解析失败。
我们先尝试查看当前目录下的文件名
构造的payload为:?file=data://txt/plain,<?PHP system('ls');?>
本想照猫画虎构造查询flag.php中的内容
但当输入payload:?file=PHP://filter/read=convert.base64-encode/resource=flag.PHP但是这个payload却执行失败
因为协议名不区分大小写,但文件名通常区分大小写。
失败原因分析
在 Linux 系统(大部分 CTF 题目环境)中,文件名是严格区分大小写的。
协议部分 (PHP://filter):PHP 解释器能够识别并将其处理为 php://filter,这部分你绕过了 str_replace 的限制,是正确的。
资源部分 (resource=flag.PHP):这里你写的是 .PHP (大写)。服务器的文件系统中真实存在的文件名大概率是 flag.php (小写)。因为 Linux 找不到名为 flag.PHP 的文件,所以 include 失败并抛出了 failed to open stream
我们考虑使用?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
这段 Payload 由三部分组成:
协议头 (data://):告诉 PHP 这是一个“数据流”协议,而不是一个真实存在的文件。
类型声明 (text/plain;base64):告诉 PHP 后面的数据是纯文本格式,并且是用 Base64 编码过的。
数据负载 (PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==):这是经过编码后的实际 PHP 代码。
2. 内部逻辑:代码解释
我们将那串 Base64 字符串解码:
PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg== \rightarrow <?php system('cat flag.php');?>
system(…):这是一个强大的 PHP 函数,用于执行系统命令并直接输出结果。
‘cat flag.php’:这是 Linux 命令。cat 命令用于查看文件内容。所以这句代码的意思是:“请帮我把服务器上 flag.php 的内容读出来并打印在页面上。”
3. 为什么这样写能绕过过滤?
回到你最初看到的题目源码:
$file = str_replace(“php”, “???”, $file);
巧妙的“隐身术”
绕过关键词:在 Payload 中,我们的 PHP 代码是被 Base64 编码过的。编码后的字符串里完全没有 php 这三个字母。
协议名绕过:如果题目对协议名也做了限制(比如 data 变 ???),你可以利用 PHP 伪协议不区分大小写的特性,写成 Data://。只要避开小写的 php 组合,str_replace 就找不到目标,也就无法拦截。
4. 执行流程图解
输入注入:你把 data://… 传给file。过滤检查:strreplace扫描字符串。由于Base64编码后的字符乱序,它找不到php,直接放行。文件包含:include(file。 过滤检查:str_replace 扫描字符串。由于 Base64 编码后的字符乱序,它找不到 php,直接放行。 文件包含:include(file。过滤检查:strreplace扫描字符串。由于Base64编码后的字符乱序,它找不到php,直接放行。文件包含:include(file) 接收到了这个数据流。
动态解码:PHP 引擎会自动将 Base64 数据解码回 <?php system('cat flag.php');?>。
代码执行:因为 include 函数会解析并执行 PHP 标签内的内容,服务器便乖乖地执行了 cat flag.php 命令,把 flag 发送到了你的浏览器。