235 lines
8.0 KiB
Ruby
235 lines
8.0 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
module Rex
|
|
module Post
|
|
module Sql
|
|
module Ui
|
|
module Console
|
|
module CommandDispatcher
|
|
|
|
###
|
|
#
|
|
# Core Generic SQL client commands
|
|
#
|
|
###
|
|
module Client
|
|
|
|
include Rex::Post::Sql::Ui::Console::CommandDispatcher
|
|
|
|
@@query_opts = Rex::Parser::Arguments.new(
|
|
['-h', '--help'] => [false, 'Help menu.'],
|
|
['-i', '--interact'] => [false, 'Enter an interactive prompt for running multiple SQL queries'],
|
|
)
|
|
|
|
#
|
|
# Initializes an instance of the core command set using the supplied console
|
|
# for interactivity.
|
|
#
|
|
# @param console The protocol-specific Rex Ui Console
|
|
def initialize(console)
|
|
super
|
|
|
|
@db_search_results = []
|
|
end
|
|
|
|
#
|
|
# List of supported commands.
|
|
#
|
|
def commands
|
|
cmds = {
|
|
'query' => 'Run a single SQL query',
|
|
'query_interactive' => 'Enter an interactive prompt for running multiple SQL queries',
|
|
}
|
|
|
|
reqs = {}
|
|
|
|
filter_commands(cmds, reqs)
|
|
end
|
|
|
|
# @return [String] The name of the client
|
|
def name
|
|
raise ::NotImplementedError
|
|
end
|
|
|
|
# @param [Array] args An array of arguments passed in to a command
|
|
# @return [TrueClass, FalseClass] True if the array contains '-h' or '--help', else false.
|
|
def help_args?(args)
|
|
return false unless args.instance_of?(::Array)
|
|
|
|
args.include?('-h') || args.include?('--help')
|
|
end
|
|
|
|
def cmd_query_interactive_help
|
|
print_line 'Usage: query_interactive'
|
|
print_line
|
|
print_line 'Go into an interactive SQL shell where SQL queries can be executed.'
|
|
print_line "To exit, type 'exit', 'quit', 'end' or 'stop'."
|
|
print_line
|
|
end
|
|
|
|
def query_interactive_help
|
|
print_line 'Interactive SQL prompt'
|
|
print_line
|
|
print_line 'You are in an interactive SQL shell where SQL queries can be executed.'
|
|
print_line 'SQL commands ending with ; will be executed on the remote server.'
|
|
print_line "To exit, type 'exit', 'quit', 'end' or 'stop'."
|
|
print_line
|
|
end
|
|
|
|
def cmd_query_interactive(*args)
|
|
if help_args?(args)
|
|
cmd_query_interactive_help
|
|
return
|
|
end
|
|
|
|
console = shell
|
|
# Pass in self so that we can call cmd_query in subsequent calls
|
|
console.interact_with_client(client_dispatcher: self)
|
|
end
|
|
|
|
def normalise_sql_result(result)
|
|
raise ::NotImplementedError
|
|
end
|
|
|
|
# Take in a normalised SQL result and print it.
|
|
# If there are any errors, print those instead.
|
|
# @param [Hash {Symbol => Array, Symbol => Array, Symbol => Array}] result A hash of 'rows', 'columns' and 'errors'
|
|
def format_result(result)
|
|
if result[:errors].any?
|
|
return "Query has failed. Reasons: #{result[:errors].join(', ')}"
|
|
end
|
|
|
|
number_column = ['#']
|
|
columns = [number_column, result[:columns]].flatten
|
|
rows = result[:rows].map.each_with_index do |row, i|
|
|
[i, row].flatten
|
|
end
|
|
|
|
::Rex::Text::Table.new(
|
|
'Header' => 'Response',
|
|
'Indent' => 4,
|
|
'Columns' => columns,
|
|
'Rows' => rows
|
|
)
|
|
end
|
|
|
|
def cmd_query_help
|
|
raise ::NotImplementedError
|
|
end
|
|
|
|
def run_query(query)
|
|
begin
|
|
result = client.query(query)
|
|
rescue => e
|
|
elog("Running query '#{query}' failed on session #{self.inspect}", error: e)
|
|
return { status: :error, result: { errors: [e] } }
|
|
end
|
|
|
|
if result.respond_to?(:cmd_tag) && result.cmd_tag
|
|
print_status result.cmd_tag
|
|
print_line
|
|
end
|
|
|
|
{ status: :success, result: result }
|
|
end
|
|
|
|
def cmd_query(*args)
|
|
show_help = false
|
|
call_interactive = false
|
|
|
|
@@query_opts.parse(args) do |opt, _idx, val|
|
|
case opt
|
|
when nil
|
|
show_help = true if val == 'help'
|
|
break
|
|
when '-h', '--help'
|
|
show_help = true
|
|
break
|
|
when '-i', '--interact'
|
|
call_interactive = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if args.empty? || show_help
|
|
cmd_query_help
|
|
return
|
|
end
|
|
|
|
if call_interactive
|
|
cmd_query_interactive
|
|
return
|
|
end
|
|
|
|
result = run_query(args.join(' '))
|
|
case result[:status]
|
|
when :success
|
|
# When changing a database in MySQL, we get a nil result back.
|
|
if result[:result].nil?
|
|
print_status 'Query executed successfully'
|
|
return
|
|
end
|
|
|
|
normalised_result = normalise_sql_result(result[:result])
|
|
|
|
# MSSQL returns :success, even if the query failed due to wrong syntax.
|
|
if normalised_result[:errors].any?
|
|
print_error "Query has failed. Reasons: #{normalised_result[:errors].join(', ')}"
|
|
return
|
|
end
|
|
|
|
# When changing a database in MSSQL, we get a result, but it doesn't contain colnames or rows.
|
|
if normalised_result[:rows].nil? || normalised_result[:columns].nil?
|
|
print_status 'Query executed successfully'
|
|
return
|
|
end
|
|
|
|
formatted_result = format_result(normalised_result)
|
|
print_line(formatted_result.to_s)
|
|
when :error
|
|
print_error "Query has failed. Reasons: #{result[:result][:errors].join(', ')}"
|
|
result[:result][:errors].each do |error|
|
|
handle_error(error)
|
|
end
|
|
else
|
|
elog "Unknown query status: #{result[:status]}"
|
|
print_error "Unknown query status: #{result[:status]}"
|
|
end
|
|
end
|
|
|
|
def process_query(query: '')
|
|
return '' if query.empty?
|
|
|
|
query.lines.each.map { |line| line.chomp.chomp('\\').strip }.reject(&:empty?).compact.join(' ')
|
|
end
|
|
|
|
# Handles special cased error for each protocol
|
|
# that are return when a session has died resulting in a session
|
|
# needing to be closed
|
|
#
|
|
# @param [Object] e
|
|
def handle_error(e)
|
|
case e
|
|
when EOFError
|
|
_close_session
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
# Sets session.alive to close sessions and handle setting session.client.interacting
|
|
# if currently in the context of the `query_interactive` command
|
|
#
|
|
# @return [FalseClass]
|
|
def _close_session
|
|
session.alive = false
|
|
session.client.interacting = false if session.client && session.client.respond_to?(:interacting)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|