Path: blob/master/spec/msf/ui/console/module_argument_parsing_spec.rb
19715 views
require 'rspec'12RHOST_EXAMPLES = [3'192.168.172.1',4'192.168.172.1/32',5'file:foo.txt',6'example',7'localhost',8'example.com',9'http://example.com',10'https://example.com:443',11'https://example.com:443/foo/bar?baz=qux&a=b',12'cidr:/30:http://multiple_ips.example.com/foo',13'http://[::ffff:7f00:1]:8000/',14'smb://example.com/',15'smb://[email protected]/',16'smb://user:[email protected]',17'smb://:@example.com',18'smb://domain;user:[email protected]/'19].freeze2021# Shared examples to ensure that all command parsing supports the same ways of22# supplying inline datastore values23RSpec.shared_examples_for 'a command which parses datastore values' do |opts|24context 'when the -o option flag is supplied' do25it 'shows the help menu when no value is supplied' do26expect(subject.send(opts[:method_name], ['-o'])).to be_nil27expect(subject).to have_received(opts[:expected_help_cmd])28end2930it 'allows setting one value' do31expected_result = {32datastore_options: {33'RHOSTS' => '192.168.172.1'34}35}36expect(subject.send(opts[:method_name], ['-o', 'RHOSTS=192.168.172.1'])).to include(expected_result)37end3839it 'allows setting namespaced datastore options' do40expected_result = {41datastore_options: {42'SMB::PROTOCOLVERSION' => '1,2'43}44}45expect(subject.send(opts[:method_name], ['SMB::ProtocolVersion=1,2'])).to include(expected_result)46end4748it 'allows setting datastore options with underscores' do49expected_result = {50datastore_options: {51'USER_FILE' => './example.txt'52}53}54expect(subject.send(opts[:method_name], ['user_file=./example.txt'])).to include(expected_result)55end5657it 'allows setting multiple options individually' do58expected_result = {59datastore_options: {60'RHOSTS' => '192.168.172.1 192.168.172.2',61'RPORT' => '1337'62}63}64expect(subject.send(opts[:method_name], ['-o', 'RHOSTS=192.168.172.1', '-o', 'RPORT=1337', '-o', 'rhosts=192.168.172.2'])).to include(expected_result)65end6667it 'allows setting action inline' do68expected_result = {69datastore_options: {70'RHOSTS' => '192.168.172.1',71'RPORT' => '1337',72}73}74expected_result[:action] = 'action-name' unless opts[:method_name] == 'parse_check_opts'75result = subject.send(opts[:method_name], ['RHOSTS=192.168.172.1', 'RPORT=1337', 'action=action-name'])76expect(result).to include(expected_result)77end7879it 'parses the option str directly into its components' do80expected_result = {81datastore_options: {82'RHOSTS' => '192.168.172.1',83'RPORT' => '1337'84}85}86expect(subject.send(opts[:method_name], ['-o', 'RHOSTS=192.168.172.1,RPORT=1337'])).to include(expected_result)87end8889it 'handles arguments containing spaces' do90args = ['-o', 'RHOSTS=http://user:this is a [email protected]']91expected_result = {92datastore_options: {93'RHOSTS' => '"http://user:this is a [email protected]"'94}95}96expect(subject.send(opts[:method_name], args)).to include(expected_result)97end9899RHOST_EXAMPLES.each do |value|100it "parses the option str correctly for rhost #{value.inspect}" do101expected_result = {102datastore_options: {103'RHOSTS' => value,104'RPORT' => '1337'105}106}107expect(subject.send(opts[:method_name], ['-o', "RHOSTS=#{value},RPORT=1337"])).to include(expected_result)108end109end110111it 'correctly handles combinations of inline options, arguments, and option str being provided' do112args = [113'-o', 'RHOSTS=192.168.172.1,RPORT=1337',114'192.168.172.2',115'LPORT=5555'116]117expected_result = {118datastore_options: {119'RHOSTS' => '192.168.172.1 192.168.172.2',120'RPORT' => '1337',121'LPORT' => '5555'122}123}124expect(subject.send(opts[:method_name], args)).to include(expected_result)125end126end127128context 'when arbitrary datastore key value pairs are provided' do129it 'allows setting one value' do130expected_result = {131datastore_options: {132'RHOSTS' => '192.168.172.1'133}134}135expect(subject.send(opts[:method_name], ['RHOSTS=192.168.172.1'])).to include(expected_result)136end137138it 'allows setting multiple options individually' do139expected_result = {140datastore_options: {141'RHOSTS' => '192.168.172.1',142'RPORT' => '1337'143}144}145expect(subject.send(opts[:method_name], ['RHOSTS=192.168.172.1', 'RPORT=1337'])).to include(expected_result)146end147148it 'correctly handles a missing value' do149expected_result = {150datastore_options: {151'RPORT' => ''152}153}154expect(subject.send(opts[:method_name], ['RPORT='])).to include(expected_result)155end156157it 'handles multiple values' do158args = ['RHOSTS=192.168.172.1', 'rhosts=192.168.172.2', 'rhost=smb://user:a b [email protected]']159expected_result = {160datastore_options: {161'RHOSTS' => '192.168.172.1 192.168.172.2 "smb://user:a b [email protected]"'162}163}164expect(subject.send(opts[:method_name], args)).to include(expected_result)165end166167it 'handles whitespaces' do168args = ['rhosts=http://user:this is a [email protected]', 'http://user:[email protected]']169expected_result = {170datastore_options: {171'RHOSTS' => '"http://user:this is a [email protected]" http://user:[email protected]'172}173}174expect(subject.send(opts[:method_name], args)).to include(expected_result)175end176end177178context 'when arguments that resemble an RHOST value are used' do179it 'handles arguments containing spaces' do180args = ['http://user:this is a [email protected]', 'http://user:[email protected]']181expected_result = {182datastore_options: {183'RHOSTS' => '"http://user:this is a [email protected]" http://user:[email protected]'184}185}186expect(subject.send(opts[:method_name], args)).to include(expected_result)187end188189RHOST_EXAMPLES.each do |value|190it "works with a single value of #{value}" do191expected_result = {192datastore_options: {193'RHOSTS' => value194}195}196expect(subject.send(opts[:method_name], [value])).to include(expected_result)197end198199it 'works with multiple values' do200expected_result = {201datastore_options: {202'RHOSTS' => "#{value} #{value} #{value}"203}204}205expect(subject.send(opts[:method_name], [value, value, value])).to include(expected_result)206end207208it 'works with arbitrary option values' do209expected_result = {210datastore_options: {211'RHOSTS' => "#{value} #{value}",212'RPORT' => '2000',213'LPORT' => '5555'214}215}216expect(subject.send(opts[:method_name], ['-o', "RHOSTS=#{value}", '-o', 'RPORT=2000', value, 'LPORT=5555'])).to include(expected_result)217end218end219end220end221222RSpec.shared_examples_for 'a command which shows help menus' do |opts|223it 'shows the help menu with the -h flag' do224expect(subject.send(opts[:method_name], ['-h'])).to be_nil225expect(subject).to have_received(opts[:expected_help_cmd])226end227228it 'shows the help menu with --help flag' do229expect(subject.send(opts[:method_name], ['--help'])).to be_nil230expect(subject).to have_received(opts[:expected_help_cmd])231end232233[234['--foo'],235['--foo', 'bar'],236].each do |args|237it "shows the help menu with unknown flags #{args.inspect}" do238expect(subject.send(opts[:method_name], args)).to be_nil239expect(subject).to have_received(opts[:expected_help_cmd])240end241end242end243244RSpec.describe Msf::Ui::Console::ModuleArgumentParsing do245include_context 'Msf::UIDriver'246247let(:framework) { nil }248let(:subject) do249described_class = self.described_class250dummy_class = Class.new do251include Msf::Ui::Console::ModuleCommandDispatcher252include described_class253254# Method not provided by the mixin, needs to be implemented by class that mixes in described_class255def cmd_run_help256# noop257end258259# Method not provided by the mixin, needs to be implemented by class that mixes in described_class260def cmd_exploit_help261# noop262end263end264instance = dummy_class.new(driver)265instance266end267268before do269allow(subject).to receive(:cmd_run_help)270allow(subject).to receive(:cmd_exploit_help)271allow(subject).to receive(:cmd_check_help)272end273274describe '#parse_check_opts' do275let(:current_mod) { instance_double Msf::Auxiliary, datastore: {} }276277before do278allow(subject).to receive(:mod).and_return(current_mod)279end280281it_behaves_like 'a command which parses datastore values',282method_name: 'parse_check_opts',283expected_help_cmd: 'cmd_check_help'284285it_behaves_like 'a command which shows help menus',286method_name: 'parse_check_opts',287expected_help_cmd: 'cmd_check_help'288end289290describe '#parse_run_opts' do291let(:current_mod) { instance_double Msf::Auxiliary, datastore: {} }292293before do294allow(subject).to receive(:mod).and_return(current_mod)295end296297it_behaves_like 'a command which parses datastore values',298method_name: 'parse_run_opts',299expected_help_cmd: 'cmd_run_help'300301it_behaves_like 'a command which shows help menus',302method_name: 'parse_run_opts',303expected_help_cmd: 'cmd_run_help'304305it 'handles an action being supplied' do306args = []307expected_result = {308jobify: false,309quiet: false,310action: 'action-name',311datastore_options: {}312}313expect(subject.parse_run_opts(args, action: 'action-name')).to eq(expected_result)314end315316it 'handles an action being specified from the original datastore value' do317current_mod.datastore['action'] = 'datastore-action-name'318args = []319expected_result = {320jobify: false,321quiet: false,322action: 'action-name',323datastore_options: {}324}325expect(subject.parse_run_opts(args, action: 'action-name')).to eq(expected_result)326end327328it 'handles an action being nil' do329args = []330expected_result = {331jobify: false,332quiet: false,333action: nil,334datastore_options: {}335}336expect(subject.parse_run_opts(args)).to eq(expected_result)337end338end339340describe '#parse_exploit_opts' do341let(:current_mod) { instance_double Msf::Exploit, datastore: {} }342343before do344allow(subject).to receive(:mod).and_return(current_mod)345end346347it_behaves_like 'a command which parses datastore values',348method_name: 'parse_exploit_opts',349expected_help_cmd: 'cmd_exploit_help'350351it_behaves_like 'a command which shows help menus',352method_name: 'parse_exploit_opts',353expected_help_cmd: 'cmd_exploit_help'354355it 'handles no arguments being supplied' do356args = []357expected_result = {358action: nil,359jobify: false,360quiet: false,361datastore_options: {}362}363expect(subject.parse_exploit_opts(args)).to eq(expected_result)364end365366it 'allows multiple exploit options to be set' do367args = [368# encoder369'-e', 'encoder_value',370# force371'-f',372# quiet373'-q',374# nop375'-n', 'nop_value',376# option str377'-o', 'RPORT=9001',378# payload379'-p', 'payload_value',380# target381'-t', '5',382# run in the background383'-z',384# inline option385'LPORT=5555',386# rhosts387'192.168.172.1',388'192.168.172.2',389'example.com'390]391expected_result = {392action: nil,393jobify: false,394quiet: true,395datastore_options: {396'RHOSTS' => '192.168.172.1 192.168.172.2 example.com',397'RPORT' => '9001',398'LPORT' => '5555'399},400encoder: 'encoder_value',401force: true,402nop: 'nop_value',403payload: 'payload_value',404target: 5,405background: true406}407expect(subject.parse_exploit_opts(args)).to eq(expected_result)408end409end410end411412413