Skip to content

Commit

Permalink
Add PostgreSQL session type
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanusz-r7 committed Jan 4, 2024
1 parent 5e59389 commit 507078b
Show file tree
Hide file tree
Showing 14 changed files with 746 additions and 3 deletions.
3 changes: 2 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ PATH
rb-readline
recog
redcarpet
reline (~> 0.4.1)
rex-arch
rex-bin_tools
rex-core
Expand Down Expand Up @@ -377,7 +378,7 @@ GEM
nokogiri
redcarpet (3.6.0)
regexp_parser (2.8.1)
reline (0.3.8)
reline (0.4.1)
io-console (~> 0.5)
require_all (3.0.0)
rex-arch (0.1.15)
Expand Down
20 changes: 18 additions & 2 deletions lib/metasploit/framework/login_scanner/postgres.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ module LoginScanner
class Postgres
include Metasploit::Framework::LoginScanner::Base

# @returns [Boolean] If a login is successful and this attribute is true - a PostgreSQL::Client instance is used as proof,
# and the socket is not immediately closed
attr_accessor :use_client_as_proof

# @!attribute dispatcher
# @return [PostgreSQL::Dispatcher::Socket]
attr_accessor :dispatcher

DEFAULT_PORT = 5432
DEFAULT_REALM = 'template1'
LIKELY_PORTS = [ DEFAULT_PORT ]
Expand Down Expand Up @@ -42,7 +50,7 @@ def attempt_login(credential)

begin
pg_conn = Msf::Db::PostgresPR::Connection.new(db_name,credential.public,credential.private,uri)
rescue RuntimeError => e
rescue ::RuntimeError => e
case e.to_s.split("\t")[1]
when "C3D000"
result_options.merge!({
Expand Down Expand Up @@ -70,8 +78,16 @@ def attempt_login(credential)
end

if pg_conn
pg_conn.close
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL

# This module no long owns the socket, return it as proof so the calling context can perform additional operations
# Additionally assign values to nil to avoid closing the socket etc automatically
if use_client_as_proof
result_options[:proof] = pg_conn
pg_conn = nil
else
pg_conn.close
end
else
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end
Expand Down
11 changes: 11 additions & 0 deletions lib/msf/base/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ def self.smb_session_history
self.new.smb_session_history
end

# Returns the full path to the PostgreSQL session history file.
#
# @return [String] path to the history file.
def self.postgresql_session_history
self.new.postgresql_session_history
end

def self.pry_history
self.new.pry_history
end
Expand Down Expand Up @@ -330,6 +337,10 @@ def smb_session_history
config_directory + FileSep + "smb_session_history"
end

def postgresql_session_history
config_directory + FileSep + "postgresql_session_history"
end

def pry_history
config_directory + FileSep + "pry_history"
end
Expand Down
145 changes: 145 additions & 0 deletions lib/msf/base/sessions/postgresql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# -*- coding: binary -*-

require 'rex/post/postgresql'

class Msf::Sessions::PostgreSQL
#
# This interface supports basic interaction.
#
include Msf::Session::Basic
include Msf::Sessions::Scriptable

# @return [Rex::Post::PostgreSQL::Ui::Console] The interactive console
attr_accessor :console
# @return [PostgreSQL::Client]
attr_accessor :client
attr_accessor :platform, :arch

# @param[Rex::IO::Stream] rstream
# @param [Hash] opts
# @param opts [PostgreSQL::Client] :client
def initialize(rstream, opts = {})
@client = opts.fetch(:client)
@console = ::Rex::Post::PostgreSQL::Ui::Console.new(self)
super(rstream, opts)
end

def bootstrap(datastore = {}, handler = nil)
# this won't work after the rstream is initialized, so do it first
# @platform = 'windows' # Metasploit::Framework::Ssh::Platform.get_platform(ssh_connection)
# super

session = self
session.init_ui(user_input, user_output)

@info = "PostgreSQL #{datastore['USERNAME']} @ #{@peer_info}"
end

def process_autoruns(datastore)
['InitialAutoRunScript', 'AutoRunScript'].each do |key|
next if datastore[key].nil? || datastore[key].empty?

args = Shellwords.shellwords(datastore[key])
print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")
session.execute_script(args.shift, *args)
end
end

def type
self.class.type
end

#
# @return [String] The type of the session
#
def self.type
'PostgreSQL'
end

#
# @return [Boolean] Can the session clean up after itself
def self.can_cleanup_files
false
end

#
# @return [String] The session description
#
def desc
'PostgreSQL'
end

def address
return @address if @address

@address, @port = @client.conn.peerinfo.split(':')
@address
end

def port
return @port if @port

@address, @port = @client.conn.peerinfo.split(':')
@port
end

##
# :category: Msf::Session::Interactive implementors
#
# Initializes the console's I/O handles.
#
def init_ui(input, output)
super(input, output)

console.init_ui(input, output)
console.set_log_source(self.log_source)
end

##
# :category: Msf::Session::Interactive implementors
#
# Resets the console's I/O handles.
#
def reset_ui
console.unset_log_source
console.reset_ui
end

def exit
console.stop
end

protected

##
# :category: Msf::Session::Interactive implementors
#
# Override the basic session interaction to use shell_read and
# shell_write instead of operating on rstream directly.
def _interact
framework.events.on_session_interact(self)
framework.history_manager.with_context(name: type.to_sym) { _interact_stream }
end

##
# :category: Msf::Session::Interactive implementors
#
def _interact_stream
framework.events.on_session_interact(self)

console.framework = framework
# if framework.datastore['MeterpreterPrompt']
# console.update_prompt(framework.datastore['MeterpreterPrompt'])
# end
# Call the console interaction of the smb client and
# pass it a block that returns whether or not we should still be
# interacting. This will allow the shell to abort if interaction is
# canceled.
console.interact { interacting != true }
console.framework = nil

# If the stop flag has been set, then that means the user exited. Raise
# the EOFError so we can drop this handle like a bad habit.
raise ::EOFError if (console.stopped? == true)
end
end
7 changes: 7 additions & 0 deletions lib/msf/core/feature_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class FeatureManager
DNS_FEATURE = 'dns_feature'
HIERARCHICAL_SEARCH_TABLE = 'hierarchical_search_table'
SMB_SESSION_TYPE = 'smb_session_type'
POSTGRESQL_SESSION_TYPE = 'postgresql_session_type'
DEFAULTS = [
{
name: WRAPPED_TABLES,
Expand Down Expand Up @@ -69,6 +70,12 @@ class FeatureManager
requires_restart: true,
default_value: false
}.freeze,
{
name: POSTGRESQL_SESSION_TYPE,
description: 'When enabled will allow for the creation/use of PostgreSQL sessions',
requires_restart: true,
default_value: false
}.freeze,
{
name: DNS_FEATURE,
description: 'When enabled, allows configuration of DNS resolution behaviour in Metasploit',
Expand Down
1 change: 1 addition & 0 deletions lib/msf_autoload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def custom_inflections
'cli' => 'CLI',
'sqlitei' => 'SQLitei',
'mysqli' => 'MySQLi',
'postgresql' => 'PostgreSQL',
'postgresqli' => 'PostgreSQLi',
'ssh' => 'SSH',
'winrm' => 'WinRM',
Expand Down
3 changes: 3 additions & 0 deletions lib/rex/post/postgresql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: binary -*-

require 'rex/post/postgresql/ui'
3 changes: 3 additions & 0 deletions lib/rex/post/postgresql/ui.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: binary -*-

require 'rex/post/postgresql/ui/console'
Loading

0 comments on commit 507078b

Please sign in to comment.