close search bar

Sorry, not available in this language yet

close language selection

Apache Struts research at scale, Part 2: Execution environments

Christopher Fearon

Mar 10, 2020 / 7 min read

In August 2019, the Synopsys Cybersecurity Research Center (CyRC) coordinated with the Apache Software Foundation to publish Apache Struts Security Advisory S2-058. The advisory represents research undertaken in Belfast that focused on 64 vulnerabilities through 115 versions of Struts, identifying roughly 50 affected versions per vulnerability. We wanted to share our experiences in a series of blog posts.

This blog series is for a technical audience. It discusses insights, problems we encountered, and solutions we came up with during the project:

This is the second blog post in the series. We recommend looking at the first post if you haven’t had a chance.

Why we researched Apache Struts

During August 2018, we examined a newly released Apache Struts remote code execution vulnerability (CVE-2018-11776 / S2-057). Through creating our own proof-of-concept and testing it against Apache Struts’ past releases, we discovered that the vulnerability affected more versions than were initially reported. We reported these findings in accordance with our responsible disclosure policy. But our discovery also prompted the question, what about all the previous Apache Struts vulnerabilities? We set out to create a system where we could conduct vulnerability research at scale.

In the beginning

Diagram Illustrating Apache Struts Research Project in Java Environments

Java was one of the first languages to promote the idea of universal binaries and components, or “build once, execute everywhere.” It was meant to provide a stable platform for development and execution. However, Java is also quite old and has seen many changes over the years. Java platforms have different nuances, and in the case of Struts 2, there is no single servlet runner either.

Apache Struts 2.0.1, the first version of Struts 2, was released over a decade ago in September 2006, when Java 1.5 (which still supported older operating systems, such as Windows 95) was still king. (It doesn’t that seem deprecating older operating systems is any easier now, as Windows 7, released in 2009, left support only recently.)

Rather than reproduce every single environment that existed previously, we decided to take a simpler approach in the beginning, trying a reasonable in-between Java runtime and servlet runner and seeing where our edge cases lay.

Our initial environment

We started with a quick and dirty approach for running our different versions of Struts. We had set up a basic Tomcat installation on Ubuntu 18 LTS using the system’s provided Tomcat 8 and Java 8 packages. Java 8 was capable of compiling for 1.5, our starting point. And Tomcat 8 was known to be backward compatible back to the 1996 Java servlet 1.0 specification, which was far older than what we expected from the Struts 2 WAR files. Also, it was modern enough that in theory, many later versions of Struts could run in the same instance.

Surprisingly, it’s quick and trivial to set this up on Ubuntu 18. It requires only a few chained commands, and within a few minutes, you’ll have our initial configuration running:

Bash shell

sudo apt-get update && sudo apt-get -y dist-upgrade && sudo apt-get -y install openjdk-8-jdk tomcat8 tomcat8-user && sudo service tomcat8 stop && sudo update-java-alternatives -s java-1.8.0-openjdk-amd64 && sudo service tomcat8 start

Bash Shell

Many of the WARs we prepared ran without error or issues, as expected. We often used the showcase WAR file initially to test some of the basic Struts functionality first.

We also set up remote debugging functionality with the Eclipse IDE to help us track what happened when we tried to trigger exploits.

Creation and reproduction of exploits

When reproducing exploits, or creating new exploits based on known information, we ran into a variety of barriers, which we will detail below.



This vulnerability allows a denial-of-service (DoS) via an insufficient ban in CookieInterceptor leading to ClassLoader manipulation.

When we started sending payloads to the server, we noticed with remote debugging that the payload was not triggering anything. When we looked closer for the payload’s existence, we couldn’t find it at all in our remote debugging tools. It was also returning a Tomcat 404 error message.

Apache Struts Environments Diagram Showing Error Message



Considering the payload, we didn’t expect this to be a problem produced by the environment. However, it eventually became clear that something was sanitizing the data before it was passed to the servlet. Even more curious, when we checked the access log produced by Tomcat, we saw that the accessed URL was different from what we had expected.

