Files
metasploit-gs/spec/module_validation_spec.rb
T
adfoster-r7 7545328be1 Linting
2026-03-02 15:02:56 +00:00

420 lines
16 KiB
Ruby

RSpec.describe ModuleValidation::Validator do
let(:mod_class) { Msf::Exploit }
let(:mod_options) do
{
framework: framework,
name: 'Testing bad chars',
author: [
Msf::Author.new('Foobar'),
Msf::Author.new('Jim'),
Msf::Author.new('Bob')
],
license: MSF_LICENSE,
references: [Msf::Module::SiteReference.new('URL', 'https://example.com')],
rank_to_s: Msf::RankingName[Msf::ExcellentRanking],
rank: Msf::ExcellentRanking,
notes: {
'Stability' => [Msf::CRASH_SAFE],
'SideEffects' => [Msf::ARTIFACTS_ON_DISK],
'Reliability' => [Msf::FIRST_ATTEMPT_FAIL],
'AKA' => %w[SMBGhost CoronaBlue]
},
stability: [Msf::CRASH_SAFE],
side_effects: [Msf::ARTIFACTS_ON_DISK],
reliability: [Msf::FIRST_ATTEMPT_FAIL],
file_path: 'modules/exploits/windows/smb/cve_2020_0796_smbghost.rb',
type: 'exploit',
platform: Msf::Module::PlatformList.new(Msf::Module::Platform::Windows),
arch: [Rex::Arch::ARCH_X86],
targets: [Msf::Module::Target.new('Windows 10 v1903-1909 x64', { 'Platform' => 'win', 'Arch' => ['x64'] })],
description: %q{
A vulnerability exists within the Microsoft Server Message Block 3.1.1 (SMBv3) protocol that can be leveraged to
execute code on a vulnerable server. This remove exploit implementation leverages this flaw to execute code
in the context of the kernel, finally yielding a session as NT AUTHORITY\SYSTEM in spoolsv.exe. Exploitation
can take a few minutes as the necessary data is gathered.
}
}
end
let(:framework) do
instance_double(Msf::Framework)
end
let(:mod) do
instance_double(mod_class, **mod_options)
end
subject { described_class.new(mod) }
describe '#errors' do
before(:each) do |example|
subject.validate unless example.metadata[:skip_before]
end
context 'when the module is valid' do
it 'has no errors' do
expect(subject.errors.full_messages).to be_empty
end
end
context 'when notes contains an invalid value' do
let(:mod_options) do
super().merge(notes: {
'Stability' => [Msf::CRASH_SAFE],
'SideEffects' => [Msf::ARTIFACTS_ON_DISK],
'Reliability' => [Msf::FIRST_ATTEMPT_FAIL],
'AKA' => %w[SMBGhost CoronaBlue],
'NOCVE' => 'Reason not given'
})
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Notes note value "NOCVE" must be an array, got "Reason not given"']
end
end
context 'when the stability rating contains an invalid value' do
let(:mod_options) do
super().merge(stability: ['CRASH_SAFE'], rank: Msf::GreatRanking, rank_to_s: 'great')
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Stability contains invalid values ["CRASH_SAFE"] - only ["crash-safe", "crash-service-restarts", "crash-service-down", "crash-os-restarts", "crash-os-down", "service-resource-loss", "os-resource-loss"] is allowed']
end
end
context 'when the stability rating contains an invalid values and an excellent ranking' do
let(:mod_options) do
super().merge(stability: [Msf::CRASH_SERVICE_RESTARTS])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Stability must have CRASH_SAFE value if module has an ExcellentRanking, instead found ["crash-service-restarts"]']
end
end
context 'when the side effects rating contains an invalid value' do
let(:mod_options) do
super().merge(side_effects: ['ARTIFACTS_ON_DISK'])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Side effects contains invalid values ["ARTIFACTS_ON_DISK"] - only ["artifacts-on-disk", "config-changes", "ioc-in-logs", "account-lockouts", "account-logout", "screen-effects", "audio-effects", "physical-effects"] is allowed']
end
end
context 'when the reliability rating contains an invalid value' do
let(:mod_options) do
super().merge(reliability: ['FIRST_ATTEMPT_FAIL'])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Reliability contains invalid values ["FIRST_ATTEMPT_FAIL"] - only ["first-attempt-fail", "repeatable-session", "unreliable-session", "event-dependent"] is allowed']
end
end
context 'when the references contains an invalid value' do
let(:mod_options) do
super().merge(references: [
Msf::Module::SiteReference.new('url', 'https://example.com'),
Msf::Module::SiteReference.new('FOO', 'https://example.com'),
Msf::Module::SiteReference.new('NOCVE', 'Reason not given'),
Msf::Module::SiteReference.new('AKA', 'Foobar'),
Msf::Module::SiteReference.new('ATTACK', 'Foobar'),
])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq [
"References url is not valid, must be in [\"ATT&CK\", \"CVE\", \"CWE\", \"BID\", \"MSB\", \"EDB\", \"GHSA\", \"OSV\", \"US-CERT-VU\", \"ZDI\", \"URL\", \"WPVDB\", \"PACKETSTORM\", \"LOGO\", \"SOUNDTRACK\", \"OSVDB\", \"VTS\", \"OVE\"]",
"References FOO is not valid, must be in [\"ATT&CK\", \"CVE\", \"CWE\", \"BID\", \"MSB\", \"EDB\", \"GHSA\", \"OSV\", \"US-CERT-VU\", \"ZDI\", \"URL\", \"WPVDB\", \"PACKETSTORM\", \"LOGO\", \"SOUNDTRACK\", \"OSVDB\", \"VTS\", \"OVE\"]",
"References NOCVE please include NOCVE values in the 'notes' section, rather than in 'references'",
"References AKA please include AKA values in the 'notes' section, rather than in 'references'",
"References ATTACK is not valid, must be in [\"ATT&CK\", \"CVE\", \"CWE\", \"BID\", \"MSB\", \"EDB\", \"GHSA\", \"OSV\", \"US-CERT-VU\", \"ZDI\", \"URL\", \"WPVDB\", \"PACKETSTORM\", \"LOGO\", \"SOUNDTRACK\", \"OSVDB\", \"VTS\", \"OVE\"]"
]
end
end
context 'when the license contains an invalid value' do
let(:mod_options) do
super().merge(license: 'MSF_LICENSE')
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['License must include a valid license']
end
end
context 'when the rank contains an invalid value' do
let(:mod_options) do
super().merge(rank: 'ExcellentRanking')
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Rank must include a valid module ranking']
end
end
context 'when the author is missing' do
let(:mod_options) do
super().merge(author: [])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ["Author can't be blank"]
end
end
context 'when the author contains bad characters' do
let(:mod_options) do
super().merge(author: [
Msf::Author.new('@Foobar'),
Msf::Author.new('Foobar')
])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Author must not include username handles, found "@Foobar". Try leaving it in a comment instead']
end
end
context 'when the module name contains bad characters' do
let(:mod_options) do
super().merge(name: 'Testing <> bad & chars')
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Name must not contain the characters &<>']
end
end
context 'when the name has non-printable ascii characters' do
let(:mod_options) do
super().merge(name: 'Testing human-readable printable ascii characters ≤')
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Name must only contain human-readable printable ascii characters']
end
end
context 'when the module file path is not snake case' do
let(:mod_options) do
super().merge(file_path: 'modules/exploits/windows/smb/CVE_2020_0796_smbghost.rb')
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['File path must be snake case, instead found "modules/exploits/windows/smb/CVE_2020_0796_smbghost.rb"']
end
end
context 'when the description is missing' do
let(:mod_options) do
super().merge(description: nil)
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ["Description can't be blank"]
end
end
context 'when the description has non-printable ascii characters' do
let(:mod_options) do
super().merge(description: "Testing human-readable printable ascii characters ≤\n\tand newlines/tabs")
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Description must only contain human-readable printable ascii characters, including newlines and tabs']
end
end
context 'when the platform value is invalid', skip_before: true do
let(:mod_options) do
super().merge(platform: Msf::Module::PlatformList.new('foo'))
end
it 'raises an ArgumentError' do
expect { subject }.to raise_error ArgumentError, 'No classes in Msf::Module::Platform for foo!'
end
end
context 'when the arch array contains a valid value' do
it 'has no errors' do
expect(subject.errors.full_messages).to be_empty
end
end
context 'when the arch array contains an invalid value' do
let(:mod_options) do
super().merge(arch: ["Rex::Arch::ARCH_X86"])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ["Arch contains invalid values [\"Rex::Arch::ARCH_X86\"] - only [\"x86\", \"x86_64\", \"x64\", \"mips\", \"mipsle\", \"mipsbe\", \"mips64\", \"mips64le\", \"ppc\", \"ppce500v2\", \"ppc64\", \"ppc64le\", \"cbea\", \"cbea64\", \"sparc\", \"sparc64\", \"armle\", \"armbe\", \"aarch64\", \"cmd\", \"php\", \"tty\", \"java\", \"ruby\", \"dalvik\", \"python\", \"nodejs\", \"firefox\", \"zarch\", \"r\", \"riscv32be\", \"riscv32le\", \"riscv64be\", \"riscv64le\", \"loongarch64\"] is allowed"]
end
end
context 'when the platform is missing and targets does not contain platform values' do
let(:mod_options) do
super().merge(platform: nil, targets: [Msf::Module::Target.new('Windows 10 v1903-1909 x64', { 'Arch' => ['x64'] })])
end
it 'has errors' do
expect(subject.errors.full_messages).to eq ['Platform must be included either within targets or platform module metadata']
end
end
context 'when the notes section contains sentinel values' do
let(:mod_options) do
new_module_options = {
notes: {
'Stability' => Msf::UNKNOWN_STABILITY,
'SideEffects' => Msf::UNKNOWN_SIDE_EFFECTS,
'Reliability' => Msf::UNKNOWN_RELIABILITY,
},
stability: Msf::UNKNOWN_STABILITY,
side_effects: Msf::UNKNOWN_SIDE_EFFECTS,
reliability: Msf::UNKNOWN_RELIABILITY,
}
super().merge(new_module_options)
end
it 'has no errors' do
expect(subject.errors.full_messages).to be_empty
end
end
context 'when the notes section contains in correct sentinel values' do
let(:mod_options) do
new_module_options = {
notes: {
'Stability' => [Msf::UNKNOWN_STABILITY],
'SideEffects' => [Msf::UNKNOWN_SIDE_EFFECTS],
'Reliability' => [Msf::UNKNOWN_RELIABILITY],
},
stability: [Msf::UNKNOWN_STABILITY],
side_effects: [Msf::UNKNOWN_SIDE_EFFECTS],
reliability: [Msf::UNKNOWN_RELIABILITY],
}
super().merge(new_module_options, rank: Msf::GreatRanking, rank_to_s: 'great')
end
it 'has errors' do
expect(subject.errors.full_messages).to eq [
"Stability contains invalid values [[\"unknown-stability\"]] - only [\"crash-safe\", \"crash-service-restarts\", \"crash-service-down\", \"crash-os-restarts\", \"crash-os-down\", \"service-resource-loss\", \"os-resource-loss\"] is allowed",
"Side effects contains invalid values [[\"unknown-side-effects\"]] - only [\"artifacts-on-disk\", \"config-changes\", \"ioc-in-logs\", \"account-lockouts\", \"account-logout\", \"screen-effects\", \"audio-effects\", \"physical-effects\"] is allowed",
"Reliability contains invalid values [[\"unknown-reliability\"]] - only [\"first-attempt-fail\", \"repeatable-session\", \"unreliable-session\", \"event-dependent\"] is allowed"
]
end
end
context 'when the references contains ATT&CK values' do
let(:mod_options) do
super().merge(references: [
Msf::Module::SiteReference.new('ATT&CK', 'T1059.001'),
Msf::Module::SiteReference.new('ATT&CK', 'BAD1059.001')
])
end
it 'has errors for invalid ATT&CK references' do
expect(subject.errors.full_messages).to eq ["References ATT&CK reference 'BAD1059.001' is invalid. Must start with one of [\"TA\", \"DS\", \"S\", \"M\", \"A\", \"G\", \"C\", \"T\"] and be followed by digits/periods, no whitespace."]
end
context 'with only valid ATT&CK references' do
let(:mod_options) do
super().merge(references: [Msf::Module::SiteReference.new('ATT&CK', 'T1059.001')])
end
it 'has no errors' do
expect(subject.errors.full_messages).to be_empty
end
end
end
context 'when the references contains URL values' do
let(:mod_options) do
super().merge(references: [
Msf::Module::SiteReference.new('URL', 'not a valid url'),
Msf::Module::SiteReference.new('URL', 'ftp://example.com/file.txt'),
Msf::Module::SiteReference.new('URL', 'ht tp://example.com'),
Msf::Module::SiteReference.new('URL', 'example.com/exploit/research')
])
end
it 'has errors for invalid URL references' do
expect(subject.errors.full_messages).to include(
"References URL reference 'not a valid url' is not a valid HTTP(s) URI with valid percent encoding",
"References URL reference 'ftp://example.com/file.txt' is not a valid HTTP(s) URI with valid percent encoding",
"References URL reference 'ht tp://example.com' is not a valid HTTP(s) URI with valid percent encoding",
"References URL reference 'example.com/exploit/research' is not a valid HTTP(s) URI with valid percent encoding"
)
end
context 'with only HTTP URL references' do
let(:mod_options) do
super().merge(references: [Msf::Module::SiteReference.new('URL', 'http://example.com/path')])
end
it 'has no errors' do
expect(subject.errors.full_messages).to be_empty
end
end
context 'with only valid HTTPS URL references' do
let(:mod_options) do
super().merge(references: [Msf::Module::SiteReference.new('URL', 'https://example.com/path')])
end
it 'has no errors' do
expect(subject.errors.full_messages).to be_empty
end
end
end
context 'when targets and default target are present' do
let(:mod_options) do
super().merge(
targets: [
[
'Automatic (Unix In-Memory)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' },
'Type' => :unix_memory
}
],
[
'Automatic (Linux Dropper)',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'CmdStagerFlavor' => ['echo', 'printf', 'wget', 'curl'],
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' },
'Type' => :linux_dropper
}
]
],
default_target: 1
)
end
it 'has no errors' do
expect(subject.errors.full_messages).to be_empty
end
context 'when the default_target is out of bounds' do
let(:mod_options) do
super().merge(
default_target: 4
)
end
it 'has errors for default_target' do
expect(subject.errors.full_messages).to eq ["Default target is out of range. Must specify a valid target index between 0 and 1, got '4'"]
end
end
end
end
end