Skip to content

Commit

Permalink
Use PostgreSQL session type for modules
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanusz-r7 committed Feb 9, 2024
1 parent 9caa2fa commit 30fc29e
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 139 deletions.
30 changes: 9 additions & 21 deletions lib/msf/core/exploit/remote/postgres.rb
Expand Up @@ -88,11 +88,6 @@ def verbose; datastore['VERBOSE']; end
# @return [:error] if some other error occurred
# @return [:connected] if everything went as planned
def postgres_login(opts={})
unless defined?(session).nil? || session.nil?
self.postgres_conn = session.client
return :connected
end

postgres_logout if self.postgres_conn
db = opts[:database] || datastore['DATABASE']
username = opts[:username] || datastore['USERNAME']
Expand Down Expand Up @@ -125,7 +120,7 @@ def postgres_login(opts={})
return :connection_refused
end
if self.postgres_conn
print_good "#{ip}:#{port} Postgres - Logged in to '#{db}' with '#{username}':'#{password}'" if verbose
print_good "#{self.postgres_conn.address}:#{self.postgres_conn.port} Postgres - Logged in to '#{db}' with '#{username}':'#{password}'" if verbose
return :connected
end
end
Expand All @@ -134,20 +129,15 @@ def postgres_login(opts={})
#
# @return [void]
def postgres_logout
ip = datastore['RHOST']
port = datastore['RPORT']
ip = self.postgres_conn.address
port = self.postgres_conn.port
verbose = datastore['VERBOSE']
# Don't log out if we are using a session.
if defined?(session) && session
print_status "#{ip}:#{port} Postgres - Skipping disconnecting from the session" if verbose
return
end

if self.postgres_conn
self.postgres_conn.close if(self.postgres_conn.kind_of?(Connection) && self.postgres_conn.instance_variable_get("@conn"))
self.postgres_conn = nil
print_status "#{ip}:#{port} Postgres - Disconnected" if verbose
end
print_status "#{ip}:#{port} Postgres - Disconnected" if verbose
end

# If not currently connected, attempt to connect. If an
Expand All @@ -158,17 +148,16 @@ def postgres_logout
# @param doprint [Boolean] Whether the result should be printed
# @return [Hash]
def postgres_query(sql=nil,doprint=false)
ip = datastore['RHOST']
port = datastore['RPORT']
unless self.postgres_conn
result = postgres_login
unless result == :connected
return { :conn_error => result }
return { conn_error: result }
end
end

if self.postgres_conn
sql ||= datastore['SQL']
vprint_status "#{ip}:#{port} Postgres - querying with '#{sql}'"
vprint_status "#{self.postgres_conn.address}:#{self.postgres_conn.port} Postgres - querying with '#{sql}'"
begin
resp = self.postgres_conn.query(sql)
rescue RuntimeError => e
Expand Down Expand Up @@ -202,12 +191,11 @@ def postgres_query(sql=nil,doprint=false)
# Otherwise, create a rowset using Rex::Text::Table (if there's
# more than 0 rows) and return :complete.
def postgres_print_reply(resp=nil,sql=nil)
ip = datastore['RHOST']
port = datastore['RPORT']
verbose = datastore['VERBOSE']
return :error unless resp.kind_of? Connection::Result

if resp.rows and resp.fields
print_status "#{ip}:#{port} Rows Returned: #{resp.rows.size}" if verbose
print_status "#{postgres_conn.address}:#{postgres_conn.port} Rows Returned: #{resp.rows.size}" if verbose
if resp.rows.size > 0
tbl = Rex::Text::Table.new(
'Indent' => 4,
Expand Down
4 changes: 4 additions & 0 deletions lib/msf/core/optional_session.rb
Expand Up @@ -29,6 +29,7 @@ def initialize(info = {})
Msf::Opt::RPORT(3306, false)
]
)
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
end

if framework.features.enabled?(Msf::FeatureManager::POSTGRESQL_SESSION_TYPE)
Expand All @@ -37,8 +38,11 @@ def initialize(info = {})
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
Msf::OptString.new('DATABASE', [ false, 'The database to authenticate against', 'postgres']),
Msf::OptString.new('USERNAME', [ false, 'The username to authenticate as', 'postgres']),
Msf::Opt::RHOST(nil, false),
Msf::Opt::RPORT(5432, false)
]
)
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
end

if framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE)
Expand Down
8 changes: 8 additions & 0 deletions lib/postgres/postgres-pr/connection.rb
Expand Up @@ -121,6 +121,14 @@ def initialize(database, user, password=nil, uri = nil)
end
end

def address
@conn.peerhost
end

def port
@conn.peerport
end

def close
raise "connection already closed" if @conn.nil?
@conn.shutdown
Expand Down
26 changes: 15 additions & 11 deletions modules/auxiliary/admin/postgres/postgres_readfile.rb
Expand Up @@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Postgres
include Msf::Auxiliary::Report
include Msf::OptionalSession

def initialize(info = {})
super(update_info(info,
Expand All @@ -17,13 +18,15 @@ def initialize(info = {})
as well as read privileges to the target file.
},
'Author' => [ 'todb' ],
'License' => MSF_LICENSE
'License' => MSF_LICENSE,
'SessionTypes' => %w[PostgreSQL]
))

register_options(
[
OptString.new('RFILE', [ true, 'The remote file', '/etc/passwd'])
])
]
)

deregister_options( 'SQL', 'RETURN_ROWSET' )
end
Expand All @@ -37,20 +40,21 @@ def rport
end

def run
self.postgres_conn = session.client if session
ret = postgres_read_textfile(datastore['RFILE'])
case ret.keys[0]
when :conn_error
print_error "#{rhost}:#{rport} Postgres - Authentication failure, could not connect."
when :sql_error
case ret[:sql_error]
when /^C58P01/
print_error "#{rhost}:#{rport} Postgres - No such file or directory."
vprint_status "#{rhost}:#{rport} Postgres - #{ret[:sql_error]}"
print_error "#{postgres_conn.address}:#{postgres_conn.port} Postgres - No such file or directory."
vprint_status "#{postgres_conn.address}:#{postgres_conn.port} Postgres - #{ret[:sql_error]}"
when /^C42501/
print_error "#{rhost}:#{rport} Postgres - Insufficient file permissions."
vprint_status "#{rhost}:#{rport} Postgres - #{ret[:sql_error]}"
print_error "#{postgres_conn.address}:#{postgres_conn.port} Postgres - Insufficient file permissions."
vprint_status "#{postgres_conn.address}:#{postgres_conn.port} Postgres - #{ret[:sql_error]}"
else
print_error "#{rhost}:#{rport} Postgres - #{ret[:sql_error]}"
print_error "#{postgres_conn.address}:#{postgres_conn.port} Postgres - #{ret[:sql_error]}"
end
when :complete
loot = ''
Expand All @@ -59,10 +63,10 @@ def run
loot << row.first
}
# No idea what the actual ctype will be, text/plain is just a guess
path = store_loot('postgres.file', 'text/plain', rhost, loot, datastore['RFILE'])
print_good("#{rhost}:#{rport} Postgres - #{datastore['RFILE']} saved in #{path}")
vprint_good "#{rhost}:#{rport} Postgres - Command complete."
path = store_loot('postgres.file', 'text/plain', postgres_conn.address, loot, datastore['RFILE'])
print_good("#{postgres_conn.address}:#{postgres_conn.port} Postgres - #{datastore['RFILE']} saved in #{path}")
vprint_good "#{postgres_conn.address}:#{postgres_conn.port} Postgres - Command complete."
end
postgres_logout if self.postgres_conn
postgres_logout if self.postgres_conn && session.blank?
end
end
13 changes: 7 additions & 6 deletions modules/auxiliary/admin/postgres/postgres_sql.rb
Expand Up @@ -5,6 +5,7 @@

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Postgres
include Msf::OptionalSession

def initialize(info = {})
super(update_info(info,
Expand All @@ -18,10 +19,9 @@ def initialize(info = {})
'References' =>
[
[ 'URL', 'www.postgresql.org' ]
]
],
'SessionTypes' => %w[PostgreSQL]
))

#register_options( [ ], self.class) # None needed.
end

def auxiliary_commands
Expand All @@ -42,15 +42,16 @@ def rport
end

