更新记录:
2024.8.11 完成
2024.8.18 回归了下之前的问题,并新增了修复方法
2024.8.25 在Java17下进行了漏洞的复现(未成功)
文章内容:
1、shiro反序列化源码分析
2、利用工具的代码分析
0x01 环境搭建
1、基于shadowsock5/shiro-root: shiro漏洞环境(1.2.4) (github.com) 搭建shiro环境
2、利用SummerSec/ShiroAttack2: shiro反序列化漏洞综合利用,包含(回显执行命令/注入内存马)修复原版中NoCC的问题 https://github.com/j1anFen/shiro_attack 验证漏洞
0x02 漏洞分析
搜索readobject相关代码
定位到org/apache/shiro/io/DefaultSerializer.java
ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
BufferedInputStream bis = new BufferedInputStream(bais);
try {
ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
@SuppressWarnings({"unchecked"})
T deserialized = (T) ois.readObject();
ois.close();
return deserialized;
}
主要关注这一段代码:尝试通过自定义的 ClassResolvingObjectInputStream
类来解析 bis
中,创建一个ObjectInputStream对象。
使用 ois.readObject()
方法读取对象。
这个就是我们要找的反序列化相关的readobject。
查看调用的地方org/apache/shiro/mgt/AbstractRememberMeManager.java
再找上一步调用
到这里就知道了这条链路最终是通过login触发的。
断点调试
这里注意登录失败的断点和登录成功的断点位置不一致(从login方法那里开始打断点的话)。
最关键的一步—反序列化
之后就是反序列化创建对象,执行代码了。
可以看到分析的结果和实际调试的结果是一致的。
0x03 漏洞修复
重写方法,使得反序列化之前获取对象的名称,并根据白名单校验
尝试一:
=====以下方法错误=====2024.8.18
ObjectStreamClass osc = ObjectStreamClass.lookupAny(ois.readObject().getClass()); 会直接序列化对象
后续失败,是因为上次的ois在抛出异常前没有关闭
==========================
这里简单的举个例子(没有成功?)
非常简单粗暴的方法,直接获取反序列化对象的类名,如果不等于某些类名,就直接抛出异常
实测加入代码前
加入代码后仍会有一次新增的dnslog记录,但是之后不会再有新增的记录,why?
抛出的异常,应该没有反序列化对象
没有增加代码时候,log记录了序列化的对象序列化的类
(这里添加代码后,每次发送请求都是使用新的jssessionid)
=====
尝试二:
重写resolveclass
protected Class resolveClass(ObjectStreamClass osc) throws IOException,ClassNotFoundException {
try {
String className = osc.getName();
// 常见的危险类黑名单
List<String> dangerousClasses = Arrays.asList(
"java.util.PriorityQueue",
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"java.rmi.server.RemoteObjectInvocationHandler",
"javax.naming.spi.ObjectFactory",
"java.lang.reflect.Proxy"
);
// 检查是否包含危险类
for (String dangerousClass : dangerousClasses) {
if (className.equals(dangerousClass) || className.contains(dangerousClass)) {
throw new ClassNotFoundException("Attempted to load dangerous class [" + className + "]");
}
}
// 针对 org.apache 包的特定处理
if (className.startsWith("org.apache.")) {
if (className.startsWith("org.apache.shiro.subject.")) {
return ClassUtils.forName(className);
}
throw new ClassNotFoundException("Attempted to load dangerous class from org.apache package [" + className + "]");
}
// 白名单检查
if (className.startsWith("java.lang") || className.startsWith("java.util")) {
return ClassUtils.forName(className);
} else {
throw new ClassNotFoundException("Class not allowed [" + className + "]");
}
} catch (UnknownClassException uce) {
throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", uce);
}
}
0x04 问题
1、添加代码后没有反序列化对象,为什么还会执行一次?(已解决24.8.18)
2、实际生产中一般是继承ObjectInputStream然后重写他resolveClass方法(已实现24.8.18)
3、dnslog的请求个数的问题
如果用户请求解析 www.example.com
:
用户端:
发送一个 DNS 查询请求到本地 DNS 解析服务器。
本地 DNS 解析服务器(若未缓存):
向根 DNS 服务器发送请求。
向 TLD DNS 服务器发送请求。
向权威 DNS 服务器发送请求。
(4个请求)
0X05 Java17下的漏洞复现
在 Java 17 中,TemplatesImpl 类和相关的内部 API 被封装在模块系统中,并且不再对外部模块公开。
即使检测到了秘钥,也无法利用
0x06 参考
Java安全之Shiro 550反序列化漏洞分析 – nice_0e3 – 博客园 (cnblogs.com)
Java反序列化(六) | Shiroの起始篇(环境搭建+原理分析) – h0cksr – 博客园 (cnblogs.com)