metasploit-framework/spec/lib/msf/base/serializer/readable_text_spec.rb

478 lines
19 KiB
Ruby

# -*- coding:binary -*-
require 'spec_helper'
require 'rex/text'
RSpec.describe Msf::Serializer::ReadableText do
# The described_class API takes a mix of strings and whitespace character counts
let(:indent_string) { '' }
let(:indent_length) { indent_string.length }
let(:default_module_options) do
[
Msf::Opt::RHOSTS,
Msf::Opt::RPORT(3000),
Msf::OptString.new(
'foo',
[true, 'Foo option', 'bar']
),
Msf::OptString.new(
'fizz',
[true, 'fizz option', 'buzz']
),
Msf::OptString.new(
'baz',
[true, 'baz option', 'qux']
),
Msf::OptString.new(
'OptionWithModuleDefault',
[true, 'option with module default', true]
),
Msf::OptFloat.new('FloatValue', [false, 'A FloatValue ', 3.5]),
Msf::OptString.new(
'NewOptionName',
[true, 'An option with a new name. Aliases ensure the old and new names are synchronized', 'default_value'],
aliases: ['OLD_OPTION_NAME']
),
Msf::OptString.new(
'SMBUser',
[true, 'The SMB username'],
fallbacks: ['username']
),
Msf::OptString.new(
'SMBDomain',
[true, 'The SMB username', 'WORKGROUP'],
aliases: ['WindowsDomain'],
fallbacks: ['domain']
)
]
end
let(:default_advanced_module_options) do
[
Msf::OptEnum.new('DigestAlgorithm', [ true, 'The digest algorithm to use', 'SHA256', %w[SHA1 SHA256] ])
]
end
let(:default_evasion_module_options) do
[
Msf::OptInt.new('EVASION_TEST_OPTION', [ true, 'The evasion test option'])
]
end
let(:module_options) { default_module_options }
let(:advanced_module_options) { default_advanced_module_options }
let(:evasion_module_options) { default_evasion_module_options }
# (see Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options#kerberos_auth_options)
def kerberos_auth_options(protocol:, auth_methods:)
mixin = Class.new.extend(Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options)
mixin.kerberos_auth_options(protocol: protocol, auth_methods: auth_methods)
end
let(:aux_mod) do
mod_klass = Class.new(Msf::Auxiliary) do
def initialize
super(
'Name' => 'mock module',
'Description' => 'mock module',
'Author' => ['Unknown'],
'License' => MSF_LICENSE,
'DefaultOptions' => {
'OptionWithModuleDefault' => false,
'foo' => 'foo_from_module',
'baz' => 'baz_from_module'
},
)
end
end
mod = mod_klass.new
mod.send(:register_options, module_options)
mod.send(:register_advanced_options, advanced_module_options)
mod.send(:register_evasion_options, evasion_module_options)
mock_framework = instance_double(::Msf::Framework, datastore: Msf::DataStore.new)
allow(mod).to receive(:framework).and_return(mock_framework)
mod
end
let(:aux_mod_with_set_options) do
mod = aux_mod.replicant
mod.framework.datastore['RHOSTS'] = '192.0.2.2'
mod.framework.datastore['FloatValue'] = 5
mod.framework.datastore['foo'] = 'foo_from_framework'
mod.datastore['foo'] = 'new_value'
mod.datastore.unset('foo')
mod.datastore['OLD_OPTION_NAME'] = nil
mod.datastore['username'] = 'username'
mod.datastore['fizz'] = 'new_fizz'
mod
end
before(:each) do
allow(Rex::Text::Table).to receive(:wrapped_tables?).and_return(true)
end
describe '.dump_datastore' do
context 'when the datastore is empty' do
it 'returns the datastore as a table' do
expect(described_class.dump_datastore('Table name', Msf::DataStore.new, indent_length)).to match_table <<~TABLE
Table name
==========
No entries in data store.
TABLE
end
end
context 'when the datastore has values' do
it 'returns the datastore as a table' do
expect(described_class.dump_datastore('Table name', aux_mod_with_set_options.datastore, indent_length)).to match_table <<~TABLE
Table name
==========
Name Value
---- -----
DigestAlgorithm SHA256
EVASION_TEST_OPTION
FloatValue 5
NewOptionName
OptionWithModuleDefault false
RHOSTS 192.0.2.2
RPORT 3000
SMBDomain WORKGROUP
SMBUser username
VERBOSE false
WORKSPACE
baz baz_from_module
fizz new_fizz
foo foo_from_framework
username username
TABLE
end
end
end
describe '.dump_options' do
context 'when missing is false' do
it 'returns the options as a table' do
expect(described_class.dump_options(aux_mod_with_set_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
RHOSTS 192.0.2.2 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 3000 yes The target port
SMBDomain WORKGROUP yes The SMB username
SMBUser username yes The SMB username
baz baz_from_module yes baz option
fizz new_fizz yes fizz option
foo foo_from_framework yes Foo option
TABLE
end
end
context 'when missing is true' do
it 'returns the options as a table' do
expect(described_class.dump_options(aux_mod_with_set_options, indent_string, true)).to match_table <<~TABLE
Name Current Setting Required Description
---- --------------- -------- -----------
NewOptionName yes An option with a new name. Aliases ensure the old and new names are synchronized
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
context 'when kerberos options are present' do
let(:advanced_module_options) do
[
*default_advanced_module_options,
*kerberos_auth_options(protocol: 'Winrm', auth_methods: Msf::Exploit::Remote::AuthOption::WINRM_OPTIONS),
]
end
it 'returns the options as a table' do
expect(described_class.dump_advanced_options(aux_mod_with_set_options, indent_string)).to match_table <<~TABLE
Name Current Setting Required Description
---- --------------- -------- -----------
DigestAlgorithm SHA256 yes The digest algorithm to use (Accepted: SHA1, SHA256)
VERBOSE false no Enable detailed status messages
WORKSPACE no Specify the workspace for this module
Winrm::Auth auto yes The Authentication mechanism to use (Accepted: auto, ntlm, kerberos, plaintext)
When Winrm::Auth is kerberos:
Name Current Setting Required Description
---- --------------- -------- -----------
DomainControllerRhost no The resolvable rhost for the Domain Controller
Winrm::Krb5Ccname no The ccache file to use for kerberos authentication
Winrm::KrbOfferedEncryptionTypes AES256,AES128,RC4-HMAC,DES-CBC-MD5,DES3-CBC-SHA1 yes Kerberos encryption types to offer
Winrm::Rhostname no The rhostname which is required for kerberos - the SPN
TABLE
end
end
end
describe '.dump_evasion_options' do
context 'when kerberos options are present' do
let(:evasion_module_options) do
[
*default_evasion_module_options,
*kerberos_auth_options(protocol: 'Winrm', auth_methods: Msf::Exploit::Remote::AuthOption::WINRM_OPTIONS),
]
end
it 'returns the options as a table' do
expect(described_class.dump_evasion_options(aux_mod_with_set_options, indent_string)).to match_table <<~TABLE
Name Current Setting Required Description
---- --------------- -------- -----------
EVASION_TEST_OPTION yes The evasion test option
Winrm::Auth auto yes The Authentication mechanism to use (Accepted: auto, ntlm, kerberos, plaintext)
When Winrm::Auth is kerberos:
Name Current Setting Required Description
---- --------------- -------- -----------
DomainControllerRhost no The resolvable rhost for the Domain Controller
Winrm::Krb5Ccname no The ccache file to use for kerberos authentication
Winrm::KrbOfferedEncryptionTypes AES256,AES128,RC4-HMAC,DES-CBC-MD5,DES3-CBC-SHA1 yes Kerberos encryption types to offer
Winrm::Rhostname no The rhostname which is required for kerberos - the SPN
TABLE
end
end
end
describe '.dump_description' do
context 'when the module description is nil' do
it 'dumps the module description' do
mod = instance_double(
Msf::Module,
description: nil
)
result = described_class.dump_description(mod, ' ')
expect(result).to match_table <<~TABLE
Description:
TABLE
end
end
context 'when the module description has no whitespace' do
it 'dumps the module description' do
mod = instance_double(
Msf::Module,
description: 'this is a module description'
)
result = described_class.dump_description(mod, ' ')
expect(result).to match_table <<~TABLE
Description:
this is a module description
TABLE
end
end
context 'when the module description is a single line' do
it 'dumps the module description' do
mod = instance_double(
Msf::Module,
description: %q{ This is a description; with module details etc. }
)
result = described_class.dump_description(mod, ' ')
expect(result).to match_table <<~TABLE
Description:
This is a description; with module details etc.
TABLE
end
end
context 'when the first line has less preceding whitespace than the subsequent lines' do
it 'dumps the module description' do
mod = instance_double(
Msf::Module,
description: 'Listen for a connection. First, the port will need to be knocked from
the IP defined in KHOST. This IP will work as an authentication method
(you can spoof it with tools like hping). After that you could get your
shellcode from any IP. The socket will appear as "closed," thus helping to
hide the shellcode',
)
result = described_class.dump_description(mod, ' ')
expect(result).to match_table <<~TABLE
Description:
Listen for a connection. First, the port will need to be knocked from
the IP defined in KHOST. This IP will work as an authentication method
(you can spoof it with tools like hping). After that you could get your
shellcode from any IP. The socket will appear as "closed," thus helping to
hide the shellcode
TABLE
end
end
context 'when the first line has more whitespace than the subsequent lines' do
it 'dumps the module description' do
mod = instance_double(
Msf::Module,
description: %q{
Login credentials to the Motorola WR850G router with
firmware v4.03 can be obtained via a simple GET request
if issued while the administrator is logged in. A lot
more information is available through this request, but
you can get it all and more after logging in.
},
)
result = described_class.dump_description(mod, ' ')
expect(result).to match_table <<~TABLE
Description:
Login credentials to the Motorola WR850G router with
firmware v4.03 can be obtained via a simple GET request
if issued while the administrator is logged in. A lot
more information is available through this request, but
you can get it all and more after logging in.
TABLE
end
end
context 'when there are two blank lines in a row' do
it 'dumps the module description' do
mod = instance_double(
Msf::Module,
description: "Run a meterpreter server in Android.\n\nTunnel communication over HTTP"
)
result = described_class.dump_description(mod, ' ')
expect(result).to match_table <<~TABLE
Description:
Run a meterpreter server in Android.
Tunnel communication over HTTP
TABLE
end
end
context 'when the module description spans multiple lines' do
it 'dumps the module description' do
mod = instance_double(
Msf::Module,
description: %q{
This is a description; with module details etc.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer quis mattis lacus. Nam nisi diam, commodo id eu.
This is a list of important items to consider:
- Item A
- Item B
- Item C
}
)
result = described_class.dump_description(mod, ' ')
expect(result).to match_table <<~TABLE
Description:
This is a description; with module details etc.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer quis mattis lacus. Nam nisi diam, commodo id eu.
This is a list of important items to consider:
- Item A
- Item B
- Item C
TABLE
end
end
end
end