Shiro反序列化(CVE-2016-4437)

更新记录:

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)

上一篇
下一篇