于我

【漏洞分析】Confluence Ognl表达式注入漏洞 (CVE-2021-26084)

2021/12/6

背景介绍:

Confluence是一个专业的企业知识管理与协同软件,也可以用于构建企业wiki。它使用Java语言编写, SpringMVC作为中间业务层, webwork2框架作为web层,数据层使用Hibernate。其中,webwork2框架使用Ognl作为表达式语言。

Ognl的全称是Object Graphic Navigation Language(对象图导航语言),它能够通过简单一致的表达式语法实现:存取java对象的任意属性,调用java对象的方法,遍历整个对象的结构图,进行字段类型转化等功能。Struts2系列漏洞就是因为程序未对Ognl表达式做严格过滤,导致的命令执行。

本文主要从代码层面对Confluence CVE-2021-26084 Ognl表达式命令执行漏洞产生的原因进行分析。

1、测试环境准备

靶场环境选择:atlassian-confluence-7.12.3版本

2、漏洞复现

根据官方发布的补丁文件我们可以看到共修改了5个文件,这5个文件均存在漏洞,我们选择其中不需要登录就可以利用漏洞的pages/doenterpagevariables.action页面进行复现分析。

从补丁文件中可以发现,pages/doenterpagevariables.action页面中的漏洞所在点是queryString参数和linkCreation参数。

上图中可以看到Ognl表达式被执行了,证明漏洞存在。

3、漏洞分析

因为是Ognl表达式注入漏洞,所以选择在Ognl.getValue()函数处进行断点。此时,queryString参数的程序调用链如图一所示:

图一

queryString参数value值的调用链,如图二所示:

图二

由此,我们可以看出大概的程序处理逻辑 :webwork接收请求开始 --> velocity模板分析--> Template模板类处理--> velocity node节点处理--> AST语法树分析--> Tag标签处理--> Ognl黑名单--> Ognl处理。

下面是pages/doenterpagevariables.action页面的所有参数:

#tag ("Hidden" "name='queryString'" "value='$!queryString'")

#tag ("Hidden" "name='templateId'" "value='$pageTemplate.id'")

#tag ("Hidden" "name='linkCreation'" "value='$linkCreation'")

#tag ("Hidden" "name='title'" "value=title")

#tag ("Hidden" "name='parentPageId'" "value=parentPageId")

#tag ("Hidden" "name='fromPageId'" "value=fromPageId")

#tag ("Hidden" "name='spaceKey'" "value=spaceKey")

其中queryString参数和linkCreation参数存在漏洞,其他参数不存在漏洞。下面,我们来分析这些参数的不同:

1、使用templateId参数发送恶意数据时,Ognl表达式获取到的值还是'$pageTemplate.id'(如下图所示),相当于后端没有接收传入的templateId value值。所以可以确定,templateId参数是不存在页面传参的,也就不存在漏洞。

2、title传入参数时,后端依然不会接收前端输入的值,也可以说明不存在漏洞。

3、经手动测试发现,parentPageId,fromPageId,spaceKey均与title类似。

通过对比不难发现:其他参数都不存在漏洞,是因为后端没有接收这几个参数的值。具体原因,分析如下:

1、通过对比查看调试时的实例内容,可以发现程序大概是在图示中这两处对前端value值进行的提取。

在这一步,object对象已经获取到了传过来的value值。接下来,我们将逐步演示object获取到value值的方式。

在当前函数下对object对象进行创建和操作分析,目的在于弄清一个问题,即value值的获取是在创建对象时,还是在后来的某种赋值操作中。如下图所示,可以看到此时object还没有获取到value值,

运行到下一步的时候,object获取到了value

进入到applyAttributes方法中继续分析。经过几次函数调用,发现最终逻辑是在node.value()方法中实现的。

程序获取queryString的value时,interpolate参数是True,这时会通过render方法将页面传入的value写入到msg中并返回。

对比

"value='$!queryString'"  "value='$pageTemplate.id'"

"value='$linkCreation'"  "value=title"  "value=parentPageId" 

"value=fromPageId"  "value=spaceKey"

我们发现程序运行到该函数时,value值中如果包含$符号,则interpolage值为ture,就会通过render方法将前端传入的value值赋值到后端对象的value参数中。

这就是为什么两个漏洞所在点的值都有$符号。

但为什么templateId参数值也包含了$,而程序并没有将前端值传入value中?

下面详细分析:

#tag ("Hidden" "name='templateId'" "value='$pageTemplate.id'")

测试使用queryString传入恶意value时的场景,发现获取前端数据绑定到后端value参数前的程序调用链如下所示:

查看当使用templateId传入恶意value时,程序从context上下文中取pageTemplate的值。此处的调用链与queryString一致,如下图所示:

但是当render运行完毕后,发现并没有将前端value值写入到writer中,

下图可以看出此时的context上下文中确实已经获取到了前端传入的templateId值,但是render函数调用链并没有赋值给result参数,导致返回了$pageTemplate.id,

跟进去发现上图中的getVariableValue()函数通过在context上下文中寻找rootString参数,而rootString参数值是$符号后面的字符串。当使用templateId传输时,程序是在context上下文中寻找"pageTemplate"参数名对应的值,这个值我们没办法控制,默认是null,所以templateId参数不存在漏洞。

Confluence会通过黑名单的方式,对要进行Ognl计算的字符串做一次过滤。前面程序的大概执行逻辑中,倒数第二步就是Ognl黑名单,每次在调用ognl.getValue()函数前都需要检验表达式是否安全。

SafeExpressionUtil.isSafeExpression()函数内部逻辑如下图所示:

可以看到:

第一个set禁用了Ognl表示式静态方法、静态参数、构造函数等的调用;

第二和第三个set禁用了常见的获取执行java反射的方式;

第四个set禁用了常用的获取模板context上下文的方式。

因为黑名单的存在,所以常用的获取Runtime类如"".getClass()等代码利用方式已经不可以使用了,但是可以使用

""["class"].forName["java.lang.Runtime"]

进行绕过。

4、漏洞修复

Confluence新版本中取消了所有漏洞点参数中的$符号,程序不会再对前端获取到的值进行Ognl表达式计算,也就不存在Ognl表达式注入导致命令执行的漏洞了。

参考

https://confluence.atlassian.com/doc/confluence-security-advisory-2021-08-25-1077906215.html

https://github.com/httpvoid/writeups/blob/main/Confluence-RCE.md

https://www.anquanke.com/post/id/253398

下一篇:【漏洞预警】Apache Log4j2远程代码执行漏洞

开始免费试用灰度产品

申请试用