diff --git a/lib/msf/core/exploit/postgres.rb b/lib/msf/core/exploit/postgres.rb index cf20ba57b6..78b8a07339 100644 --- a/lib/msf/core/exploit/postgres.rb +++ b/lib/msf/core/exploit/postgres.rb @@ -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 diff --git a/modules/auxiliary/scanner/postgres/postgres_version.rb b/modules/auxiliary/scanner/postgres/postgres_version.rb new file mode 100644 index 0000000000..30de086f7e --- /dev/null +++ b/modules/auxiliary/scanner/postgres/postgres_version.rb @@ -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