Skip to content

Commit

Permalink
Initial PostgreSQL session type spike
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanusz-r7 committed Sep 19, 2023
1 parent 0fc88a8 commit 454015d
Show file tree
Hide file tree
Showing 8 changed files with 876 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/metasploit/framework/login_scanner/postgres.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,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
131 changes: 131 additions & 0 deletions lib/msf/base/sessions/postgresql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# -*- coding: binary -*-

require 'rex/post/postgresql'

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

# @return [Rex::Post::PostgreSQL::Ui::Console] The interactive console
attr_accessor :console
# PostgreSQL::Client maybe?
# @return [RubySMB::Client] The SMB client
# @return [PostgreSQL::Client]
attr_accessor :client
attr_accessor :platform
attr_accessor :arch

# TODO: Confirm existing terminology, smb_client vs client vs dispatcher vs something else
# @param [RubySMB::Client] client
def initialize(rstream, opts={})
@client = opts.fetch(:client)
# @info = "SMB TODO"
#self.console = Rex::Post::SMB::Ui::Console.new(self)
self.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(self.user_input, self.user_output)

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

def process_autoruns(datastore)
# TODO - Implemented for now to keep things happy
end

def type
self.class.type
end

#
# @return [String] The type of the session
#
def self.type
# 'SMB'
'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

protected

##
# :category: Msf::Session::Interactive implementors
#
# Initializes the console's I/O handles.
#
def init_ui(input, output)
self.user_input = input
self.user_output = output
console.init_ui(input, output)
console.set_log_source(log_source)

super
end

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

##
# :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)
Rex::Ui::Text::Shell::HistoryManager.with_context(name: self.type.to_sym) {
_interact_stream
}
end

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

console.framework = self.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 { self.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
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'
155 changes: 155 additions & 0 deletions lib/rex/post/postgresql/ui/console.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# -*- coding: binary -*-

module Rex
module Post
module PostgreSQL
module Ui

###
#
# This class provides a shell driven interface to the PostgreSQL client API.
#
###
class Console

include Rex::Ui::Text::DispatcherShell

# Dispatchers
require 'rex/post/postgresql/ui/console/command_dispatcher'
require 'rex/post/postgresql/ui/console/command_dispatcher/core'
#require 'rex/post/postgresql/ui/console/command_dispatcher/shares'

#
# Initialize the SMB console.
#
# @param [Msf::Sessions::SMB] session
def initialize(session)
# @type [::Msf::Config.meterpreter_history]
todo_hist_file = nil
super("%undPostgreSQL%clr", '>', todo_hist_file, nil, :postgresql)
#if Rex::Compat.is_windows
#super("smb")
# super('postgresql')
#else
# todo_hist_file = nil # Msf::Config.meterpreter_history
#super("%undSMB%clr", '>', todo_hist_file, nil, :smb)
# super("%undPostgreSQL%clr", '>', todo_hist_file, nil, :postgresql)
#end

# The postgresql client context
self.session = session
self.client = session.client

# Queued commands array
self.commands = []

# Point the input/output handles elsewhere
reset_ui

enstack_dispatcher(Rex::Post::SMB::Ui::Console::CommandDispatcher::Core)
#enstack_dispatcher(Rex::Post::SMB::Ui::Console::CommandDispatcher::Shares)

# Set up logging to whatever logsink 'core' is using
#if ! $dispatcher['smb']
if ! $dispatcher['postgresql']
#$dispatcher['smb'] = $dispatcher['core']
$dispatcher['postgresql'] = $dispatcher['core']
end
end

#
# Called when someone wants to interact with the smb client. It's
# Called when someone wants to interact with the postgresql client. It's
# assumed that init_ui has been called prior.
#
def interact(&block)
# Run queued commands
commands.delete_if do |ent|
run_single(ent)
true
end

# Run the interactive loop
run do |line|
# Run the command
run_single(line)

# If a block was supplied, call it, otherwise return false
if block
block.call
else
false
end
end
end

#
# Queues a command to be run when the interactive loop is entered.
#
def queue_cmd(cmd)
self.commands << cmd
end

#
# Runs the specified command wrapper in something to catch meterpreter
# exceptions.
#
def run_command(dispatcher, method, arguments)
begin
super
rescue ::Timeout::Error
log_error('Operation timed out.')
rescue ::Rex::InvalidDestination => e
log_error(e.message)
rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError
self.session.kill
rescue ::Exception => e
log_error("Error running command #{method}: #{e.class} #{e}")
elog(e)
end
end

#
# Logs that an error occurred and persists the callstack.
#
def log_error(msg)
print_error(msg)

elog(msg, 'postgresql')

dlog("Call stack:\n#{$@.join("\n")}", 'postgresql')
end

# @return [Msf::Sessions::PostgreSQL]
attr_reader :session

# @return [PostgreSQL::Client]
attr_reader :client # :nodoc:

# TODO Should this belong elsewhere - it's required for prompt details
# @return [String]
attr_accessor :cwd

def format_prompt(val)
## TODO: Verify how active_module impacts the prompt name, and follow the same pattern
#if active_share
# share_name = active_share.share[/[^\\].*$/, 0] # active_share.share[/[^\\]+$/, 0]
# cwd = self.cwd.blank? ? '' : "\\#{self.cwd}"
# return substitute_colors("%undSMB%clr (#{share_name}#{cwd}) > ", true)
#end

super
end

protected

attr_writer :session # :nodoc:
attr_writer :client # :nodoc:
attr_accessor :commands # :nodoc:

end

end
end
end
end
Loading

0 comments on commit 454015d

Please sign in to comment.