Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve options display optional session types #18817

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
dwelch-r7 marked this conversation as resolved.
Show resolved Hide resolved

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

dwelch-r7 marked this conversation as resolved.
Show resolved Hide resolved
# 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:)
cgranleese-r7 marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker; This existing class extending Hash is odd. It's probably something we should remove in the future if the API surface area is small :+1;

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
dwelch-r7 marked this conversation as resolved.
Show resolved Hide resolved
# @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
Loading