Tomcat 8 access log

“GET /j8- HTTP/1.1” 404 989

We tried switching to Tomcat 6.5 and reran the exploit. We discovered that the payloads functioned properly. Further, the Tomcat 6.5 access log also looked very different.

Tomcat 6.5 access log

“GET /j8- HTTP/1.0” 200 12502

This is a good example of how far you can go toward mitigating vulnerabilities simply by keeping infrastructure around older software upgraded. On the reverse side, it might mean that exploits like these exist in other products but go undetected because the average install is expected to be running more modern infrastructure.


This vulnerability allows OGNL evaluation on ExceptionDelegator. OGNL expressions are Turing complete and often have access to base Java classes, which attackers can further use to execute code within the context and capabilities of the host application, leading to remote code execution (RCE).

This vulnerability introduced a payload through the cookie header. What was interesting about this vulnerability was that the inclusion of Apache Struts was enough to make it exploitable, so even the blank.war application was susceptible. In our example payload, we decided just to launch a shell command using OGNL and touch a file:

Apache Struts Research Execution Environments HTTP Payload Exchange Screenshot

This worked and produced /tmp/RCE when it came to Tomcat 6. However, when we tried this same payload on Tomcat 8, it was quick to sanitize the cookie input and declare its displeasure in status logs:

Tomcat 8 status log

Again, this shows that keeping infrastructure around older software upgraded can go a long way toward mitigating vulnerabilities.

Java Runtime Environment


This vulnerability allows an RCE attack when using the Struts REST plugin with XStream handler to parse XML.

We set out to validate whether this RCE attack would function. We were able to reproduce the vulnerability, which results in a cast exception, using an adapted version of the publicly known payloads.

Java Runtime Environment 8 vulnerability exception

com.thoughtworks.xstream.converters.ConversionException: java.lang.String cannot be cast to$Service : java.lang.String cannot be cast to$Service

When we tried these same exploits against Java Runtime Environment 11 and a variety of others, we discovered that the payloads did not work. This was in part due to changes that occurred in runtime distributions, which lead to a com.thoughtworks.xstream.mapper.CannotResolveClassException.

However, we were able to modify the payload further so it did function in 11. Our experience shows that there are cases where a vulnerability seems mitigated but further modifications can be made to create an exploitation. In this case, it wasn’t enough just to upgrade the environment around the vulnerable component to mitigate it.

In our next blog post about exploitation, we’ll go into more depth about why it is necessary to modify exploits and similar scenarios to this.



The error pages now describe S2-006 as a cross-site scripting (XSS) vulnerability, thanks to this project.

S2-006 was originally published with basic information that didn’t make much sense:

S2-006 vulnerability summary

Cross site scripting (XSS) vulnerability on <s:url> and <s:a> tags

For both the <s:url> and the <s:a> tag, it is possible to inject parameter values that do not get escaped properly when the tag’s resulting URLs are constructed and rendered. The following scenarios are known:

  • A parameter value included in the construction of a <s:a> result can inject an unescaped double quote, thus being able to inject code in the resulting HTML by escaping the rendered href attribute.
  • Both the <s:url> and the <s:a> tag fail to escape <script> tags when includeParams is set to any other value than “none”, which can be exploited by calling the containing JSP/action with GET parameters such as http://localhost/foo/bar.action? <script>alert(1)</script>test=hello

Through a lot of trial and error, we eventually learned that these vulnerable tags were generated by error pages when Struts was running in development mode. In development mode, Apache Struts generates its own error pages. Otherwise, Apache Tomcat generates error pages, which aren’t susceptible to these issues.

In conclusion

Environments matter, even in Java, where environments are usually very compatible. Runtime version, server software, and even software configuration all have an effect. Surprisingly, new runtimes and servlet software can mitigate some issues. In other cases, they can simply break an exploit, which requires an alternate payload to work around the issue.


  • Stephen Mort
  • Padraig Donnelly
  • Ashley Stone
  • Omer Demirok

Continue Reading

Explore Topics