230 lines
7.2 KiB
Ruby
230 lines
7.2 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Auxiliary::Scanner
|
|
include Msf::Auxiliary::AuthBrute
|
|
include Msf::Auxiliary::Report
|
|
|
|
def initialize
|
|
super(
|
|
'Name' => 'Oracle iSQLPlus SID Check',
|
|
'Description' => %q{
|
|
This module attempts to bruteforce the SID on the Oracle application server iSQL*Plus
|
|
login pages. It does this by testing Oracle error responses returned in the HTTP response.
|
|
Incorrect username/pass with a correct SID will produce an Oracle ORA-01017 error.
|
|
Works against Oracle 9.2, 10.1 & 10.2 iSQL*Plus. This module will attempt to
|
|
fingerprint the version and automatically select the correct POST request.
|
|
},
|
|
'References' =>
|
|
[
|
|
[ 'URL', 'http://carnal0wnage.attackresearch.com' ],
|
|
],
|
|
'Author' => [ 'CG', 'todb' ],
|
|
'License' => MSF_LICENSE
|
|
)
|
|
|
|
register_options([
|
|
Opt::RPORT(5560),
|
|
OptString.new('URI', [ true, 'Oracle iSQLPlus path', '/isqlplus/']),
|
|
OptString.new('SID', [ false, 'A single SID to test']),
|
|
OptPath.new('SIDFILE', [ false, 'A file containing a list of SIDs', File.join(Msf::Config.install_root, 'data', 'wordlists', 'sid.txt')]),
|
|
OptInt.new('TIMEOUT', [false, 'Time to wait for HTTP responses', 30])
|
|
], self.class)
|
|
|
|
deregister_options(
|
|
"RHOST", "USERNAME", "PASSWORD", "USER_FILE", "PASS_FILE", "USERPASS_FILE",
|
|
"BLANK_PASSWORDS", "USER_AS_PASS", "REMOVE_USER_FILE", "REMOVE_PASS_FILE",
|
|
"BRUTEFORCE_SPEED" # Slow as heck anyway
|
|
)
|
|
|
|
end
|
|
|
|
def sid_file
|
|
datastore['SIDFILE']
|
|
end
|
|
|
|
def hostport
|
|
[target_host,rport].join(":")
|
|
end
|
|
|
|
def uri
|
|
datastore['URI'] || "/isqlplus/"
|
|
end
|
|
|
|
def timeout
|
|
(datastore['TIMEOUT'] || 30).to_i
|
|
end
|
|
|
|
def msg
|
|
msg = "#{hostport} - Oracle iSQL*Plus -"
|
|
end
|
|
|
|
def run_host(ip)
|
|
oracle_ver = get_oracle_version(ip)
|
|
if not check_oracle_version(oracle_ver)
|
|
print_error "#{msg} Unknown Oracle version, skipping."
|
|
return
|
|
end
|
|
begin
|
|
print_status("#{msg} Starting SID check")
|
|
sid_data.each do |sid|
|
|
guess = check_oracle_sid(ip,oracle_ver,sid)
|
|
return if guess and datastore['STOP_ON_SUCCESS']
|
|
end
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
|
|
print_error "#{msg} Cannot connect"
|
|
rescue ::Timeout::Error, ::Errno::EPIPE,Errno::ECONNRESET => e
|
|
print_error e.message
|
|
end
|
|
end
|
|
|
|
def get_oracle_version(ip)
|
|
begin
|
|
res = send_request_cgi({
|
|
'version' => '1.1',
|
|
'uri' => uri,
|
|
'method' => 'GET',
|
|
}, timeout)
|
|
oracle_ver = nil
|
|
if (res.nil?)
|
|
print_error("#{msg} no response")
|
|
elsif (res.code == 200)
|
|
print_status("#{msg} Received an HTTP #{res.code}")
|
|
oracle_ver = detect_oracle_version(res)
|
|
elsif (res.code == 404)
|
|
print_error("#{msg} Received an HTTP 404, check URIPATH")
|
|
elsif (res.code == 302)
|
|
print_error("#{msg} Received an HTTP 302 to #{res.headers['Location']}")
|
|
else
|
|
print_error("#{msg} Received an HTTP #{res.code}")
|
|
end
|
|
return oracle_ver
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
|
|
print_error "#{msg} Cannot connect"
|
|
end
|
|
end
|
|
|
|
def detect_oracle_version(res)
|
|
m = res.body.match(/iSQL\*Plus Release (9\.0|9\.1|9\.2|10\.1|10\.2)/)
|
|
oracle_ver = nil
|
|
oracle_ver = 10 if m[1] && m[1] =~ /10/
|
|
oracle_ver = m[1].to_f if m[1] && m[1] =~ /9\.[012]/
|
|
if oracle_ver
|
|
print_status("#{msg} Detected Oracle version #{oracle_ver}")
|
|
print_status("#{msg} SID detection for iSQL*Plus 10.1 may be unreliable") if oracle_ver == 10.1
|
|
else
|
|
print_error("#{msg} Unknown Oracle version detected.")
|
|
end
|
|
return oracle_ver
|
|
end
|
|
|
|
def check_oracle_version(ver)
|
|
[9.0,9.1,9.2,10].include? ver
|
|
end
|
|
|
|
def build_post_request(ver,sid)
|
|
post_request = nil
|
|
case ver
|
|
when 9.0
|
|
post_request = "action=logon&sqlcmd=&sqlparms=&username=scott&password=tiger&sid=#{sid.strip}&privilege=&Log+In=%B5%C7%C2%BC"
|
|
when 9.1
|
|
post_request = "action=logon&username=a&password=a&sid=#{sid.strip}&login=Login"
|
|
when 9.2
|
|
post_request = "action=logon&username=a&password=a&sid=#{sid.strip}&login=Login"
|
|
when 10
|
|
post_request = "username=a&password=a&connectID=#{sid.strip}&report=&script=&dynamic=&type=&action=&variables=&event=login"
|
|
end
|
|
return post_request
|
|
end
|
|
|
|
def parse_isqlplus_response(res,sid)
|
|
guess = false
|
|
if (res.nil?)
|
|
print_error("#{msg} No response")
|
|
elsif (res.code == 200)
|
|
if (res.body =~ /ORA-01017:/ or res.body =~ /ORA-28273:/)
|
|
if sid.nil? || sid.empty?
|
|
print_good("#{msg} Received ORA-01017 on a blank SID -- SIDs are not enforced upon login.")
|
|
else
|
|
print_good("#{msg} Received ORA-01017, probable correct SID '#{sid.strip}'")
|
|
end
|
|
guess = true
|
|
elsif (res.body =~ /(ORA-12170):/ or res.body =~ /(ORA-12154):/ or res.body =~ /(ORA-12162):/)
|
|
vprint_status("#{msg} Incorrect SID: '#{sid.strip}' (got error code #{$1})")
|
|
elsif res.body =~ /(ORA-12541):/
|
|
print_status("#{msg} Possible correct SID, but got ORA-12541: No Listener error.")
|
|
guess = true
|
|
else
|
|
print_status("#{msg} Received an unknown error") # Should say what the error was
|
|
end
|
|
elsif (res.code == 404)
|
|
print_status("#{msg} Received an HTTP 404, check URIPATH")
|
|
elsif (res.code == 302)
|
|
print_status("#{msg} Received an HTTP 302 redirect to #{res.headers['Location']}")
|
|
else
|
|
print_status("#{msg} Received an unexpected response: #{res.code}")
|
|
end
|
|
|
|
report_isqlplus_service(target_host,res) if res
|
|
return guess
|
|
end
|
|
|
|
def report_isqlplus_service(ip,res)
|
|
sname = datastore['SSL'] ? 'https' : 'http'
|
|
report_service(
|
|
:host => ip,
|
|
:proto => 'tcp',
|
|
:port => rport,
|
|
:name => sname,
|
|
:info => res.headers["Server"].to_s.strip
|
|
)
|
|
end
|
|
|
|
def report_oracle_sid(ip,sid)
|
|
report_note(
|
|
:host => ip,
|
|
:proto => 'tcp',
|
|
:port => rport,
|
|
:type => "oracle.sid",
|
|
:data => ((sid.nil? || sid.empty?) ? "*BLANK*" : sid),
|
|
:update => :unique_data
|
|
)
|
|
end
|
|
|
|
def sid_data
|
|
if datastore['SID'] and not datastore['SID'].empty?
|
|
[datastore['SID']]
|
|
elsif sid_file and ::File.readable? sid_file
|
|
::File.open(sid_file,"rb") {|f| f.read f.stat.size}.each_line.map {|x| x.strip.upcase}.uniq
|
|
else
|
|
raise ArugmentError, "Cannot read file '#{sid_file}'"
|
|
end
|
|
end
|
|
|
|
def check_oracle_sid(ip,oracle_ver,sid)
|
|
post_request = build_post_request(oracle_ver,sid)
|
|
vprint_status "#{msg} Trying SID '#{sid}', waiting for response..."
|
|
res = send_request_cgi({
|
|
'version' => '1.1',
|
|
'uri' => uri,
|
|
'method' => 'POST',
|
|
'data' => post_request,
|
|
'headers' =>
|
|
{
|
|
'Referer' => "http://#{ip}:#{rport}#{uri}"
|
|
}
|
|
}, timeout)
|
|
guess = parse_isqlplus_response(res,sid)
|
|
report_oracle_sid(ip,sid) if guess
|
|
return guess
|
|
end
|
|
|
|
end
|