Skip to content

Commit

Permalink
Land #18817, Improve options display optional session types
Browse files Browse the repository at this point in the history
  • Loading branch information
cgranleese-r7 authored Feb 21, 2024
2 parents f706671 + bf1608a commit 7b618d4
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 7 deletions.
13 changes: 12 additions & 1 deletion lib/msf/base/serializer/readable_text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,13 @@ def self.dump_generic_module(mod, indent = '')
def self.dump_options(mod, indent = '', missing = false, advanced: false, evasion: false)
filtered_options = mod.options.values.select { |opt| opt.advanced? == advanced && opt.evasion? == evasion }

options_grouped_by_conditions = filtered_options.group_by(&:conditions)
option_groups = mod.options.groups.map { |_name, group| group }.sort_by(&:name)
options_by_group = option_groups.map do |group|
[group, group.option_names.map { |name| mod.options[name] }.compact]
end.to_h
grouped_option_names = option_groups.flat_map(&:option_names)
remaining_options = filtered_options.reject { |option| grouped_option_names.include?(option.name) }
options_grouped_by_conditions = remaining_options.group_by(&:conditions)

option_tables = []

Expand All @@ -587,6 +593,11 @@ def self.dump_options(mod, indent = '', missing = false, advanced: false, evasio
end
end

options_by_group.each do |group, options|
tbl = options_table(missing, mod, options, indent)
option_tables << "#{indent}#{group.description}:\n\n#{tbl}"
end

result = option_tables.join("\n\n")
result
end
Expand Down
24 changes: 24 additions & 0 deletions lib/msf/core/module/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,28 @@ def register_options(options, owner = self.class)
self.options.add_options(options, owner)
import_defaults(false)
end

# Registers a new option group, merging options by default
#
# @param name [String] Name for the group
# @param description [String] Description of the group
# @param option_names [Array<String>] List of datastore option names
# @param merge [Boolean] whether to merge or overwrite the groups option names
def register_option_group(name:, description:, option_names: [], merge: true)
existing_group = options.groups[name]
if merge && existing_group
existing_group.description = description
existing_group.add_options(option_names)
else
option_group = Msf::OptionGroup.new(name: name, description: description, option_names: option_names)
options.add_group(option_group)
end
end

# De-registers an option group by name
#
# @param name [String] Name for the group
def deregister_option_group(name:)
options.remove_group(name)
end
end
9 changes: 6 additions & 3 deletions lib/msf/core/opt_condition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
module Msf
module OptCondition
# Check a condition's result
# @param [Msf::Module] mod The module module
# @param [Msf::OptBase] opt the option which has conditions present
# @return [String]
# @param [String] left_value The left hand side of the condition
# @param [String] operator The conditions comparison operator
# @param [String] right_value The right hand side of the condition
# @return [Boolean]
def self.eval_condition(left_value, operator, right_value)
case operator.to_sym
when :==
Expand All @@ -16,6 +17,8 @@ def self.eval_condition(left_value, operator, right_value)
right_value.include?(left_value)
when :nin
!right_value.include?(left_value)
else
raise ArgumentError("Operator: #{operator} is invalid")
end
end

Expand Down
20 changes: 20 additions & 0 deletions lib/msf/core/option_container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class OptionContainer < Hash
#
def initialize(opts = {})
self.sorted = []
self.groups = {}

add_options(opts)
end
Expand Down Expand Up @@ -313,14 +314,33 @@ def merge_sort(other_container)
result.sort
end

# Adds an option group to the container
#
# @param option_group [Msf::OptionGroup]
def add_group(option_group)
groups[option_group.name] = option_group
end

# Removes an option group from the container by name
#
# @param group_name [String]
def remove_group(group_name)
groups.delete(group_name)
end

#
# The sorted array of options.
#
attr_reader :sorted

# @return [Hash<String, Msf::OptionGroup>]
attr_reader :groups

protected

attr_writer :sorted # :nodoc:

attr_writer :groups
end

end
32 changes: 32 additions & 0 deletions lib/msf/core/option_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: binary -*-

module Msf
class OptionGroup

# @return [String] Name for the group
attr_accessor :name
# @return [String] Description to be displayed to the user
attr_accessor :description
# @return [Array<String>] List of datastore option names
attr_accessor :option_names

# @param name [String] Name for the group
# @param description [String] Description to be displayed to the user
# @param option_names [Array<String>] List of datastore option names
def initialize(name:, description:, option_names: [])
self.name = name
self.description = description
self.option_names = option_names
end

# @param option_name [String] Name of the datastore option to be added to the group
def add_option(option_name)
@option_names << option_name
end

# @param option_names [Array<String>] List of datastore option names to be added to the group
def add_options(option_names)
@option_names.concat(option_names)
end
end
end
9 changes: 9 additions & 0 deletions lib/msf/core/optional_session/mssql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module OptionalSession
module MSSQL
include Msf::OptionalSession

RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT DATABASE USERNAME PASSWORD THREADS]

def initialize(info = {})
super(
update_info(
Expand All @@ -14,6 +16,12 @@ def initialize(info = {})
)

if framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE)
register_option_group(name: 'SESSION',
description: 'Used when connecting via an existing SESSION',
option_names: ['SESSION'])
register_option_group(name: 'RHOST',
description: 'Used when making a new connection via RHOSTS',
option_names: RHOST_GROUP_OPTIONS)
register_options(
[
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
Expand All @@ -23,6 +31,7 @@ def initialize(info = {})
Msf::Opt::RPORT(1433, false)
]
)

add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
end
end
Expand Down
10 changes: 9 additions & 1 deletion lib/msf/core/optional_session/mysql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module OptionalSession
module MySQL
include Msf::OptionalSession

RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT DATABASE USERNAME PASSWORD THREADS]

def initialize(info = {})
super(
update_info(
Expand All @@ -14,15 +16,21 @@ def initialize(info = {})
)

if framework.features.enabled?(Msf::FeatureManager::MYSQL_SESSION_TYPE)
register_option_group(name: 'SESSION',
description: 'Used when connecting via an existing SESSION',
option_names: ['SESSION'])
register_option_group(name: 'RHOST',
description: 'Used when making a new connection via RHOSTS',
option_names: RHOST_GROUP_OPTIONS)
register_options(
[
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
Msf::Opt::RHOST(nil, false),
Msf::Opt::RPORT(3306, false)
]
)
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')

add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
end
end

Expand Down
10 changes: 10 additions & 0 deletions lib/msf/core/optional_session/postgresql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ module OptionalSession
module PostgreSQL
include Msf::OptionalSession

RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT DATABASE USERNAME PASSWORD THREADS]

def initialize(info = {})
super(
update_info(
info,
'SessionTypes' => %w[postgresql]
)
)

if framework.features.enabled?(Msf::FeatureManager::POSTGRESQL_SESSION_TYPE)
register_option_group(name: 'SESSION',
description: 'Used when connecting via an existing SESSION',
option_names: ['SESSION'])
register_option_group(name: 'RHOST',
description: 'Used when making a new connection via RHOSTS',
option_names: RHOST_GROUP_OPTIONS)
register_options(
[
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
Expand All @@ -22,6 +31,7 @@ def initialize(info = {})
Msf::Opt::RPORT(5432, false)
]
)

add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
end
end
Expand Down
12 changes: 10 additions & 2 deletions lib/msf/core/optional_session/smb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module OptionalSession
module SMB
include Msf::OptionalSession

RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT SMBDomain SMBUser SMBPass THREADS]

def initialize(info = {})
super(
update_info(
Expand All @@ -13,15 +15,21 @@ def initialize(info = {})
)
)


if framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE)
register_option_group(name: 'SESSION',
description: 'Used when connecting via an existing SESSION',
option_names: ['SESSION'])
register_option_group(name: 'RHOST',
description: 'Used when making a new connection via RHOSTS',
option_names: RHOST_GROUP_OPTIONS)
register_options(
[
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
Msf::Opt::RHOST(nil, false),
Msf::Opt::RPORT(443, false)
Msf::Opt::RPORT(445, false)
]
)

add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
end
end
Expand Down
83 changes: 83 additions & 0 deletions spec/lib/msf/base/serializer/readable_text_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,89 @@ def initialize
TABLE
end
end

context 'when some options are grouped' do
let(:group_name) { 'group_name' }
let(:group_description) { 'Used for example reasons' }
let(:option_names) { %w[RHOSTS SMBUser SMBDomain] }
let(:group) { Msf::OptionGroup.new(name: group_name, description: group_description, option_names: option_names) }
let(:aux_mod_with_grouped_options) do
mod = aux_mod_with_set_options.replicant
mod.options.add_group(group)
mod
end

it 'should return the grouped options separate to the rest of the options' do
expect(described_class.dump_options(aux_mod_with_grouped_options, indent_string, false)).to match_table <<~TABLE
Name Current Setting Required Description
---- --------------- -------- -----------
FloatValue 5 no A FloatValue
NewOptionName yes An option with a new name. Aliases ensure the old and new names are synchronized
OptionWithModuleDefault false yes option with module default
RPORT 3000 yes The target port
baz baz_from_module yes baz option
fizz new_fizz yes fizz option
foo foo_from_framework yes Foo option
#{group_description}:
Name Current Setting Required Description
---- --------------- -------- -----------
RHOSTS 192.0.2.2 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
SMBDomain WORKGROUP yes The SMB username
SMBUser username yes The SMB username
TABLE
end
end

context 'when there are multiple options groups' do
let(:group_name_1) { 'group_name_1' }
let(:group_description_1) { 'Used for example reasons_1' }
let(:option_names_1) {['RHOSTS']}
let(:group_name_2) { 'group_name_2' }
let(:group_description_2) { 'Used for example reasons_2' }
let(:option_names_2) { %w[SMBUser SMBDomain] }

let(:group_1) { Msf::OptionGroup.new(name: group_name_1, description: group_description_1, option_names: option_names_1) }
let(:group_2) { Msf::OptionGroup.new(name: group_name_2, description: group_description_2, option_names: option_names_2) }

let(:aux_mod_with_grouped_options) do
mod = aux_mod_with_set_options.replicant
mod.options.add_group(group_1)
mod.options.add_group(group_2)
mod
end

it 'should return the grouped options separate to the rest of the options' do
expect(described_class.dump_options(aux_mod_with_grouped_options, indent_string, false)).to match_table <<~TABLE
Name Current Setting Required Description
---- --------------- -------- -----------
FloatValue 5 no A FloatValue
NewOptionName yes An option with a new name. Aliases ensure the old and new names are synchronized
OptionWithModuleDefault false yes option with module default
RPORT 3000 yes The target port
baz baz_from_module yes baz option
fizz new_fizz yes fizz option
foo foo_from_framework yes Foo option
#{group_description_1}:
Name Current Setting Required Description
---- --------------- -------- -----------
RHOSTS 192.0.2.2 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
#{group_description_2}:
Name Current Setting Required Description
---- --------------- -------- -----------
SMBDomain WORKGROUP yes The SMB username
SMBUser username yes The SMB username
TABLE
end
end
end

describe '.dump_advanced_options' do
Expand Down
Loading

0 comments on commit 7b618d4

Please sign in to comment.