Fixes #811 by implementing an enumerator for PostgreSQL.

git-svn-id: file:///home/svn/framework3/trunk@8371 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
Tod Beardsley
2010-02-05 15:20:59 +00:00
parent aeba7e0429
commit c8cdf9c938
2 changed files with 219 additions and 4 deletions
+82 -4
View File
@@ -27,7 +27,7 @@ module Exploit::Remote::Postgres
Opt::RPORT(5432),
OptString.new('DATABASE', [ true, 'The database to authenticate against', 'template1']),
OptString.new('USERNAME', [ true, 'The username to authenticate as', 'postgres']),
OptString.new('PASSWORD', [ true, 'The password for the specified username', '']),
OptString.new('PASSWORD', [ false, 'The password for the specified username. Leave blank for a random password.', '']),
OptBool.new('VERBOSE', [false, 'Enable verbose output', false]),
OptString.new('SQL', [ false, 'The SQL query to execute', 'select version()']),
OptBool.new('RETURN_ROWSET', [false, "Set to true to see query result sets", true])
@@ -78,16 +78,19 @@ module Exploit::Remote::Postgres
def postgres_logout
ip = datastore['RHOST']
port = datastore['RPORT']
verbose = datastore['VERBOSE']
if self.postgres_conn
self.postgres_conn.close if(self.postgres_conn.kind_of?(Connection) && self.postgres_conn.instance_variable_get("@conn"))
self.postgres_conn = nil
end
print_status "#{ip}:#{port} Postgres - Disconnected" if datastore['VERBOSE']
print_status "#{ip}:#{port} Postgres - Disconnected" if verbose
end
# If not currently connected, postgres_query will attempt to connect. If an
# error is encountered while executing the query, it will return with
# :error ; otherwise, it will return with :complete.
# TODO: move print_status up to the module; functions like this should just
# return things like error codes and :status and the like.
def postgres_query(sql=nil,doprint=false)
ip = datastore['RHOST']
port = datastore['RPORT']
@@ -115,8 +118,8 @@ module Exploit::Remote::Postgres
return :error
end
postgres_print_reply(resp,sql) if doprint
print_good "#{ip}:#{port} Postgres - Command complete."
return :complete
print_good "#{ip}:#{port} Postgres - Command complete." if datastore['VERBOSE']
return resp
end
end
@@ -143,5 +146,80 @@ module Exploit::Remote::Postgres
return :complete
end
# postgres_fingerprint attempts to fingerprint a remote Postgresql instance,
# inferring version number from the failed authentication messages.
def postgres_fingerprint(args={})
postgres_logout if self.postgres_conn
db = args[:database] || datastore['DATABASE']
username = args[:username] || datastore['USERNAME']
password = args[:password] || datastore['PASSWORD']
rhost = args[:server] || datastore['RHOST']
rport = args[:port] || datastore['RPORT']
uri = "tcp://#{rhost}:#{rport}"
verbose = args[:verbose] || datastore['VERBOSE']
begin
self.postgres_conn = Connection.new(db,username,password,uri)
rescue RuntimeError => e
version_hash = analyze_auth_error e
return version_hash
end
if self.postgres_conn # Just ask for the version.
resp = postgres_query("select version()",false)
ver = resp.rows[0][0].split(/\s/)[1]
return {:auth => ver}
end
end
# Matches up filename, line number, and routine with a version.
# These all come from source builds of Postgres. TODO: check
# in on the binary distros, see if they're different.
def analyze_auth_error(e)
fname,fline,froutine = e.to_s.split("\t")[3,3]
fingerprint = "#{fname}:#{fline}:#{froutine}"
case fingerprint
when "Fauth.c:L395:Rauth_failed" ; return {:preauth => "7.4.26-27"} # Failed (bad db, bad credentials)
when "Fpostinit.c:L264:RInitPostgres" ; return {:preauth => "7.4.26-27"} # Failed (bad db, good credentials)
when "Fauth.c:L452:RClientAuthentication" ; return {:preauth => "7.4.26-27"} # Rejected (maybe good, but not allowed due to pg_hba.conf)
when "Fauth.c:L400:Rauth_failed" ; return {:preauth => "8.0.22-23"} # Failed (bad db, bad credentials)
when "Fpostinit.c:L274:RInitPostgres" ; return {:preauth => "8.0.22-23"} # Failed (bad db, good credentials)
when "Fauth.c:L457:RClientAuthentication" ; return {:preauth => "8.0.22-23"} # Rejected (maybe good)
when "Fauth.c:L337:Rauth_failed" ; return {:preauth => "8.1.18-19"} # Failed (bad db, bad credentials)
when "Fpostinit.c:L354:RInitPostgres" ; return {:preauth => "8.1.18-19"} # Failed (bad db, good credentials)
when "Fauth.c:L394:RClientAuthentication" ; return {:preauth => "8.1.18-19"} # Rejected (maybe good)
when "Fauth.c:L362:Rauth_failed" ; return {:preauth => "8.2.14-15"} # Failed (bad db, bad credentials)
when "Fpostinit.c:L319:RInitPostgres" ; return {:preauth => "8.2.14-15"} # Failed (bad db, good credentials)
when "Fauth.c:L419:RClientAuthentication" ; return {:preauth => "8.2.14-15"} # Rejected (maybe good)
when "Fauth.c:L1003:Rauth_failed" ; return {:preauth => "8.3.8"} # Failed (bad db, bad credentials)
when "Fpostinit.c:L388:RInitPostgres" ; return {:preauth => "8.3.8-9"} # Failed (bad db, good credentials)
when "Fauth.c:L1060:RClientAuthentication" ; return {:preauth => "8.3.8"} # Rejected (maybe good)
when "Fauth.c:L1017:Rauth_failed" ; return {:preauth => "8.3.9"} # Failed (bad db, bad credentials)
when "Fauth.c:L1074:RClientAuthentication" ; return {:preauth => "8.3.9"} # Rejected (maybe good, but not allowed due to pg_hba.conf)
when "Fauth.c:L258:Rauth_failed" ; return {:preauth => "8.4.1"} # Failed (bad db, bad credentials)
when "Fpostinit.c:L422:RInitPostgres" ; return {:preauth => "8.4.1-2"} # Failed (bad db, good credentials)
when "Fauth.c:L349:RClientAuthentication" ; return {:preauth => "8.4.1"} # Rejected (maybe good)
when "Fauth.c:L273:Rauth_failed" ; return {:preauth => "8.4.2"} # Failed (bad db, bad credentials)
when "Fauth.c:L364:RClientAuthentication" ; return {:preauth => "8.4.2"} # Rejected (maybe good)
else
return {:unknown => fingerprint}
end
end
def postgres_password
if datastore['PASSWORD'].to_s.size > 0
datastore['PASSWORD'].to_s
else
Rex::Text.rand_text_english(rand(6)+2)
end
end
end
end
@@ -0,0 +1,137 @@
##
# $Id$
##
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Postgres
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
# Creates an instance of this module.
def initialize(info = {})
super(update_info(info,
'Name' => 'PostgreSQL Login Utility',
'Description' => %q{
Enumerates the verion of PostgreSQL servers.
},
'Author' => [ 'todb' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'www.postgresql.org' ]
],
'Version' => '$Revision$' # 2009-02-05
))
register_options([ ], self.class) # None needed.
deregister_options('SQL', 'RETURN_ROWSET')
end
# Loops through each host in turn. Note the current IP address is both
# ip and datastore['RHOST']
def run_host(ip)
user = datastore['USERNAME']
pass = postgres_password
do_fingerprint(user,pass,datastore['DATABASE'],datastore['VERBOSE'])
end
# Alias for RHOST
def rhost
datastore['RHOST']
end
# Alias for RPORT
def rport
datastore['RPORT']
end
# Test the connection with Rex::Socket before handing
# off to Postgres-PR, since Postgres-PR takes forever
# to return from connection errors. TODO: convert
# Postgres-PR to use Rex::Socket natively to avoid
# this double-connect business.
def test_connection
begin
sock = Rex::Socket::Tcp.create(
'PeerHost' => rhost,
'PeerPort' => rport
)
rescue Rex::ConnectionError
print_error "#{rhost}:#{rport} Connection Error: #{$!}" if datastore['VERBOSE']
raise $!
end
end
# Test the connection, then actually do all the fingerprinting.
def do_fingerprint(user=nil,pass=nil,database=nil,verbose=false)
begin
test_connection
rescue Rex::ConnectionError
return :done
end
msg = "#{rhost}:#{rport} Postgres -"
password = pass || postgres_password
print_status("#{msg} Trying username:'#{user}' with password:'#{password}' against #{rhost}:#{rport} on database '#{database}'") if verbose
result = postgres_fingerprint(
:db => database,
:username => user,
:password => password
)
if result[:auth]
print_good "#{rhost}:#{rport} Postgres - Logged in to '#{db}' with '#{user}':'#{password}'" if verbose
print_good "#{rhost}:#{rport} Postgres - Version #{result[:auth]} (Post-Auth)"
elsif result[:preauth]
print_good "#{rhost}:#{rport} Postgres - Version #{result[:preauth]} (Pre-Auth)"
else # It's something we don't know yet
print_status "#{rhost}:#{rport} Postgres - Authentication Error Fingerprint: #{result[:unknown]}" if datastore['VERBOSE']
print_error "#{rhost}:#{rport} Postgres - Version Unknown (Pre-Auth)"
end
# Reporting
report_service(
:host => rhost,
:port => rport,
:name => "postgresql",
:info => result.values.first
)
if self.postgres_conn
report_auth_info(
:host => rhost,
:proto => "postgresql",
:user => user,
:pass => password,
:targ_host => rhost,
:targ_port => rport
)
end
if result[:unknown]
report_note(
:host => rhost,
:proto => 'postgresql',
:port => rport,
:data => "Unknown Pre-Auth fingerprint: #{result[:unknown]}"
)
end
# Logout
postgres_logout
end
end