Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use PostgreSQL session type for Postgres modules #18706

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
Comment on lines +41 to +42
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

]
)
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to circle back to renaming this to the lexicon similar to peer used elsewhere in metasploit, and other languages

Example ruby snippets:

>> s = TCPSocket.new('127.0.0.1', 5000)
=> #<TCPSocket:fd 18, AF_INET, 127.0.0.1, 52847>
>> s.peeraddr
=> ["AF_INET", 5000, "127.0.0.1", "127.0.0.1"]
>> s.local_address
=> #<Addrinfo: 127.0.0.1:52847 TCP>

Example Python snippets:

>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> result = s.connect(('127.0.0.1', 5000))
>>> s.getpeername()
('127.0.0.1', 5000)
>>> s.getsockname()
('127.0.0.1', 52883)

@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