Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/plugins/wmap.rb
Views: 11704
#1# Web assessment for the Metasploit Framework2# Efrain Torres - et[ ] metasploit.com 20123#45require 'English'6require 'rabal/tree'78module Msf9class Plugin::Wmap < Msf::Plugin10class WmapCommandDispatcher1112# @!attribute wmapmodules13# @return [Array] Enabled WMAP modules14# @!attribute targets15# @return [Hash] WMAP targets16# @!attribute lastsites17# @return [Array] Temp location of previously obtained sites18# @!attribute rpcarr19# @return [Array] Array or rpc connections20# @!attribute njobs21# @return [Integer] Max number of jobs22# @!attribute nmaxdisplay23# @return [Boolean] Flag to stop displaying the same message24# @!attribute runlocal25# @return [Boolean] Flag to run local modules only26# @!attribute masstop27# @return [Boolean] Flag to stop everything28# @!attribute killwhenstop29# @return [Boolean] Kill process when exiting30attr_accessor :wmapmodules, :targets, :lastsites, :rpcarr, :njobs, :nmaxdisplay, :runlocal, :masstop, :killwhenstop3132include Msf::Ui::Console::CommandDispatcher3334def name35'wmap'36end3738#39# The initial command set40#41def commands42{43'wmap_targets' => 'Manage targets',44'wmap_sites' => 'Manage sites',45'wmap_nodes' => 'Manage nodes',46'wmap_run' => 'Test targets',47'wmap_modules' => 'Manage wmap modules',48'wmap_vulns' => 'Display web vulns'49}50end5152def cmd_wmap_vulns(*args)53args.push('-h') if args.empty?5455while (arg = args.shift)56case arg57when '-l'58view_vulns59when '-h'60print_status('Usage: wmap_vulns [options]')61print_line("\t-h Display this help text")62print_line("\t-l Display web vulns table")6364print_line('')65else66print_error('Unknown flag.')67end68return69end70end7172def cmd_wmap_modules(*args)73args.push('-h') if args.empty?7475while (arg = args.shift)76case arg77when '-l'78view_modules79when '-r'80load_wmap_modules(true)81when '-h'82print_status('Usage: wmap_modules [options]')83print_line("\t-h Display this help text")84print_line("\t-l List all wmap enabled modules")85print_line("\t-r Reload wmap modules")8687print_line('')88else89print_error('Unknown flag.')90end91return92end93end9495def cmd_wmap_targets(*args)96args.push('-h') if args.empty?9798while (arg = args.shift)99case arg100when '-c'101self.targets = Hash.new102when '-l'103view_targets104return105when '-t'106process_urls(args.shift)107when '-d'108process_ids(args.shift)109when '-h'110print_status('Usage: wmap_targets [options]')111print_line("\t-h Display this help text")112print_line("\t-t [urls] Define target sites (vhost1,url[space]vhost2,url) ")113print_line("\t-d [ids] Define target sites (id1, id2, id3 ...)")114print_line("\t-c Clean target sites list")115print_line("\t-l List all target sites")116117print_line('')118return119else120print_error('Unknown flag.')121return122end123end124end125126def cmd_wmap_sites(*args)127args.push('-h') if args.empty?128129while (arg = args.shift)130case arg131when '-a'132site = args.shift133if site134s = add_web_site(site)135if s136print_status('Site created.')137else138print_error('Unable to create site')139end140else141print_error('No site provided.')142end143when '-d'144del_idx = args145if !del_idx.empty?146delete_sites(del_idx.select { |d| d =~ /^[0-9]*$/ }.map(&:to_i).uniq)147return148else149print_error('No index provided.')150end151when '-l'152view_sites153return154when '-s'155u = args.shift156l = args.shift157o = args.shift158159return unless u160161if l.nil? || l.empty?162l = 200163o = 'true'164elsif (l == 'true') || (l == 'false')165# Add check if unicode parameters is the second one166o = l167l = 200168else169l = l.to_i170end171172o = (o == 'true')173174if u.include? 'http'175# Parameters are in url form176view_site_tree(u, l, o)177else178# Parameters are digits179if !lastsites || lastsites.empty?180view_sites181print_status('Web sites ids. referenced from previous table.')182end183184target_whitelist = []185ids = u.to_s.split(/,/)186187ids.each do |id|188next if id.to_s.strip.empty?189190if id.to_i > lastsites.length191print_error("Skipping id #{id}...")192else193target_whitelist << lastsites[id.to_i]194# print_status("Loading #{self.lastsites[id.to_i]}.")195end196end197198# Skip the DB entirely if no matches199return if target_whitelist.empty?200201unless targets202self.targets = Hash.new203end204205target_whitelist.each do |ent|206view_site_tree(ent, l, o)207end208end209return210when '-h'211print_status('Usage: wmap_sites [options]')212print_line("\t-h Display this help text")213print_line("\t-a [url] Add site (vhost,url)")214print_line("\t-d [ids] Delete sites (separate ids with space)")215print_line("\t-l List all available sites")216print_line("\t-s [id] Display site structure (vhost,url|ids) (level) (unicode output true/false)")217print_line('')218return219else220print_error('Unknown flag.')221return222end223end224end225226def cmd_wmap_nodes(*args)227if !rpcarr228self.rpcarr = Hash.new229end230231args.push('-h') if args.empty?232233while (arg = args.shift)234case arg235when '-a'236h = args.shift237r = args.shift238s = args.shift239u = args.shift240p = args.shift241242res = rpc_add_node(h, r, s, u, p, false)243if res244print_status('Node created.')245else246print_error('Unable to create node')247end248when '-c'249idref = args.shift250251if !idref252print_error('No id defined')253return254end255if idref.upcase == 'ALL'256print_status('All nodes removed')257self.rpcarr = Hash.new258else259idx = 0260rpcarr.each do |k, _v|261if idx == idref.to_i262rpcarr.delete(k)263print_status("Node deleted #{k}")264end265idx += 1266end267end268when '-d'269host = args.shift270port = args.shift271user = args.shift272pass = args.shift273dbname = args.shift274275res = rpc_db_nodes(host, port, user, pass, dbname)276if res277print_status('OK.')278else279print_error('Error')280end281when '-l'282rpc_list_nodes283return284when '-j'285rpc_view_jobs286return287when '-k'288node = args.shift289jid = args.shift290rpc_kill_node(node, jid)291return292when '-h'293print_status('Usage: wmap_nodes [options]')294print_line("\t-h Display this help text")295print_line("\t-c id Remove id node (Use ALL for ALL nodes")296print_line("\t-a host port ssl user pass Add node")297print_line("\t-d host port user pass db Force all nodes to connect to db")298print_line("\t-j View detailed jobs")299print_line("\t-k ALL|id ALL|job_id Kill jobs on node")300print_line("\t-l List all current nodes")301302print_line('')303return304else305print_error('Unknown flag.')306return307end308end309end310311def cmd_wmap_run(*args)312# Stop everything313self.masstop = false314self.killwhenstop = true315316trap('INT') do317print_error('Stopping execution...')318self.masstop = true319if killwhenstop320rpc_kill_node('ALL', 'ALL')321end322end323324# Max numbers of concurrent jobs per node325self.njobs = 25326self.nmaxdisplay = false327self.runlocal = false328329# Formatting330sizeline = 60331332wmap_show = 2**0333wmap_expl = 2**1334335# Exclude files can be modified by setting datastore['WMAP_EXCLUDE']336wmap_exclude_files = '.*\.(gif|jpg|png*)$'337338run_wmap_ssl = true339run_wmap_server = true340run_wmap_dir_file = true341run_wmap_query = true342run_wmap_unique_query = true343run_wmap_generic = true344345# If module supports datastore['VERBOSE']346moduleverbose = false347348showprogress = false349350if !rpcarr351self.rpcarr = Hash.new352end353354if !run_wmap_ssl355print_status('Loading of wmap ssl modules disabled.')356end357if !run_wmap_server358print_status('Loading of wmap server modules disabled.')359end360if !run_wmap_dir_file361print_status('Loading of wmap dir and file modules disabled.')362end363if !run_wmap_query364print_status('Loading of wmap query modules disabled.')365end366if !run_wmap_unique_query367print_status('Loading of wmap unique query modules disabled.')368end369if !run_wmap_generic370print_status('Loading of wmap generic modules disabled.')371end372373stamp = Time.now.to_f374mode = 0375376eprofile = []377using_p = false378using_m = false379usinginipath = false380381mname = ''382inipathname = '/'383384args.push('-h') if args.empty?385386while (arg = args.shift)387case arg388when '-t'389mode |= wmap_show390when '-e'391mode |= wmap_expl392393profile = args.shift394395if profile396print_status("Using profile #{profile}.")397398begin399File.open(profile).each do |str|400if !str.include? '#'401# Not a comment402modname = str.strip403if !modname.empty?404eprofile << modname405end406end407using_p = true408end409rescue StandardError410print_error('Profile not found or invalid.')411return412end413else414print_status('Using ALL wmap enabled modules.')415end416when '-m'417mode |= wmap_expl418419mname = args.shift420421if mname422print_status("Using module #{mname}.")423end424using_m = true425when '-p'426mode |= wmap_expl427428inipathname = args.shift429430if inipathname431print_status("Using initial path #{inipathname}.")432end433usinginipath = true434435when '-h'436print_status('Usage: wmap_run [options]')437print_line("\t-h Display this help text")438print_line("\t-t Show all enabled modules")439print_line("\t-m [regex] Launch only modules that name match provided regex.")440print_line("\t-p [regex] Only test path defined by regex.")441print_line("\t-e [/path/to/profile] Launch profile modules against all matched targets.")442print_line("\t (No profile file runs all enabled modules.)")443print_line('')444return445else446print_error('Unknown flag')447return448end449end450451if rpcarr.empty? && (mode & wmap_show == 0)452print_error('NO WMAP NODES DEFINED. Executing local modules')453self.runlocal = true454end455456if targets.nil?457print_error('Targets have not been selected.')458return459end460461if targets.keys.empty?462print_error('Targets have not been selected.')463return464end465466execmod = true467if (mode & wmap_show != 0)468execmod = false469end470471targets.each_with_index do |t, idx|472selected_host = t[1][:host]473selected_port = t[1][:port]474selected_ssl = t[1][:ssl]475selected_vhost = t[1][:vhost]476477print_status('Testing target:')478print_status("\tSite: #{selected_vhost} (#{selected_host})")479print_status("\tPort: #{selected_port} SSL: #{selected_ssl}")480print_line '=' * sizeline481print_status("Testing started. #{Time.now}")482483if !selected_ssl484run_wmap_ssl = false485# print_status ("Target is not SSL. SSL modules disabled.")486end487488# wmap_dir, wmap_file489matches = Hash.new490491# wmap_server492matches1 = Hash.new493494# wmap_query495matches2 = Hash.new496497# wmap_ssl498matches3 = Hash.new499500# wmap_unique_query501matches5 = Hash.new502503# wmap_generic504matches10 = Hash.new505506# OPTIONS507jobify = false508509# This will be clean later510load_wmap_modules(false)511512wmapmodules.each do |w|513case w[2]514when :wmap_server515if run_wmap_server516matches1[w] = true517end518when :wmap_query519if run_wmap_query520matches2[w] = true521end522when :wmap_unique_query523if run_wmap_unique_query524matches5[w] = true525end526when :wmap_generic527if run_wmap_generic528matches10[w] = true529end530when :wmap_dir, :wmap_file531if run_wmap_dir_file532matches[w] = true533end534when :wmap_ssl535if run_wmap_ssl536matches3[w] = true537end538else539# Black Hole540end541end542543# Execution order (orderid)544matches = sort_by_orderid(matches)545matches1 = sort_by_orderid(matches1)546matches2 = sort_by_orderid(matches2)547matches3 = sort_by_orderid(matches3)548matches5 = sort_by_orderid(matches5)549matches10 = sort_by_orderid(matches10)550551#552# Handle modules that need to be run before all tests IF SERVER is SSL, once usually again the SSL web server.553# :wmap_ssl554#555556print_status "\n=[ SSL testing ]="557print_line '=' * sizeline558559if !selected_ssl560print_status('Target is not SSL. SSL modules disabled.')561end562563idx = 0564matches3.each_key do |xref|565if masstop566print_error('STOPPED.')567return568end569570# Module not part of profile or not match571next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)572573idx += 1574575begin576# Module options hash577modopts = Hash.new578579#580# The code is just a proof-of-concept and will be expanded in the future581#582print_status "Module #{xref[0]}"583584if (mode & wmap_expl != 0)585586#587# For modules to have access to the global datastore588# i.e. set -g DOMAIN test.com589#590framework.datastore.each do |gkey, gval|591modopts[gkey] = gval592end593594#595# Parameters passed in hash xref596#597modopts['RHOST'] = selected_host598modopts['RHOSTS'] = selected_host599modopts['RPORT'] = selected_port.to_s600modopts['SSL'] = selected_ssl601modopts['VHOST'] = selected_vhost.to_s602modopts['VERBOSE'] = moduleverbose603modopts['ShowProgress'] = showprogress604modopts['RunAsJob'] = jobify605606begin607if execmod608rpc_round_exec(xref[0], xref[1], modopts, njobs)609end610rescue ::Exception611print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")612end613end614rescue ::Exception615print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")616end617end618619#620# Handle modules that need to be run before all tests, once usually again the web server.621# :wmap_server622#623print_status "\n=[ Web Server testing ]="624print_line '=' * sizeline625626idx = 0627matches1.each_key do |xref|628if masstop629print_error('STOPPED.')630return631end632633# Module not part of profile or not match634next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)635636idx += 1637638begin639# Module options hash640modopts = Hash.new641642#643# The code is just a proof-of-concept and will be expanded in the future644#645646print_status "Module #{xref[0]}"647648if (mode & wmap_expl != 0)649650#651# For modules to have access to the global datastore652# i.e. set -g DOMAIN test.com653#654framework.datastore.each do |gkey, gval|655modopts[gkey] = gval656end657658#659# Parameters passed in hash xref660#661modopts['RHOST'] = selected_host662modopts['RHOSTS'] = selected_host663modopts['RPORT'] = selected_port.to_s664modopts['SSL'] = selected_ssl665modopts['VHOST'] = selected_vhost.to_s666modopts['VERBOSE'] = moduleverbose667modopts['ShowProgress'] = showprogress668modopts['RunAsJob'] = jobify669670begin671if execmod672rpc_round_exec(xref[0], xref[1], modopts, njobs)673end674rescue ::Exception675print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")676end677end678rescue ::Exception679print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")680end681end682683#684# Handle modules to be run at every path/file685# wmap_dir, wmap_file686#687print_status "\n=[ File/Dir testing ]="688print_line '=' * sizeline689690idx = 0691matches.each_key do |xref|692if masstop693print_error('STOPPED.')694return695end696697# Module not part of profile or not match698next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)699700idx += 1701702begin703# Module options hash704modopts = Hash.new705706#707# The code is just a proof-of-concept and will be expanded in the future708#709710print_status "Module #{xref[0]}"711712if (mode & wmap_expl != 0)713#714# For modules to have access to the global datastore715# i.e. set -g DOMAIN test.com716#717framework.datastore.each do |gkey, gval|718modopts[gkey] = gval719end720721#722# Parameters passed in hash xref723#724modopts['RHOST'] = selected_host725modopts['RHOSTS'] = selected_host726modopts['RPORT'] = selected_port.to_s727modopts['SSL'] = selected_ssl728modopts['VHOST'] = selected_vhost.to_s729modopts['VERBOSE'] = moduleverbose730modopts['ShowProgress'] = showprogress731modopts['RunAsJob'] = jobify732733#734# Run the plugins that only need to be735# launched once.736#737738wtype = xref[2]739740h = framework.db.workspace.hosts.find_by_address(selected_host)741s = h.services.find_by_port(selected_port)742w = s.web_sites.find_by_vhost(selected_vhost)743744test_tree = load_tree(w)745test_tree.each do |node|746if masstop747print_error('STOPPED.')748return749end750751p = node.current_path752testpath = Pathname.new(p)753strpath = testpath.cleanpath(false).to_s754755#756# Fixing paths757#758759if node.is_leaf? && !node.is_root?760#761# Later we can add here more checks to see if its a file762#763elsif node.is_root?764strpath = '/'765else766strpath = strpath.chomp + '/'767end768769strpath = strpath.gsub('//', '/')770# print_status("Testing path: #{strpath}")771772#773# Launch plugin depending module type.774# Module type depends on main input type.775# Code may be the same but it depend on final776# versions of plugins777#778779case wtype780when :wmap_file781if node.is_leaf? && !node.is_root?782#783# Check if an exclusion regex has been defined784#785excludefilestr = framework.datastore['WMAP_EXCLUDE'] || wmap_exclude_files786787if !(strpath.match(excludefilestr) && (!usinginipath || (usinginipath && strpath.match(inipathname))))788modopts['PATH'] = strpath789print_status("Path: #{strpath}")790791begin792if execmod793rpc_round_exec(xref[0], xref[1], modopts, njobs)794end795rescue ::Exception796print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")797end798end799end800when :wmap_dir801if ((node.is_leaf? && !strpath.include?('.')) || node.is_root? || !node.is_leaf?) && (!usinginipath || (usinginipath && strpath.match(inipathname)))802803modopts['PATH'] = strpath804print_status("Path: #{strpath}")805806begin807if execmod808rpcnode = rpc_round_exec(xref[0], xref[1], modopts, njobs)809end810rescue ::Exception811print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")812end813end814end815end816end817rescue ::Exception818print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")819end820end821822#823# Run modules for each request to play with URI with UNIQUE query parameters.824# wmap_unique_query825#826print_status "\n=[ Unique Query testing ]="827print_line '=' * sizeline828829idx = 0830matches5.each_key do |xref|831if masstop832print_error('STOPPED.')833return834end835836# Module not part of profile or not match837next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)838839idx += 1840841begin842# Module options hash843modopts = Hash.new844845#846# The code is just a proof-of-concept and will be expanded in the future847#848849print_status "Module #{xref[0]}"850851if (mode & wmap_expl != 0)852#853# For modules to have access to the global datastore854# i.e. set -g DOMAIN test.com855#856framework.datastore.each do |gkey, gval|857modopts[gkey] = gval858end859860#861# Parameters passed in hash xref862#863864modopts['RHOST'] = selected_host865modopts['RHOSTS'] = selected_host866modopts['RPORT'] = selected_port.to_s867modopts['SSL'] = selected_ssl868modopts['VHOST'] = selected_vhost.to_s869modopts['VERBOSE'] = moduleverbose870modopts['ShowProgress'] = showprogress871modopts['RunAsJob'] = jobify872873#874# Run the plugins for each request that have a distinct875# GET/POST URI QUERY string.876#877878utest_query = Hash.new879880h = framework.db.workspace.hosts.find_by_address(selected_host)881s = h.services.find_by_port(selected_port)882w = s.web_sites.find_by_vhost(selected_vhost)883884w.web_forms.each do |form|885if masstop886print_error('STOPPED.')887return888end889890#891# Only test unique query strings by comparing signature to previous tested signatures 'path,p1,p2,pn'892#893894datastr = ''895typestr = ''896897temparr = []898899# print_status "---------"900# print_status form.params901# print_status "+++++++++"902903form.params.each do |p|904pn, pv, _pt = p905if pn906if !pn.empty?907if !pv || pv.empty?908# TODO: add value based on param name909pv = 'aaa'910end911912# temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)913temparr << pn.to_s + '=' + pv.to_s914end915else916print_error("Blank parameter name. Form #{form.path}")917end918end919920datastr = temparr.join('&') if (temparr && !temparr.empty?)921922if (utest_query.key?(signature(form.path, datastr)) == false)923924modopts['METHOD'] = form.method.upcase925modopts['PATH'] = form.path926modopts['QUERY'] = form.query927if form.method.upcase == 'GET'928modopts['QUERY'] = datastr929modopts['DATA'] = ''930end931if form.method.upcase == 'POST'932modopts['DATA'] = datastr933end934modopts['TYPES'] = typestr935936#937# TODO: Add headers, etc.938#939if !usinginipath || (usinginipath && form.path.match(inipathname))940print_status "Path #{form.path}"941942# print_status("Unique PATH #{modopts['PATH']}")943# print_status("Unique GET #{modopts['QUERY']}")944# print_status("Unique POST #{modopts['DATA']}")945# print_status("MODOPTS: #{modopts}")946947begin948if execmod949rpcnode = rpc_round_exec(xref[0], xref[1], modopts, njobs)950end951utest_query[signature(form.path, datastr)] = 1952rescue ::Exception953print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")954end955end956end957end958end959rescue ::Exception960print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")961end962end963964#965# Run modules for each request to play with URI query parameters.966# This approach will reduce the complexity of the Tree used before967# and will make this shotgun implementation much simple.968# wmap_query969#970print_status "\n=[ Query testing ]="971print_line '=' * sizeline972973idx = 0974matches2.each_key do |xref|975if masstop976print_error('STOPPED.')977return978end979980# Module not part of profile or not match981next unless !(using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)982983idx += 1984985begin986# Module options hash987modopts = Hash.new988989#990# The code is just a proof-of-concept and will be expanded in the future991#992993print_status "Module #{xref[0]}"994995if (mode & wmap_expl != 0)996997#998# For modules to have access to the global datastore999# i.e. set -g DOMAIN test.com1000#1001framework.datastore.each do |gkey, gval|1002modopts[gkey] = gval1003end10041005#1006# Parameters passed in hash xref1007#10081009modopts['RHOST'] = selected_host1010modopts['RHOSTS'] = selected_host1011modopts['RPORT'] = selected_port.to_s1012modopts['SSL'] = selected_ssl1013modopts['VHOST'] = selected_vhost.to_s1014modopts['VERBOSE'] = moduleverbose1015modopts['ShowProgress'] = showprogress1016modopts['RunAsJob'] = jobify10171018#1019# Run the plugins for each request that have a distinct1020# GET/POST URI QUERY string.1021#10221023h = framework.db.workspace.hosts.find_by_address(selected_host)1024s = h.services.find_by_port(selected_port)1025w = s.web_sites.find_by_vhost(selected_vhost)10261027w.web_forms.each do |req|1028if masstop1029print_error('STOPPED.')1030return1031end10321033datastr = ''1034typestr = ''10351036temparr = []10371038req.params.each do |p|1039pn, pv, _pt = p1040if pn1041if !pn.empty?1042if !pv || pv.empty?1043# TODO: add value based on param name1044pv = 'aaa'1045end1046# temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)1047temparr << pn.to_s + '=' + pv.to_s1048end1049else1050print_error("Blank parameter name. Form #{req.path}")1051end1052end10531054datastr = temparr.join('&') if (temparr && !temparr.empty?)10551056modopts['METHOD'] = req.method.upcase1057modopts['PATH'] = req.path1058if req.method.upcase == 'GET'1059modopts['QUERY'] = datastr1060modopts['DATA'] = ''1061end1062modopts['DATA'] = datastr if req.method.upcase == 'POST'1063modopts['TYPES'] = typestr10641065#1066# TODO: Add method, headers, etc.1067#1068if !usinginipath || (usinginipath && req.path.match(inipathname))1069print_status "Path #{req.path}"10701071# print_status("Query PATH #{modopts['PATH']}")1072# print_status("Query GET #{modopts['QUERY']}")1073# print_status("Query POST #{modopts['DATA']}")1074# print_status("Query TYPES #{typestr}")10751076begin1077if execmod1078rpc_round_exec(xref[0], xref[1], modopts, njobs)1079end1080rescue ::Exception1081print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")1082end1083end1084end1085end1086rescue ::Exception1087print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")1088end1089end10901091#1092# Handle modules that need to be after all tests, once.1093# Good place to have modules that analyze the test results and/or1094# launch exploits.1095# :wmap_generic1096#1097print_status "\n=[ General testing ]="1098print_line '=' * sizeline10991100idx = 01101matches10.each_key do |xref|1102if masstop1103print_error('STOPPED.')1104return1105end11061107# Module not part of profile or not match1108next unless !(using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)11091110idx += 111111112begin1113# Module options hash1114modopts = Hash.new11151116#1117# The code is just a proof-of-concept and will be expanded in the future1118#11191120print_status "Module #{xref[0]}"11211122if (mode & wmap_expl != 0)11231124#1125# For modules to have access to the global datastore1126# i.e. set -g DOMAIN test.com1127#1128framework.datastore.each do |gkey, gval|1129modopts[gkey] = gval1130end11311132#1133# Parameters passed in hash xref1134#11351136modopts['RHOST'] = selected_host1137modopts['RHOSTS'] = selected_host1138modopts['RPORT'] = selected_port.to_s1139modopts['SSL'] = selected_ssl1140modopts['VHOST'] = selected_vhost.to_s1141modopts['VERBOSE'] = moduleverbose1142modopts['ShowProgress'] = showprogress1143modopts['RunAsJob'] = jobify11441145#1146# Run the plugins that only need to be1147# launched once.1148#11491150begin1151if execmod1152rpc_round_exec(xref[0], xref[1], modopts, njobs)1153end1154rescue ::Exception1155print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")1156end1157end1158rescue ::Exception1159print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")1160end1161end11621163if (mode & wmap_expl != 0)1164print_line '+' * sizeline11651166if !(runlocal && execmod)1167rpc_list_nodes1168print_status('Note: Use wmap_nodes -l to list node status for completion')1169end11701171print_line("Launch completed in #{Time.now.to_f - stamp} seconds.")1172print_line '+' * sizeline1173end11741175print_status('Done.')1176end11771178# EOM1179end11801181def view_targets1182if targets.nil? || targets.keys.empty?1183print_status 'No targets have been defined'1184return1185end11861187indent = ' '11881189tbl = Rex::Text::Table.new(1190'Indent' => indent.length,1191'Header' => 'Defined targets',1192'Columns' =>1193[1194'Id',1195'Vhost',1196'Host',1197'Port',1198'SSL',1199'Path',1200]1201)12021203targets.each_with_index do |t, idx|1204tbl << [ idx.to_s, t[1][:vhost], t[1][:host], t[1][:port], t[1][:ssl], "\t" + t[1][:path].to_s ]1205end12061207print_status tbl.to_s + "\n"1208end12091210def delete_sites(wmap_index)1211idx = 01212to_del = {}1213# Rebuild the index from wmap_sites -l1214framework.db.hosts.each do |bdhost|1215bdhost.services.each do |serv|1216serv.web_sites.each do |web|1217# If the index of this site matches any deletion index,1218# add to our hash, saving the index for later output1219to_del[idx] = web if wmap_index.any? { |w| w.to_i == idx }1220idx += 11221end1222end1223end1224to_del.each do |widx, wsite|1225if wsite.delete1226print_status("Deleted #{wsite.vhost} on #{wsite.service.host.address} at index #{widx}")1227else1228print_error("Could note delete {wsite.vhost} on #{wsite.service.host.address} at index #{widx}")1229end1230end1231end12321233def view_sites1234# Clean temporary sites list1235self.lastsites = []12361237indent = ' '12381239tbl = Rex::Text::Table.new(1240'Indent' => indent.length,1241'Header' => 'Available sites',1242'Columns' =>1243[1244'Id',1245'Host',1246'Vhost',1247'Port',1248'Proto',1249'# Pages',1250'# Forms',1251]1252)12531254idx = 01255framework.db.hosts.each do |bdhost|1256bdhost.services.each do |serv|1257serv.web_sites.each do |web|1258c = web.web_pages.count1259f = web.web_forms.count1260tbl << [ idx.to_s, bdhost.address, web.vhost, serv.port, serv.name, c.to_s, f.to_s ]1261idx += 112621263turl = web.vhost + ',' + serv.name + '://' + bdhost.address.to_s + ':' + serv.port.to_s + '/'1264lastsites << turl1265end1266end1267end12681269print_status tbl.to_s + "\n"1270end12711272# Reusing code from hdmoore1273#1274# Allow the URL to be supplied as VHOST,URL if a custom VHOST1275# should be used. This allows for things like:1276# localhost,http://192.168.0.2/admin/12771278def add_web_site(url)1279vhost = nil12801281# Allow the URL to be supplied as VHOST,URL if a custom VHOST1282# should be used. This allows for things like:1283# localhost,http://192.168.0.2/admin/12841285if url !~ /^http/1286vhost, url = url.split(',', 2)1287if url.to_s.empty?1288url = vhost1289vhost = nil1290end1291end12921293# Prefix http:// when the URL has no specified parameter1294if url !~ %r{^[a-z0-9A-Z]+://}1295url = 'http://' + url1296end12971298uri = begin1299URI.parse(url)1300rescue StandardError1301nil1302end1303if !uri1304print_error("Could not understand URL: #{url}")1305return1306end13071308vhost = uri.hostname if vhost.nil?13091310if uri.scheme !~ /^https?/1311print_error("Only http and https URLs are accepted: #{url}")1312return1313end13141315ssl = false1316if uri.scheme == 'https'1317ssl = true1318end13191320site = begin1321framework.db.report_web_site(wait: true, host: uri.host, port: uri.port, vhost: vhost, ssl: ssl, workspace: framework.db.workspace)1322rescue SocketError => e1323elog("Could not get address for #{uri.host}", 'wmap', error: e)1324print_status("Could not get address for #{uri.host}.")1325nil1326end13271328return site1329end13301331# Code by hdm. Modified two lines by et1332#1333def process_urls(urlstr)1334target_whitelist = []13351336urls = urlstr.to_s.split(/\s+/)13371338urls.each do |url|1339next if url.to_s.strip.empty?13401341vhost = nil13421343# Allow the URL to be supplied as VHOST,URL if a custom VHOST1344# should be used. This allows for things like:1345# localhost,http://192.168.0.2/admin/13461347if url !~ /^http/1348vhost, url = url.split(',', 2)1349if url.to_s.empty?1350url = vhost1351vhost = nil1352end1353end13541355# Prefix http:// when the URL has no specified parameter1356if url !~ %r{^[a-z0-9A-Z]+://}1357url = 'http://' + url1358end13591360uri = begin1361URI.parse(url)1362rescue StandardError1363nil1364end1365if !uri1366print_error("Could not understand URL: #{url}")1367next1368end13691370if uri.scheme !~ /^https?/1371print_error("Only http and https URLs are accepted: #{url}")1372next1373end13741375target_whitelist << [vhost || uri.host, uri]1376end13771378# Skip the DB entirely if no matches1379return if target_whitelist.empty?13801381if !targets1382# First time targets are defined1383self.targets = Hash.new1384end13851386target_whitelist.each do |ent|1387vhost, target = ent13881389begin1390address = Rex::Socket.getaddress(target.host, true)1391rescue SocketError => e1392elog("Could not get address for #{target.host}", 'wmap', error: e)1393print_status("Could not get address for #{target.host}. Skipping.")1394next1395end13961397host = framework.db.workspace.hosts.find_by_address(address)1398if !host1399print_error("No matching host for #{target.host}")1400next1401end1402serv = host.services.find_by_port_and_proto(target.port, 'tcp')1403if !serv1404print_error("No matching service for #{target.host}:#{target.port}")1405next1406end14071408sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)14091410sites.each do |site|1411# Initial default path1412inipath = target.path1413if target.path.empty?1414inipath = '/'1415end14161417# site.web_forms.where(path: target.path).each do |form|1418ckey = [ site.vhost, host.address, serv.port, inipath].join('|')14191420if !targets[ckey]1421targets[ckey] = WebTarget.new1422targets[ckey].merge!({1423vhost: site.vhost,1424host: host.address,1425port: serv.port,1426ssl: (serv.name == 'https'),1427path: inipath1428})1429# self.targets[ckey][inipath] = []1430else1431print_status('Target already set in targets list.')1432end14331434# Store the form object in the hash for this path1435# self.targets[ckey][inipath] << inipath1436# end1437end1438end1439end14401441# Code by hdm. Modified two lines by et1442# lastsites contains a temporary array with vhost,url strings so the id can be1443# referenced in the array and prevent new sites added in the db to corrupt previous id list.1444def process_ids(idsstr)1445if !lastsites || lastsites.empty?1446view_sites1447print_status('Web sites ids. referenced from previous table.')1448end14491450target_whitelist = []1451ids = idsstr.to_s.split(/,/)14521453ids.each do |id|1454next if id.to_s.strip.empty?14551456if id.to_i > lastsites.length1457print_error("Skipping id #{id}...")1458else1459target_whitelist << lastsites[id.to_i]1460print_status("Loading #{lastsites[id.to_i]}.")1461end1462end14631464# Skip the DB entirely if no matches1465return if target_whitelist.empty?14661467if !targets1468self.targets = Hash.new1469end14701471target_whitelist.each do |ent|1472process_urls(ent)1473end1474end14751476def view_site_tree(urlstr, md, ld)1477if !urlstr1478return1479end14801481site_whitelist = []14821483urls = urlstr.to_s.split(/\s+/)14841485urls.each do |url|1486next if url.to_s.strip.empty?14871488vhost = nil14891490# Allow the URL to be supplied as VHOST,URL if a custom VHOST1491# should be used. This allows for things like:1492# localhost,http://192.168.0.2/admin/14931494if url !~ /^http/1495vhost, url = url.split(',', 2)14961497if url.to_s.empty?1498url = vhost1499vhost = nil1500end1501end15021503# Prefix http:// when the URL has no specified parameter1504if url !~ %r{^[a-z0-9A-Z]+://}1505url = 'http://' + url1506end15071508uri = begin1509URI.parse(url)1510rescue StandardError1511nil1512end1513if !uri1514print_error("Could not understand URL: #{url}")1515next1516end15171518if uri.scheme !~ /^https?/1519print_error("Only http and https URLs are accepted: #{url}")1520next1521end15221523site_whitelist << [vhost || uri.host, uri]1524end15251526# Skip the DB entirely if no matches1527return if site_whitelist.empty?15281529site_whitelist.each do |ent|1530vhost, target = ent15311532host = framework.db.workspace.hosts.find_by_address(target.host)1533unless host1534print_error("No matching host for #{target.host}")1535next1536end1537serv = host.services.find_by_port_and_proto(target.port, 'tcp')1538unless serv1539print_error("No matching service for #{target.host}:#{target.port}")1540next1541end15421543sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)15441545sites.each do |site|1546t = load_tree(site)1547print_tree(t, target.host, md, ld)1548print_line("\n")1549end1550end1551end15521553# Private function to avoid duplicate code1554def load_tree_core(req, wtree)1555pathchr = '/'1556tarray = req.path.to_s.split(pathchr)1557tarray.delete('')1558tpath = Pathname.new(pathchr)1559tarray.each do |df|1560wtree.add_at_path(tpath.to_s, df)1561tpath += Pathname.new(df.to_s)1562end1563end15641565#1566# Load website structure into a tree1567#1568def load_tree(s)1569wtree = Tree.new(s.vhost)15701571# Load site pages1572s.web_pages.order('path asc').each do |req|1573if req.code != 4041574load_tree_core(req, wtree)1575end1576end15771578# Load site forms1579s.web_forms.each do |req|1580load_tree_core(req, wtree)1581end15821583wtree1584end15851586def print_file(filename)1587ext = File.extname(filename)1588if %w[.txt .md].include? ext1589print '%bld%red'1590elsif %w[.css .js].include? ext1591print '%grn'1592end15931594print_line("#{filename}%clr")1595end15961597#1598# Recursive function for printing the tree structure1599#1600def print_tree_recursive(tree, max_level, indent, prefix, is_last, unicode)1601if !tree.nil? && (tree.depth <= max_level)1602print(' ' * indent)16031604# Prefix serve to print the superior hierarchy1605prefix.each do |bool|1606if unicode1607print (bool ? ' ' : '│') + (' ' * 3)1608else1609print (bool ? ' ' : '|') + (' ' * 3)1610end1611end1612if unicode1613# The last children is special1614print (is_last ? '└' : '├') + ('─' * 2) + ' '1615else1616print (is_last ? '`' : '|') + ('-' * 2) + ' '1617end16181619c = tree.children.count16201621if c > 01622print_line "%bld%blu#{tree.name}%clr (#{c})"1623else1624print_file tree.name1625end16261627i = 11628new_prefix = prefix + [is_last]1629tree.children.each_pair do |_, child|1630is_last = i >= c1631print_tree_recursive(child, max_level, indent, new_prefix, is_last, unicode)1632i += 11633end1634end1635end16361637#1638# Print Tree structure. Less ugly1639# Modified by Jon P.1640#1641def print_tree(tree, ip, max_level, unicode)1642indent = 41643if !tree.nil? && (tree.depth <= max_level)1644if tree.depth == 01645print_line "\n" + (' ' * indent) + "%cya[#{tree.name}] (#{ip})%clr"1646end16471648i = 11649c = tree.children.count1650tree.children.each_pair do |_, child|1651print_tree_recursive(child, max_level, indent, [], i >= c, unicode)1652i += 11653end16541655end1656end16571658#1659# Signature of the form ',p1,p2,pn' then to be appended to path: path,p1,p2,pn1660#1661def signature(fpath, fquery)1662hsig = queryparse(fquery)1663fpath + ',' + hsig.map { |p| p[0].to_s }.join(',')1664end16651666def queryparse(query)1667params = Hash.new16681669query.split(/[&;]/n).each do |pairs|1670key, value = pairs.split('=', 2)1671if params.key?(key)1672# Error1673else1674params[key] = value1675end1676end1677params1678end16791680def rpc_add_node(host, port, ssl, user, pass, bypass_exist)1681if !rpcarr1682self.rpcarr = Hash.new1683end16841685istr = "#{host}|#{port}|#{ssl}|#{user}|#{pass}"16861687if rpcarr.key?(istr) && !bypass_exist && !rpcarr[istr].nil?1688print_error("Connection already exists #{istr}")1689return1690end16911692begin1693temprpc = ::Msf::RPC::Client.new(1694host: host,1695port: port,1696ssl: ssl1697)1698rescue StandardError1699print_error 'Unable to connect'1700# raise ConnectionError1701return1702end17031704res = temprpc.login(user, pass)17051706if !res1707print_error("Unable to authenticate to #{host}:#{port}.")1708return1709end17101711res = temprpc.call('core.version')1712print_status("Connected to #{host}:#{port} [#{res['version']}].")1713rpcarr[istr] = temprpc1714rescue StandardError1715print_error('Unable to connect')1716end17171718def local_module_exec(mod, mtype, opts, _nmaxjobs)1719jobify = false17201721modinst = framework.modules.create(mod)17221723if !modinst1724print_error('Unknown module')1725return1726end17271728sess = nil17291730case mtype1731when 'auxiliary'1732Msf::Simple::Auxiliary.run_simple(modinst, {1733'Action' => opts['ACTION'],1734'LocalOutput' => driver.output,1735'RunAsJob' => jobify,1736'Options' => opts1737})1738when 'exploit'1739if !(opts['PAYLOAD'])1740opts['PAYLOAD'] = WmapCommandDispatcher::Exploit.choose_payload(modinst, opts['TARGET'])1741end17421743sess = Msf::Simple::Exploit.exploit_simple(modinst, {1744'Payload' => opts['PAYLOAD'],1745'Target' => opts['TARGET'],1746'LocalOutput' => driver.output,1747'RunAsJob' => jobify,1748'Options' => opts1749})1750else1751print_error('Wrong mtype.')1752end17531754if sess1755if ((jobify == false) && sess.interactive?)1756print_line1757driver.run_single("sessions -q -i #{sess.sid}")1758else1759print_status("Session #{sess.sid} created in the background.")1760end1761end1762end17631764def rpc_round_exec(mod, mtype, opts, nmaxjobs)1765res = nil1766idx = 017671768if active_rpc_nodes == 01769if !runlocal1770print_error('All active nodes not working or removed')1771return1772end1773res = true1774else1775rpc_reconnect_nodes1776end17771778if masstop1779return1780end17811782until res1783if active_rpc_nodes == 01784print_error('All active nodes not working or removed')1785return1786end17871788# find the node with less jobs load.1789minjobs = nmaxjobs1790minconn = nil1791nid = 01792rpcarr.each do |k, rpccon|1793if !rpccon1794print_error("Skipping inactive node #{nid} #{k}")1795nid += 11796end17971798begin1799currentjobs = rpccon.call('job.list').length18001801if currentjobs < minjobs1802minconn = rpccon1803minjobs = currentjobs1804end18051806if currentjobs == nmaxjobs && (nmaxdisplay == false)1807print_error("Node #{nid} reached max number of jobs #{nmaxjobs}")1808print_error('Waiting for available node/slot...')1809self.nmaxdisplay = true1810end1811# print_status("Node #{nid} #currentjobs #{currentjobs} #min #{minjobs}")1812rescue StandardError1813print_error("Unable to connect. Node #{tarr[0]}:#{tarr[1]}")1814rpcarr[k] = nil18151816if active_rpc_nodes == 01817print_error('All active nodes, not working or removed')1818return1819else1820print_error('Sending job to next node')1821next1822end1823end18241825nid += 11826end18271828if minjobs < nmaxjobs1829res = minconn.call('module.execute', mtype, mod, opts)1830self.nmaxdisplay = false1831# print_status(">>>#{res} #{mod}")18321833if res1834if res.key?('job_id')1835return1836else1837print_error("Unable to execute module in node #{k} #{res}")1838end1839end1840end18411842# print_status("Max number of jobs #{nmaxjobs} reached in node #{k}") if minjobs >= nmaxjobs18431844idx += 11845end18461847if runlocal && !masstop1848local_module_exec(mod, mtype, opts, nmaxjobs)1849end1850end18511852def rpc_db_nodes(host, port, user, pass, name)1853rpc_reconnect_nodes18541855if active_rpc_nodes == 01856print_error('No active nodes at this time')1857return1858end18591860rpcarr.each do |k, v|1861if v1862v.call('db.driver', { driver: 'postgresql' })1863v.call('db.connect', { database: name, host: host, port: port, username: user, password: pass })18641865res = v.call('db.status')18661867if res['db'] == name1868print_status("db_connect #{res} #{host}:#{port} OK")1869else1870print_error("Error db_connect #{res} #{host}:#{port}")1871end1872else1873print_error("No connection to node #{k}")1874end1875end1876end18771878def rpc_reconnect_nodes1879# Sucky 5 mins token timeout.18801881idx = nil1882rpcarr.each do |k, rpccon|1883next unless rpccon18841885idx = k1886begin1887rpccon.call('job.list').length1888rescue StandardError1889tarr = k.split('|')18901891res = rpccon.login(tarr[3], tarr[4])18921893raise ConnectionError unless res18941895print_error("Reauth to node #{tarr[0]}:#{tarr[1]}")1896break1897end1898end1899rescue StandardError1900print_error("ERROR CONNECTING TO NODE. Disabling #{idx} use wmap_nodes -a to reconnect")1901rpcarr[idx] = nil1902if active_rpc_nodes == 01903print_error('No active nodes')1904self.masstop = true1905end1906end19071908def rpc_kill_node(i, j)1909if !i1910print_error('Nodes not defined')1911return1912end19131914if !j1915print_error('Node jobs defined')1916return1917end19181919rpc_reconnect_nodes19201921if active_rpc_nodes == 01922print_error('No active nodes at this time')1923return1924end19251926idx = 01927rpcarr.each do |_k, rpccon|1928if (idx == i.to_i) || (i.upcase == 'ALL')1929# begin1930if !rpccon1931print_error("No connection to node #{idx}")1932else1933n = rpccon.call('job.list')1934n.each do |id, name|1935if (j == id.to_s) || (j.upcase == 'ALL')1936rpccon.call('job.stop', id)1937print_status("Node #{idx} Killed job id #{id} #{name}")1938end1939end1940end1941# rescue1942# print_error("No connection")1943# end1944end1945idx += 11946end1947end19481949def rpc_view_jobs1950indent = ' '19511952rpc_reconnect_nodes19531954if active_rpc_nodes == 01955print_error('No active nodes at this time')1956return1957end19581959idx = 01960rpcarr.each do |k, rpccon|1961if !rpccon1962print_status("[Node ##{idx}: #{k} DISABLED/NO CONNECTION]")1963else19641965arrk = k.split('|')1966print_status("[Node ##{idx}: #{arrk[0]} Port:#{arrk[1]} SSL:#{arrk[2]} User:#{arrk[3]}]")19671968begin1969n = rpccon.call('job.list')19701971tbl = Rex::Text::Table.new(1972'Indent' => indent.length,1973'Header' => 'Jobs',1974'Columns' =>1975[1976'Id',1977'Job name',1978'Target',1979'PATH',1980]1981)19821983n.each do |id, name|1984jinfo = rpccon.call('job.info', id)1985dstore = jinfo['datastore']1986tbl << [ id.to_s, name, dstore['VHOST'] + ':' + dstore['RPORT'], dstore['PATH']]1987end19881989print_status tbl.to_s + "\n"1990rescue StandardError1991print_status("[Node ##{idx} #{k} DISABLED/NO CONNECTION]")1992end1993end1994idx += 11995end1996end19971998# Modified from http://stackoverflow.com/questions/946738/detect-key-press-non-blocking-w-o-getc-gets-in-ruby1999def quit?2000while (c = driver.input.read_nonblock(1))2001print_status('Quited')2002return true if c == 'Q'2003end2004false2005rescue Errno::EINTR2006false2007rescue Errno::EAGAIN2008false2009rescue EOFError2010true2011end20122013def rpc_mon_nodes2014# Pretty monitor20152016color = begin2017opts['ConsoleDriver'].output.supports_color?2018rescue StandardError2019false2020end20212022colors = [2023'%grn',2024'%blu',2025'%yel',2026'%whi'2027]20282029# begin2030loop do2031rpc_reconnect_nodes20322033idx = 02034rpcarr.each do |_k, rpccon|2035v = 'NOCONN'2036n = 12037c = '%red'20382039if !rpccon2040v = 'NOCONN'2041n = 12042c = '%red'2043else2044begin2045v = ''2046c = '%blu'2047rescue StandardError2048v = 'ERROR'2049c = '%red'2050end20512052begin2053n = rpccon.call('job.list').length2054c = '%blu'2055rescue StandardError2056n = 12057v = 'NOCONN'2058c = '%red'2059end2060end20612062# begin2063if !@stdio2064@stdio = Rex::Ui::Text::Output::Stdio.new2065end20662067if color == true2068@stdio.auto_color2069else2070@stdio.disable_color2071end2072msg = "[#{idx}] #{"%bld#{c}||%clr" * n} #{n} #{v}\n"2073@stdio.print_raw(@stdio.substitute_colors(msg))20742075# rescue2076# blah2077# end2078sleep(2)2079idx += 12080end2081end2082# rescue2083# print_status("End.")2084# end2085end20862087def rpc_list_nodes2088indent = ' '20892090tbl = Rex::Text::Table.new(2091'Indent' => indent.length,2092'Header' => 'Nodes',2093'Columns' =>2094[2095'Id',2096'Host',2097'Port',2098'SSL',2099'User',2100'Pass',2101'Status',2102'#jobs',2103]2104)21052106idx = 021072108rpc_reconnect_nodes21092110rpcarr.each do |k, rpccon|2111arrk = k.split('|')21122113if !rpccon2114v = 'NOCONN'2115n = ''2116else2117begin2118v = rpccon.call('core.version')['version']2119rescue StandardError2120v = 'ERROR'2121end21222123begin2124n = rpccon.call('job.list').length2125rescue StandardError2126n = ''2127end2128end21292130tbl << [ idx.to_s, arrk[0], arrk[1], arrk[2], arrk[3], arrk[4], v, n]2131idx += 12132end21332134print_status tbl.to_s + "\n"2135end21362137def active_rpc_nodes2138return 0 if rpcarr.empty?21392140idx = 02141rpcarr.each do |_k, conn|2142if conn2143idx += 12144end2145end21462147idx2148end21492150def view_modules2151indent = ' '21522153wmaptype = %i[2154wmap_ssl2155wmap_server2156wmap_dir2157wmap_file2158wmap_unique_query2159wmap_query2160wmap_generic2161]21622163if !wmapmodules2164load_wmap_modules(true)2165end21662167wmaptype.each do |modt|2168tbl = Rex::Text::Table.new(2169'Indent' => indent.length,2170'Header' => modt.to_s,2171'Columns' =>2172[2173'Name',2174'OrderID',2175]2176)21772178idx = 02179wmapmodules.each do |w|2180oid = w[3]2181if w[3] == 0xFFFFFF2182oid = ':last'2183end21842185if w[2] == modt2186tbl << [w[0], oid]2187idx += 12188end2189end21902191print_status tbl.to_s + "\n"2192end2193end21942195# Sort hash by orderid2196# Yes sorting hashes dont make sense but actually it does when you are enumerating one. And2197# sort_by of a hash returns an array so this is the reason for this ugly piece of code2198def sort_by_orderid(matches)2199temphash = Hash.new22002201temparr = matches.sort_by do |xref, _v|2202xref[3]2203end22042205temparr.each do |b|2206temphash[b[0]] = b[1]2207end22082209temphash2210end22112212# Load all wmap modules2213def load_wmap_modules(reload)2214if reload || !wmapmodules2215print_status('Loading wmap modules...')22162217self.wmapmodules = []22182219idx = 02220[ [ framework.auxiliary, 'auxiliary' ], [framework.exploits, 'exploit' ] ].each do |mtype|2221# Scan all exploit modules for matching references2222mtype[0].each_module do |n, m|2223e = m.new22242225# Only include wmap_enabled plugins2226next unless e.respond_to?('wmap_enabled')22272228penabled = e.wmap_enabled22292230if penabled2231wmapmodules << [mtype[1] + '/' + n, mtype[1], e.wmap_type, e.orderid]2232idx += 12233end2234end2235end2236print_status("#{idx} wmap enabled modules loaded.")2237end2238end22392240def view_vulns2241framework.db.hosts.each do |host|2242host.services.each do |serv|2243serv.web_sites.each do |site|2244site.web_vulns.each do |wv|2245print_status("+ [#{host.address}] (#{site.vhost}): #{wv.category} #{wv.path}")2246print_status("\t#{wv.name} #{wv.description}")2247print_status("\t#{wv.method} #{wv.proof}")2248end2249end2250end2251end2252end2253end22542255class WebTarget < ::Hash2256def to_url2257proto = self[:ssl] ? 'https' : 'http'2258"#{proto}://#{self[:host]}:#{self[:port]}#{self[:path]}"2259end2260end22612262def initialize(framework, opts)2263super22642265if framework.db.active == false2266raise 'Database not connected (try db_connect)'2267end22682269color = begin2270self.opts['ConsoleDriver'].output.supports_color?2271rescue StandardError2272false2273end22742275wmapversion = '1.5.1'22762277wmapbanner = "%red\n.-.-.-..-.-.-..---..---.%clr\n"2278wmapbanner += "%red| | | || | | || | || |-'%clr\n"2279wmapbanner += "%red`-----'`-'-'-'`-^-'`-'%clr\n"2280wmapbanner += "[WMAP #{wmapversion}] === et [ ] metasploit.com 2012\n"22812282if !@stdio2283@stdio = Rex::Ui::Text::Output::Stdio.new2284end22852286if color == true2287@stdio.auto_color2288else2289@stdio.disable_color2290end22912292@stdio.print_raw(@stdio.substitute_colors(wmapbanner))22932294add_console_dispatcher(WmapCommandDispatcher)2295# print_status("#{wmapbanner}")2296end22972298def cleanup2299remove_console_dispatcher('wmap')2300end23012302def name2303'wmap'2304end23052306def desc2307'Web assessment plugin'2308end23092310end2311end231223132314