shiro反序列化免疫方案

一、本文基于 oneblog 为基础作为方案演示,详情请见 jdk反序列化安全防护研究:续 文中的环境

二、本文的目的是解答一个哥们儿的问题:能不能做到只对shiro进行反序列化限制 ,尤其是 java.util.PriorityQueue 确实不敢进行全局限制

三、本文代码演示了 黑名单+白名单 的一个思路,shiro场景复杂情况下请自行调整,勿喷

 

通过学习研究大佬的一系列shiro的文章,我们在 org/apache/shiro/io/ClassResolvingObjectInputStream 中可以找到如下代码

图中存在 java.util.PriorityQueue 是因为我截取的是我在idea中的调试模式,用反序列化payload进行弹计算机的情况

如果我们重写对应的代码,其实就可以做到 白名单+黑名单 的安全方案了。

编码开始:

1. oneblog使用的是springboot,我们可以使用一个很简答的操作就能改写原始逻辑:冲掉shiro的原始类

如上图所示,oneblog使用shiro的rememberMe功能存在于 blog-admin 之中

    1.1 我们就直接创建一个同名为 org.apache.shiro.io 的package,并在其中创建同名的 ClassResolvingObjectInputStream 类

    1.2 我们将idea反编译后的 ClassResolvingObjectInputStream 类代码直接覆盖到该自建类之中

    1.3 我们来验证下,我们自建的类是否冲掉了原始的逻辑

    

2. 如图所示,我们在该类中的两个方法上都是打上断点,然后开始调试

3. 

4. 如下图所示,我们新建的类中 osc.getName() 可以获取到具体的类名

同时为了调试,我们删掉 JSESSIONID 只保留rememberMe 时,也会触发 rememberMe 的反序列化功能

这样我们可以得到一次正常反序列化所需要的类

org.apache.shiro.subject.SimplePrincipalCollection
java.util.LinkedHashMap
java.util.HashMap
java.util.LinkedHashSet
java.util.HashSet
java.lang.Long
java.lang.Number

5. 所以我们现在将自己创建的 ClassResolvingObjectInputStream 类文件进行如下修改

package org.apache.shiro.io;

import org.apache.shiro.util.ClassUtils;
import org.apache.shiro.util.UnknownClassException;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;

public class ClassResolvingObjectInputStream extends ObjectInputStream {
    public ClassResolvingObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

    protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
        try {
            String s = osc.getName();
            // 干掉常见的gadget,为了避免 [ ; 符号,必须使用contains方法
            // 简单的使用 s.equals 可能导致fastjson 以前出现的黑名单逃逸问题
            if (s.contains("java.util.PriorityQueue") || s.contains("xsltc.trax.TemplatesImpl")) {
                throw new ClassNotFoundException("Unable to load Dangerous ObjectStreamClass [" + osc + "]");
            }
            if (s.contains("org.apache.")) {
                // 直接干掉了 org.apache ,但是要保留shiro自己
                if (s.startsWith("org.apache.shiro.subject.")) {
                    return ClassUtils.forName(s);
                }
                throw new ClassNotFoundException("Unable to load Dangerous ObjectStreamClass [" + osc + "]");
            }
            // 使用白名单保证业务的正常开展
            if (s.startsWith("java.lang") || s.startsWith("java.util")) {
                return ClassUtils.forName(s);
            } else {
                throw new ClassNotFoundException("Unable to load Dangerous ObjectStreamClass [" + osc + "]");
            }
        } catch (UnknownClassException var3) {
            throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", var3);
        }
    }
}

 

所以,现在执行时反序列化中rce时,由于触发了安全规则会直接导致异常,达到断链的目的,请见下图

同时,通过测试,正常的 rememberMe 功能不受影响。

 

总结:

一、我们在使用shiro时必须严格控制 cookieRememberMeManager 的密钥,建议采用256或512位的长度,以保证爆破难度

二、联系开发人员,对shiro底层类进行改写操作,保证就算得到了key也难以反序列化rce

 

谢谢

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章