def run
self.postgres_conn = session.client if session
ret = postgres_query(datastore['SQL'],datastore['RETURN_ROWSET'])
case ret.keys[0]
when :conn_error
print_error "#{rhost}:#{rport} Postgres - Authentication failure, could not connect."
when :sql_error
print_error "#{rhost}:#{rport} Postgres - #{ret[:sql_error]}"
print_error "#{postgres_conn.address}:#{postgres_conn.port} Postgres - #{ret[:sql_error]}"
when :complete
vprint_good "#{rhost}:#{rport} Postgres - Command complete."
vprint_good "#{postgres_conn.address}:#{postgres_conn.port} Postgres - Command complete."
end
postgres_logout if self.postgres_conn
postgres_logout if self.postgres_conn && session.blank?
end
end
41 changes: 27 additions & 14 deletions modules/auxiliary/scanner/postgres/postgres_hashdump.rb
Expand Up @@ -7,6 +7,7 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Postgres
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
include Msf::OptionalSession

def initialize
super(
Expand All @@ -16,23 +17,34 @@ def initialize
hashes from a Postgres server and stores them for later cracking.
},
'Author' => ['theLightCosine'],
'License' => MSF_LICENSE
'License' => MSF_LICENSE,
'SessionTypes' => %w[PostgreSQL]
)
register_options([
OptString.new('DATABASE', [ true, 'The database to authenticate against', 'postgres']),
])
deregister_options('SQL', 'RETURN_ROWSET', 'VERBOSE')

end

def run_host(ip)
def username
session ? session.client.params['username'] : datastore['USERNAME']
end

def database
session ? session.client.params['database'] : datastore['DATABASE']
end

def password
# The session or its client doesn't store the password
session ? nil : datastore['PASSWORD']
end

def run_host(ip)
self.postgres_conn = session.client if session
# Query the Postgres Shadow table for username and password hashes and report them
res = postgres_query('SELECT usename, passwd FROM pg_shadow',false)

service_data = {
address: ip,
port: rport,
address: postgres_conn.address,
port: postgres_conn.port,
service_name: 'postgres',
protocol: 'tcp',
workspace_id: myworkspace_id
Expand All @@ -41,11 +53,11 @@ def run_host(ip)
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_data: datastore['PASSWORD'],
private_data: password,
private_type: :password,
username: datastore['USERNAME'],
username: username,
realm_key: Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE,
realm_value: datastore['DATABASE']
realm_value: database
}

credential_data.merge!(service_data)
Expand All @@ -68,10 +80,10 @@ def run_host(ip)

case res[:sql_error]
when /^C42501/
print_error "#{datastore['RHOST']}:#{datastore['RPORT']} Postgres - Insufficient permissions."
print_error "#{postgres_conn.address}:#{postgres_conn.port} Postgres - Insufficient permissions."
return
else
print_error "#{datastore['RHOST']}:#{datastore['RPORT']} Postgres - #{res[:sql_error]}"
print_error "#{postgres_conn.address}:#{postgres_conn.port} Postgres - #{res[:sql_error]}"
return
end
when :complete
Expand All @@ -96,8 +108,8 @@ def run_host(ip)
)

service_data = {
address: ::Rex::Socket.getaddress(rhost,true),
port: rport,
address: postgres_conn.address,
port: postgres_conn.port,
service_name: 'postgres',
protocol: 'tcp',
workspace_id: myworkspace_id
Expand Down Expand Up @@ -133,6 +145,7 @@ def run_host(ip)
end
print_good("#{tbl.to_s}")

postgres_logout if self.postgres_conn && session.blank?
end

end
8 changes: 6 additions & 2 deletions modules/auxiliary/scanner/postgres/postgres_schemadump.rb
Expand Up @@ -28,7 +28,11 @@ def initialize
end

def run_host(_ip)
print_status 'When targeting a session, only the current database can be dumped.' if session
if session
print_status 'When targeting a session, only the current database can be dumped.'
self.postgres_conn = session.client
end

pg_schema = get_schema
pg_schema.each do |db|
report_note(
Expand Down Expand Up @@ -70,7 +74,7 @@ def get_schema
tmp_db = {}
tmp_db['DBName'] = database_name
tmp_db['Tables'] = []
postgres_login({ database: database_name })
postgres_login({ database: database_name }) unless session
tmp_tblnames = smart_query("SELECT c.relname, n.nspname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname NOT IN ('pg_catalog','pg_toast') AND pg_catalog.pg_table_is_visible(c.oid);")
if tmp_tblnames && !tmp_tblnames.empty?
tmp_tblnames.each do |tbl_row|
Expand Down

0 comments on commit 30fc29e

Please sign in to comment.