Apache Unomi介绍
Apache Unomi具有隐私管理、用户/事件/目标跟踪、报告、访客资料管理、细分、角色、A/B测试等功能,可以作为Web CMS、个性化服务、原生移动应用的分析服务、具有分段功能的集中配置文件管理系统、授权管理中心。
漏洞介绍
CVE-2020-13942是CVE-2020-11975的绕过
该漏洞描述的是Unomi针对MVEL表达式(和OGNL表达式)的未进行敏感函数的过滤,直接解析,导致任意代码执行
本文主要对MVEL这条利用链做代码层面的分析
poc请求包
漏洞利用时相关的Poc请求包如下,后续代码分析会涉及到
分析思路
MVEL表达式解析的模式有两种,分别是
- 解释模式
MVEL.eval(expression, vars)
- 编译模式
MVEL.compileExpression(expression) + MVEL.executeExpression(compiled, vars)
具体可以参考:MVEL解析表达式
倒推分析
通过在源码中搜索关键字script::
与MVEL.executeExpression(
可以初步定位出现问题的代码位于源码的如下位置
unomi-unomi-root-1.5.1\persistence-elasticsearch\core\src\main\java\org\apache\unomi\persistence\elasticsearch\conditions\ConditionContextHelper.java
我们构造的恶意表达式就是通过这条代码进行解析从而导致RCE的
基于此方法向上回溯,找调用它的地方
继续向上回溯
看到condition.getParameterValues()
基本可以确定我们的思路是正确的
接下来主要找入口点,即往/context.json
这个路径下发送数据是如何一步一步到ConditionContextHelper#getContextualCondition()
的
向上回溯getContextualCondition()
有三处调用到
几个方法分别是buildFilter()
、count()
、eval()
,因为看到poc里有关键字filter
,所以先跟入buildFilter()
,看看是不是从这里进来的
接着向上回溯,看buildFilter()
的调用位置
最终回溯到的位置(即入口位置)位于ElasticSearchPersistenceServiceImpl
重写的excute()
方法中
看完buildFilter()
,然后接着看eval()
(count()
方法作用不大,忽略)eval()
最终回溯到的位置(即入口位置)位于ElasticSearchPersistenceServiceImpl
重写的testMatch()
方法中
然后只需要找出调用如下两个方法的位置就可以找到完整的调用链
ElasticSearchPersistenceServiceImpl#excute()
ElasticSearchPersistenceServiceImpl#testMatch()
正推分析
接下来我开始从请求的入口处开始进行分析
在docker环境中找到了如下jar包
unomi-wab-1.5.1.jar
这个jar包包含了处理web的逻辑,其对应着的源码包是
unomi-unomi-root-1.5.1/wab
里面的contextServlet.class
对应着我们请求的/context.json
该类继承了HttpServlet
类,且无doGet()
和doPost()
方法,但是有用于处理请求的service()
方法
注:
service()
此方法是接收http-request请求的原始方法,请求会发送到这个方法然后才会向doGet()
或doPost()
分发
这里的contextRequest
就是我们poc传入的body(可以根据getSessionId
等方法以及响应数据判断出来)
接着这里会将contextRequest
作为参数传入handleRequest()
接着看到355行
在执行getFilters()
后,拿到的应该是如下部分
之后进入for循环,比较重要的是有一个personalizedContent
对象,这个类的数据结构如下:
正好对应着我们上图传入的内容
然后跟进359行的personalizationService.filter()
方法
注:personalizationService是interface,我们知道,接口不可以实例化。但是接口对象可以指向它的实现类对象。
所以这里的filter()
应该是其Impl类的filter()
方法,所以我们跟进位于源码包
unomi-unomi-root-1.5.1\services\src\main\java\org\apache\unomi\services\impl\profiles\ProfileServiceImpl.java
的filter()
方法
然后跟进67行的profileService.matchCondition()
也在同一个源码包下
在781行出现了最关键的persistenceService.testMatch()
,而上半段分析的ElasticSearchPersistenceServiceImpl
正好是persistenceService
唯一的实现类
至此,整个调用链已经打通,我们整理一下调用链
调用链整理
org.apache.unomi.web.ContextServlet#service() -> org.apache.unomi.web.ContextServlet#handleRequest() -> org.apache.unomi.services.impl.personalization.PersonalizationServiceImpl#filter() -> org.apache.unomi.services.impl.profiles.ProfileServiceImpl#matchCondition() -> org.apache.unomi.persistence.elasticsearch.ElasticSearchPersistenceServiceImpl#testMatch() -> org.apache.unomi.persistence.elasticsearch.conditions.ConditionEvaluatorDispatcher#eval() -> org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper#getContextualCondition() -> org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper#parseParameter() -> org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper#executeScript()
最终被executeScript()
的
MVEL.executeExpression(mvelExpressions.get(script), context);
解析执行