478 lines
19 KiB
Ruby
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
|