Skip to content

Commit

Permalink
Show session/rhost options separate from each other
Browse files Browse the repository at this point in the history
Add ability to group options for display

Add tests for grouped options and remove unnecessary ENV check for datastore fallbacks

Fix issue where newly registered options aren't included in the group

Add convenience methods for adding option groups

Add tests for option groups
  • Loading branch information
dwelch-r7 committed Feb 21, 2024
1 parent 06b3004 commit 1bf3df2
Show file tree
Hide file tree
Showing 13 changed files with 371 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
15 changes: 15 additions & 0 deletions lib/msf/core/module/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,19 @@ def register_options(options, owner = self.class)
self.options.add_options(options, owner)
import_defaults(false)
end

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

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 1bf3df2

Please sign in to comment.