Back to Blog

【CTF题解】BUUCTF - cat flag 命令注入与字符剥离绕过

BUUCTF Medium

[BUUCTF] cat flag 题解

1. 题目源码

题目给出的 PHP 源代码如下:

<?php

if (isset($_GET['cmd'])) {
    $cmd = $_GET['cmd'];
    if (!preg_match('/flag/i', $cmd))
    {
        $cmd = escapeshellarg($cmd);
        system('cat ' . $cmd);
    }
} else {
    highlight_file(__FILE__);
}
?>

逻辑分析:

  1. 程序通过 GET 方式接收 cmd 参数。
  2. 使用正则 preg_match('/flag/i', $cmd) 对字符串中的 flag 关键字(不区分大小写)进行过滤。
  3. 若未触发正则拦截,则对 $cmd 调用 escapeshellarg() 函数进行转义,最后拼接至 system('cat ' . $cmd) 并执行命令。

2. 漏洞原理与延伸

1. escapeshellarg() 剥离非法 UTF-8 字符绕过

escapeshellarg() 的设计初衷是为字符串两端包裹单引号,并转义已有的单引号,使得整个字符串作为单一安全参数传递给 Shell,防止命令注入。

但是在 PHP 7.3(某些具体版本)中,escapeshellarg() 在处理包含非法 UTF-8 字符字节(例如 %80 / \x80)的字符串时,会将该非法字节直接从结果中剥离删除

  • 绕过正则:传入 fl%80ag 时,原始输入字符串为 fl\x80ag,不匹配正则表达式 "flag",从而顺利绕过 preg_match('/flag/i', $cmd) 黑名单检测。
  • 执行命令:经过 escapeshellarg() 处理后,0x80 字节被系统剔除,处理后的字符串恢复为 'flag',最终拼接为 cat 'flag' 并成功执行。

2. 知识点拓展:escapeshellarg() + escapeshellcmd() 经典组合漏洞

在 CTF 题目中,经常会见到将这两个转义函数组合使用的场景,例如:

<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
    highlight_file(__FILE__);
} else {
    $host = $_GET['host'];
    $host = escapeshellarg($host);
    $host = escapeshellcmd($host);
    $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
    echo 'you are in sandbox '.$sandbox;
    @mkdir($sandbox);
    chdir($sandbox);
    echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
  • 产生机制
    1. escapeshellarg() 率先把 $host 转义并用单引号包裹成单一参数。
    2. 紧接着对处理结果调用 escapeshellcmd(),后者会再次转义字符串中的 Shell 元字符(如 & | ; < > \ 及未配对的单引号)。这破坏了原本建立的单引号边界。
  • 危害后果:攻击者可利用单引号配对错位拆分参数,形成参数注入(Argument Injection)(例如向 nmap 注入追加参数以实现任意文件写入/命令执行),而非直接通过 ; 进行简单的指令拼接。

3. 解题过程

Step 1. 尝试绕过限制并读取 flag

构造包含非法 UTF-8 字节的 Payload 绕过黑名单限制:

?cmd=/fl%80ag

image-20260811232717442

页面返回提示我们并没有完整拿到 flag。


Step 2. 抓包分析 HTTP 响应头

抓包查看完整的 HTTP 响应包数据:

image-20260811232844835

从响应头中可注意到 Server: openresty。这表明服务器使用了 OpenResty/Nginx 架构,提示我们可以尝试读取 Nginx 的日志文件来获取更多线索。


Step 3. 读取日志定位真实 Flag 文件

利用 cmd 参数读取默认的 Nginx 访问日志文件 access.log

?cmd=/var/log/nginx/access.log

image-20260811235224984

在日志记录中成功找到真实的 flag 文件名:this_is_final_flag_e2a457126032b42d.php


Step 4. 读取真实 Flag 文件源码

再次结合 %80 字符绕过黑名单,读取目标 PHP 文件:

?cmd=this_is_final_fl%80ag_e2a457126032b42d.php

由于目标文件是 .php 文件,内容会被 PHP 解析执行或被浏览器当作 HTML 隐藏,按下 Ctrl + U 查看网页源代码:

CTF2{f02ea6b9-aa0b-442e-907b-213b82b99107}

image-20260811235549318

成功获取最终 Flag!