CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/plugins/wmap.rb
Views: 11704
1
#
2
# Web assessment for the Metasploit Framework
3
# Efrain Torres - et[ ] metasploit.com 2012
4
#
5
6
require 'English'
7
require 'rabal/tree'
8
9
module Msf
10
class Plugin::Wmap < Msf::Plugin
11
class WmapCommandDispatcher
12
13
# @!attribute wmapmodules
14
# @return [Array] Enabled WMAP modules
15
# @!attribute targets
16
# @return [Hash] WMAP targets
17
# @!attribute lastsites
18
# @return [Array] Temp location of previously obtained sites
19
# @!attribute rpcarr
20
# @return [Array] Array or rpc connections
21
# @!attribute njobs
22
# @return [Integer] Max number of jobs
23
# @!attribute nmaxdisplay
24
# @return [Boolean] Flag to stop displaying the same message
25
# @!attribute runlocal
26
# @return [Boolean] Flag to run local modules only
27
# @!attribute masstop
28
# @return [Boolean] Flag to stop everything
29
# @!attribute killwhenstop
30
# @return [Boolean] Kill process when exiting
31
attr_accessor :wmapmodules, :targets, :lastsites, :rpcarr, :njobs, :nmaxdisplay, :runlocal, :masstop, :killwhenstop
32
33
include Msf::Ui::Console::CommandDispatcher
34
35
def name
36
'wmap'
37
end
38
39
#
40
# The initial command set
41
#
42
def commands
43
{
44
'wmap_targets' => 'Manage targets',
45
'wmap_sites' => 'Manage sites',
46
'wmap_nodes' => 'Manage nodes',
47
'wmap_run' => 'Test targets',
48
'wmap_modules' => 'Manage wmap modules',
49
'wmap_vulns' => 'Display web vulns'
50
}
51
end
52
53
def cmd_wmap_vulns(*args)
54
args.push('-h') if args.empty?
55
56
while (arg = args.shift)
57
case arg
58
when '-l'
59
view_vulns
60
when '-h'
61
print_status('Usage: wmap_vulns [options]')
62
print_line("\t-h Display this help text")
63
print_line("\t-l Display web vulns table")
64
65
print_line('')
66
else
67
print_error('Unknown flag.')
68
end
69
return
70
end
71
end
72
73
def cmd_wmap_modules(*args)
74
args.push('-h') if args.empty?
75
76
while (arg = args.shift)
77
case arg
78
when '-l'
79
view_modules
80
when '-r'
81
load_wmap_modules(true)
82
when '-h'
83
print_status('Usage: wmap_modules [options]')
84
print_line("\t-h Display this help text")
85
print_line("\t-l List all wmap enabled modules")
86
print_line("\t-r Reload wmap modules")
87
88
print_line('')
89
else
90
print_error('Unknown flag.')
91
end
92
return
93
end
94
end
95
96
def cmd_wmap_targets(*args)
97
args.push('-h') if args.empty?
98
99
while (arg = args.shift)
100
case arg
101
when '-c'
102
self.targets = Hash.new
103
when '-l'
104
view_targets
105
return
106
when '-t'
107
process_urls(args.shift)
108
when '-d'
109
process_ids(args.shift)
110
when '-h'
111
print_status('Usage: wmap_targets [options]')
112
print_line("\t-h Display this help text")
113
print_line("\t-t [urls] Define target sites (vhost1,url[space]vhost2,url) ")
114
print_line("\t-d [ids] Define target sites (id1, id2, id3 ...)")
115
print_line("\t-c Clean target sites list")
116
print_line("\t-l List all target sites")
117
118
print_line('')
119
return
120
else
121
print_error('Unknown flag.')
122
return
123
end
124
end
125
end
126
127
def cmd_wmap_sites(*args)
128
args.push('-h') if args.empty?
129
130
while (arg = args.shift)
131
case arg
132
when '-a'
133
site = args.shift
134
if site
135
s = add_web_site(site)
136
if s
137
print_status('Site created.')
138
else
139
print_error('Unable to create site')
140
end
141
else
142
print_error('No site provided.')
143
end
144
when '-d'
145
del_idx = args
146
if !del_idx.empty?
147
delete_sites(del_idx.select { |d| d =~ /^[0-9]*$/ }.map(&:to_i).uniq)
148
return
149
else
150
print_error('No index provided.')
151
end
152
when '-l'
153
view_sites
154
return
155
when '-s'
156
u = args.shift
157
l = args.shift
158
o = args.shift
159
160
return unless u
161
162
if l.nil? || l.empty?
163
l = 200
164
o = 'true'
165
elsif (l == 'true') || (l == 'false')
166
# Add check if unicode parameters is the second one
167
o = l
168
l = 200
169
else
170
l = l.to_i
171
end
172
173
o = (o == 'true')
174
175
if u.include? 'http'
176
# Parameters are in url form
177
view_site_tree(u, l, o)
178
else
179
# Parameters are digits
180
if !lastsites || lastsites.empty?
181
view_sites
182
print_status('Web sites ids. referenced from previous table.')
183
end
184
185
target_whitelist = []
186
ids = u.to_s.split(/,/)
187
188
ids.each do |id|
189
next if id.to_s.strip.empty?
190
191
if id.to_i > lastsites.length
192
print_error("Skipping id #{id}...")
193
else
194
target_whitelist << lastsites[id.to_i]
195
# print_status("Loading #{self.lastsites[id.to_i]}.")
196
end
197
end
198
199
# Skip the DB entirely if no matches
200
return if target_whitelist.empty?
201
202
unless targets
203
self.targets = Hash.new
204
end
205
206
target_whitelist.each do |ent|
207
view_site_tree(ent, l, o)
208
end
209
end
210
return
211
when '-h'
212
print_status('Usage: wmap_sites [options]')
213
print_line("\t-h Display this help text")
214
print_line("\t-a [url] Add site (vhost,url)")
215
print_line("\t-d [ids] Delete sites (separate ids with space)")
216
print_line("\t-l List all available sites")
217
print_line("\t-s [id] Display site structure (vhost,url|ids) (level) (unicode output true/false)")
218
print_line('')
219
return
220
else
221
print_error('Unknown flag.')
222
return
223
end
224
end
225
end
226
227
def cmd_wmap_nodes(*args)
228
if !rpcarr
229
self.rpcarr = Hash.new
230
end
231
232
args.push('-h') if args.empty?
233
234
while (arg = args.shift)
235
case arg
236
when '-a'
237
h = args.shift
238
r = args.shift
239
s = args.shift
240
u = args.shift
241
p = args.shift
242
243
res = rpc_add_node(h, r, s, u, p, false)
244
if res
245
print_status('Node created.')
246
else
247
print_error('Unable to create node')
248
end
249
when '-c'
250
idref = args.shift
251
252
if !idref
253
print_error('No id defined')
254
return
255
end
256
if idref.upcase == 'ALL'
257
print_status('All nodes removed')
258
self.rpcarr = Hash.new
259
else
260
idx = 0
261
rpcarr.each do |k, _v|
262
if idx == idref.to_i
263
rpcarr.delete(k)
264
print_status("Node deleted #{k}")
265
end
266
idx += 1
267
end
268
end
269
when '-d'
270
host = args.shift
271
port = args.shift
272
user = args.shift
273
pass = args.shift
274
dbname = args.shift
275
276
res = rpc_db_nodes(host, port, user, pass, dbname)
277
if res
278
print_status('OK.')
279
else
280
print_error('Error')
281
end
282
when '-l'
283
rpc_list_nodes
284
return
285
when '-j'
286
rpc_view_jobs
287
return
288
when '-k'
289
node = args.shift
290
jid = args.shift
291
rpc_kill_node(node, jid)
292
return
293
when '-h'
294
print_status('Usage: wmap_nodes [options]')
295
print_line("\t-h Display this help text")
296
print_line("\t-c id Remove id node (Use ALL for ALL nodes")
297
print_line("\t-a host port ssl user pass Add node")
298
print_line("\t-d host port user pass db Force all nodes to connect to db")
299
print_line("\t-j View detailed jobs")
300
print_line("\t-k ALL|id ALL|job_id Kill jobs on node")
301
print_line("\t-l List all current nodes")
302
303
print_line('')
304
return
305
else
306
print_error('Unknown flag.')
307
return
308
end
309
end
310
end
311
312
def cmd_wmap_run(*args)
313
# Stop everything
314
self.masstop = false
315
self.killwhenstop = true
316
317
trap('INT') do
318
print_error('Stopping execution...')
319
self.masstop = true
320
if killwhenstop
321
rpc_kill_node('ALL', 'ALL')
322
end
323
end
324
325
# Max numbers of concurrent jobs per node
326
self.njobs = 25
327
self.nmaxdisplay = false
328
self.runlocal = false
329
330
# Formatting
331
sizeline = 60
332
333
wmap_show = 2**0
334
wmap_expl = 2**1
335
336
# Exclude files can be modified by setting datastore['WMAP_EXCLUDE']
337
wmap_exclude_files = '.*\.(gif|jpg|png*)$'
338
339
run_wmap_ssl = true
340
run_wmap_server = true
341
run_wmap_dir_file = true
342
run_wmap_query = true
343
run_wmap_unique_query = true
344
run_wmap_generic = true
345
346
# If module supports datastore['VERBOSE']
347
moduleverbose = false
348
349
showprogress = false
350
351
if !rpcarr
352
self.rpcarr = Hash.new
353
end
354
355
if !run_wmap_ssl
356
print_status('Loading of wmap ssl modules disabled.')
357
end
358
if !run_wmap_server
359
print_status('Loading of wmap server modules disabled.')
360
end
361
if !run_wmap_dir_file
362
print_status('Loading of wmap dir and file modules disabled.')
363
end
364
if !run_wmap_query
365
print_status('Loading of wmap query modules disabled.')
366
end
367
if !run_wmap_unique_query
368
print_status('Loading of wmap unique query modules disabled.')
369
end
370
if !run_wmap_generic
371
print_status('Loading of wmap generic modules disabled.')
372
end
373
374
stamp = Time.now.to_f
375
mode = 0
376
377
eprofile = []
378
using_p = false
379
using_m = false
380
usinginipath = false
381
382
mname = ''
383
inipathname = '/'
384
385
args.push('-h') if args.empty?
386
387
while (arg = args.shift)
388
case arg
389
when '-t'
390
mode |= wmap_show
391
when '-e'
392
mode |= wmap_expl
393
394
profile = args.shift
395
396
if profile
397
print_status("Using profile #{profile}.")
398
399
begin
400
File.open(profile).each do |str|
401
if !str.include? '#'
402
# Not a comment
403
modname = str.strip
404
if !modname.empty?
405
eprofile << modname
406
end
407
end
408
using_p = true
409
end
410
rescue StandardError
411
print_error('Profile not found or invalid.')
412
return
413
end
414
else
415
print_status('Using ALL wmap enabled modules.')
416
end
417
when '-m'
418
mode |= wmap_expl
419
420
mname = args.shift
421
422
if mname
423
print_status("Using module #{mname}.")
424
end
425
using_m = true
426
when '-p'
427
mode |= wmap_expl
428
429
inipathname = args.shift
430
431
if inipathname
432
print_status("Using initial path #{inipathname}.")
433
end
434
usinginipath = true
435
436
when '-h'
437
print_status('Usage: wmap_run [options]')
438
print_line("\t-h Display this help text")
439
print_line("\t-t Show all enabled modules")
440
print_line("\t-m [regex] Launch only modules that name match provided regex.")
441
print_line("\t-p [regex] Only test path defined by regex.")
442
print_line("\t-e [/path/to/profile] Launch profile modules against all matched targets.")
443
print_line("\t (No profile file runs all enabled modules.)")
444
print_line('')
445
return
446
else
447
print_error('Unknown flag')
448
return
449
end
450
end
451
452
if rpcarr.empty? && (mode & wmap_show == 0)
453
print_error('NO WMAP NODES DEFINED. Executing local modules')
454
self.runlocal = true
455
end
456
457
if targets.nil?
458
print_error('Targets have not been selected.')
459
return
460
end
461
462
if targets.keys.empty?
463
print_error('Targets have not been selected.')
464
return
465
end
466
467
execmod = true
468
if (mode & wmap_show != 0)
469
execmod = false
470
end
471
472
targets.each_with_index do |t, idx|
473
selected_host = t[1][:host]
474
selected_port = t[1][:port]
475
selected_ssl = t[1][:ssl]
476
selected_vhost = t[1][:vhost]
477
478
print_status('Testing target:')
479
print_status("\tSite: #{selected_vhost} (#{selected_host})")
480
print_status("\tPort: #{selected_port} SSL: #{selected_ssl}")
481
print_line '=' * sizeline
482
print_status("Testing started. #{Time.now}")
483
484
if !selected_ssl
485
run_wmap_ssl = false
486
# print_status ("Target is not SSL. SSL modules disabled.")
487
end
488
489
# wmap_dir, wmap_file
490
matches = Hash.new
491
492
# wmap_server
493
matches1 = Hash.new
494
495
# wmap_query
496
matches2 = Hash.new
497
498
# wmap_ssl
499
matches3 = Hash.new
500
501
# wmap_unique_query
502
matches5 = Hash.new
503
504
# wmap_generic
505
matches10 = Hash.new
506
507
# OPTIONS
508
jobify = false
509
510
# This will be clean later
511
load_wmap_modules(false)
512
513
wmapmodules.each do |w|
514
case w[2]
515
when :wmap_server
516
if run_wmap_server
517
matches1[w] = true
518
end
519
when :wmap_query
520
if run_wmap_query
521
matches2[w] = true
522
end
523
when :wmap_unique_query
524
if run_wmap_unique_query
525
matches5[w] = true
526
end
527
when :wmap_generic
528
if run_wmap_generic
529
matches10[w] = true
530
end
531
when :wmap_dir, :wmap_file
532
if run_wmap_dir_file
533
matches[w] = true
534
end
535
when :wmap_ssl
536
if run_wmap_ssl
537
matches3[w] = true
538
end
539
else
540
# Black Hole
541
end
542
end
543
544
# Execution order (orderid)
545
matches = sort_by_orderid(matches)
546
matches1 = sort_by_orderid(matches1)
547
matches2 = sort_by_orderid(matches2)
548
matches3 = sort_by_orderid(matches3)
549
matches5 = sort_by_orderid(matches5)
550
matches10 = sort_by_orderid(matches10)
551
552
#
553
# Handle modules that need to be run before all tests IF SERVER is SSL, once usually again the SSL web server.
554
# :wmap_ssl
555
#
556
557
print_status "\n=[ SSL testing ]="
558
print_line '=' * sizeline
559
560
if !selected_ssl
561
print_status('Target is not SSL. SSL modules disabled.')
562
end
563
564
idx = 0
565
matches3.each_key do |xref|
566
if masstop
567
print_error('STOPPED.')
568
return
569
end
570
571
# Module not part of profile or not match
572
next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)
573
574
idx += 1
575
576
begin
577
# Module options hash
578
modopts = Hash.new
579
580
#
581
# The code is just a proof-of-concept and will be expanded in the future
582
#
583
print_status "Module #{xref[0]}"
584
585
if (mode & wmap_expl != 0)
586
587
#
588
# For modules to have access to the global datastore
589
# i.e. set -g DOMAIN test.com
590
#
591
framework.datastore.each do |gkey, gval|
592
modopts[gkey] = gval
593
end
594
595
#
596
# Parameters passed in hash xref
597
#
598
modopts['RHOST'] = selected_host
599
modopts['RHOSTS'] = selected_host
600
modopts['RPORT'] = selected_port.to_s
601
modopts['SSL'] = selected_ssl
602
modopts['VHOST'] = selected_vhost.to_s
603
modopts['VERBOSE'] = moduleverbose
604
modopts['ShowProgress'] = showprogress
605
modopts['RunAsJob'] = jobify
606
607
begin
608
if execmod
609
rpc_round_exec(xref[0], xref[1], modopts, njobs)
610
end
611
rescue ::Exception
612
print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")
613
end
614
end
615
rescue ::Exception
616
print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")
617
end
618
end
619
620
#
621
# Handle modules that need to be run before all tests, once usually again the web server.
622
# :wmap_server
623
#
624
print_status "\n=[ Web Server testing ]="
625
print_line '=' * sizeline
626
627
idx = 0
628
matches1.each_key do |xref|
629
if masstop
630
print_error('STOPPED.')
631
return
632
end
633
634
# Module not part of profile or not match
635
next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)
636
637
idx += 1
638
639
begin
640
# Module options hash
641
modopts = Hash.new
642
643
#
644
# The code is just a proof-of-concept and will be expanded in the future
645
#
646
647
print_status "Module #{xref[0]}"
648
649
if (mode & wmap_expl != 0)
650
651
#
652
# For modules to have access to the global datastore
653
# i.e. set -g DOMAIN test.com
654
#
655
framework.datastore.each do |gkey, gval|
656
modopts[gkey] = gval
657
end
658
659
#
660
# Parameters passed in hash xref
661
#
662
modopts['RHOST'] = selected_host
663
modopts['RHOSTS'] = selected_host
664
modopts['RPORT'] = selected_port.to_s
665
modopts['SSL'] = selected_ssl
666
modopts['VHOST'] = selected_vhost.to_s
667
modopts['VERBOSE'] = moduleverbose
668
modopts['ShowProgress'] = showprogress
669
modopts['RunAsJob'] = jobify
670
671
begin
672
if execmod
673
rpc_round_exec(xref[0], xref[1], modopts, njobs)
674
end
675
rescue ::Exception
676
print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")
677
end
678
end
679
rescue ::Exception
680
print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")
681
end
682
end
683
684
#
685
# Handle modules to be run at every path/file
686
# wmap_dir, wmap_file
687
#
688
print_status "\n=[ File/Dir testing ]="
689
print_line '=' * sizeline
690
691
idx = 0
692
matches.each_key do |xref|
693
if masstop
694
print_error('STOPPED.')
695
return
696
end
697
698
# Module not part of profile or not match
699
next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)
700
701
idx += 1
702
703
begin
704
# Module options hash
705
modopts = Hash.new
706
707
#
708
# The code is just a proof-of-concept and will be expanded in the future
709
#
710
711
print_status "Module #{xref[0]}"
712
713
if (mode & wmap_expl != 0)
714
#
715
# For modules to have access to the global datastore
716
# i.e. set -g DOMAIN test.com
717
#
718
framework.datastore.each do |gkey, gval|
719
modopts[gkey] = gval
720
end
721
722
#
723
# Parameters passed in hash xref
724
#
725
modopts['RHOST'] = selected_host
726
modopts['RHOSTS'] = selected_host
727
modopts['RPORT'] = selected_port.to_s
728
modopts['SSL'] = selected_ssl
729
modopts['VHOST'] = selected_vhost.to_s
730
modopts['VERBOSE'] = moduleverbose
731
modopts['ShowProgress'] = showprogress
732
modopts['RunAsJob'] = jobify
733
734
#
735
# Run the plugins that only need to be
736
# launched once.
737
#
738
739
wtype = xref[2]
740
741
h = framework.db.workspace.hosts.find_by_address(selected_host)
742
s = h.services.find_by_port(selected_port)
743
w = s.web_sites.find_by_vhost(selected_vhost)
744
745
test_tree = load_tree(w)
746
test_tree.each do |node|
747
if masstop
748
print_error('STOPPED.')
749
return
750
end
751
752
p = node.current_path
753
testpath = Pathname.new(p)
754
strpath = testpath.cleanpath(false).to_s
755
756
#
757
# Fixing paths
758
#
759
760
if node.is_leaf? && !node.is_root?
761
#
762
# Later we can add here more checks to see if its a file
763
#
764
elsif node.is_root?
765
strpath = '/'
766
else
767
strpath = strpath.chomp + '/'
768
end
769
770
strpath = strpath.gsub('//', '/')
771
# print_status("Testing path: #{strpath}")
772
773
#
774
# Launch plugin depending module type.
775
# Module type depends on main input type.
776
# Code may be the same but it depend on final
777
# versions of plugins
778
#
779
780
case wtype
781
when :wmap_file
782
if node.is_leaf? && !node.is_root?
783
#
784
# Check if an exclusion regex has been defined
785
#
786
excludefilestr = framework.datastore['WMAP_EXCLUDE'] || wmap_exclude_files
787
788
if !(strpath.match(excludefilestr) && (!usinginipath || (usinginipath && strpath.match(inipathname))))
789
modopts['PATH'] = strpath
790
print_status("Path: #{strpath}")
791
792
begin
793
if execmod
794
rpc_round_exec(xref[0], xref[1], modopts, njobs)
795
end
796
rescue ::Exception
797
print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")
798
end
799
end
800
end
801
when :wmap_dir
802
if ((node.is_leaf? && !strpath.include?('.')) || node.is_root? || !node.is_leaf?) && (!usinginipath || (usinginipath && strpath.match(inipathname)))
803
804
modopts['PATH'] = strpath
805
print_status("Path: #{strpath}")
806
807
begin
808
if execmod
809
rpcnode = rpc_round_exec(xref[0], xref[1], modopts, njobs)
810
end
811
rescue ::Exception
812
print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")
813
end
814
end
815
end
816
end
817
end
818
rescue ::Exception
819
print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")
820
end
821
end
822
823
#
824
# Run modules for each request to play with URI with UNIQUE query parameters.
825
# wmap_unique_query
826
#
827
print_status "\n=[ Unique Query testing ]="
828
print_line '=' * sizeline
829
830
idx = 0
831
matches5.each_key do |xref|
832
if masstop
833
print_error('STOPPED.')
834
return
835
end
836
837
# Module not part of profile or not match
838
next unless (using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)
839
840
idx += 1
841
842
begin
843
# Module options hash
844
modopts = Hash.new
845
846
#
847
# The code is just a proof-of-concept and will be expanded in the future
848
#
849
850
print_status "Module #{xref[0]}"
851
852
if (mode & wmap_expl != 0)
853
#
854
# For modules to have access to the global datastore
855
# i.e. set -g DOMAIN test.com
856
#
857
framework.datastore.each do |gkey, gval|
858
modopts[gkey] = gval
859
end
860
861
#
862
# Parameters passed in hash xref
863
#
864
865
modopts['RHOST'] = selected_host
866
modopts['RHOSTS'] = selected_host
867
modopts['RPORT'] = selected_port.to_s
868
modopts['SSL'] = selected_ssl
869
modopts['VHOST'] = selected_vhost.to_s
870
modopts['VERBOSE'] = moduleverbose
871
modopts['ShowProgress'] = showprogress
872
modopts['RunAsJob'] = jobify
873
874
#
875
# Run the plugins for each request that have a distinct
876
# GET/POST URI QUERY string.
877
#
878
879
utest_query = Hash.new
880
881
h = framework.db.workspace.hosts.find_by_address(selected_host)
882
s = h.services.find_by_port(selected_port)
883
w = s.web_sites.find_by_vhost(selected_vhost)
884
885
w.web_forms.each do |form|
886
if masstop
887
print_error('STOPPED.')
888
return
889
end
890
891
#
892
# Only test unique query strings by comparing signature to previous tested signatures 'path,p1,p2,pn'
893
#
894
895
datastr = ''
896
typestr = ''
897
898
temparr = []
899
900
# print_status "---------"
901
# print_status form.params
902
# print_status "+++++++++"
903
904
form.params.each do |p|
905
pn, pv, _pt = p
906
if pn
907
if !pn.empty?
908
if !pv || pv.empty?
909
# TODO: add value based on param name
910
pv = 'aaa'
911
end
912
913
# temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
914
temparr << pn.to_s + '=' + pv.to_s
915
end
916
else
917
print_error("Blank parameter name. Form #{form.path}")
918
end
919
end
920
921
datastr = temparr.join('&') if (temparr && !temparr.empty?)
922
923
if (utest_query.key?(signature(form.path, datastr)) == false)
924
925
modopts['METHOD'] = form.method.upcase
926
modopts['PATH'] = form.path
927
modopts['QUERY'] = form.query
928
if form.method.upcase == 'GET'
929
modopts['QUERY'] = datastr
930
modopts['DATA'] = ''
931
end
932
if form.method.upcase == 'POST'
933
modopts['DATA'] = datastr
934
end
935
modopts['TYPES'] = typestr
936
937
#
938
# TODO: Add headers, etc.
939
#
940
if !usinginipath || (usinginipath && form.path.match(inipathname))
941
print_status "Path #{form.path}"
942
943
# print_status("Unique PATH #{modopts['PATH']}")
944
# print_status("Unique GET #{modopts['QUERY']}")
945
# print_status("Unique POST #{modopts['DATA']}")
946
# print_status("MODOPTS: #{modopts}")
947
948
begin
949
if execmod
950
rpcnode = rpc_round_exec(xref[0], xref[1], modopts, njobs)
951
end
952
utest_query[signature(form.path, datastr)] = 1
953
rescue ::Exception
954
print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")
955
end
956
end
957
end
958
end
959
end
960
rescue ::Exception
961
print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")
962
end
963
end
964
965
#
966
# Run modules for each request to play with URI query parameters.
967
# This approach will reduce the complexity of the Tree used before
968
# and will make this shotgun implementation much simple.
969
# wmap_query
970
#
971
print_status "\n=[ Query testing ]="
972
print_line '=' * sizeline
973
974
idx = 0
975
matches2.each_key do |xref|
976
if masstop
977
print_error('STOPPED.')
978
return
979
end
980
981
# Module not part of profile or not match
982
next unless !(using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)
983
984
idx += 1
985
986
begin
987
# Module options hash
988
modopts = Hash.new
989
990
#
991
# The code is just a proof-of-concept and will be expanded in the future
992
#
993
994
print_status "Module #{xref[0]}"
995
996
if (mode & wmap_expl != 0)
997
998
#
999
# For modules to have access to the global datastore
1000
# i.e. set -g DOMAIN test.com
1001
#
1002
framework.datastore.each do |gkey, gval|
1003
modopts[gkey] = gval
1004
end
1005
1006
#
1007
# Parameters passed in hash xref
1008
#
1009
1010
modopts['RHOST'] = selected_host
1011
modopts['RHOSTS'] = selected_host
1012
modopts['RPORT'] = selected_port.to_s
1013
modopts['SSL'] = selected_ssl
1014
modopts['VHOST'] = selected_vhost.to_s
1015
modopts['VERBOSE'] = moduleverbose
1016
modopts['ShowProgress'] = showprogress
1017
modopts['RunAsJob'] = jobify
1018
1019
#
1020
# Run the plugins for each request that have a distinct
1021
# GET/POST URI QUERY string.
1022
#
1023
1024
h = framework.db.workspace.hosts.find_by_address(selected_host)
1025
s = h.services.find_by_port(selected_port)
1026
w = s.web_sites.find_by_vhost(selected_vhost)
1027
1028
w.web_forms.each do |req|
1029
if masstop
1030
print_error('STOPPED.')
1031
return
1032
end
1033
1034
datastr = ''
1035
typestr = ''
1036
1037
temparr = []
1038
1039
req.params.each do |p|
1040
pn, pv, _pt = p
1041
if pn
1042
if !pn.empty?
1043
if !pv || pv.empty?
1044
# TODO: add value based on param name
1045
pv = 'aaa'
1046
end
1047
# temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
1048
temparr << pn.to_s + '=' + pv.to_s
1049
end
1050
else
1051
print_error("Blank parameter name. Form #{req.path}")
1052
end
1053
end
1054
1055
datastr = temparr.join('&') if (temparr && !temparr.empty?)
1056
1057
modopts['METHOD'] = req.method.upcase
1058
modopts['PATH'] = req.path
1059
if req.method.upcase == 'GET'
1060
modopts['QUERY'] = datastr
1061
modopts['DATA'] = ''
1062
end
1063
modopts['DATA'] = datastr if req.method.upcase == 'POST'
1064
modopts['TYPES'] = typestr
1065
1066
#
1067
# TODO: Add method, headers, etc.
1068
#
1069
if !usinginipath || (usinginipath && req.path.match(inipathname))
1070
print_status "Path #{req.path}"
1071
1072
# print_status("Query PATH #{modopts['PATH']}")
1073
# print_status("Query GET #{modopts['QUERY']}")
1074
# print_status("Query POST #{modopts['DATA']}")
1075
# print_status("Query TYPES #{typestr}")
1076
1077
begin
1078
if execmod
1079
rpc_round_exec(xref[0], xref[1], modopts, njobs)
1080
end
1081
rescue ::Exception
1082
print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")
1083
end
1084
end
1085
end
1086
end
1087
rescue ::Exception
1088
print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")
1089
end
1090
end
1091
1092
#
1093
# Handle modules that need to be after all tests, once.
1094
# Good place to have modules that analyze the test results and/or
1095
# launch exploits.
1096
# :wmap_generic
1097
#
1098
print_status "\n=[ General testing ]="
1099
print_line '=' * sizeline
1100
1101
idx = 0
1102
matches10.each_key do |xref|
1103
if masstop
1104
print_error('STOPPED.')
1105
return
1106
end
1107
1108
# Module not part of profile or not match
1109
next unless !(using_p && eprofile.include?(xref[0].split('/').last)) || (using_m && xref[0].to_s.match(mname)) || (!using_m && !using_p)
1110
1111
idx += 1
1112
1113
begin
1114
# Module options hash
1115
modopts = Hash.new
1116
1117
#
1118
# The code is just a proof-of-concept and will be expanded in the future
1119
#
1120
1121
print_status "Module #{xref[0]}"
1122
1123
if (mode & wmap_expl != 0)
1124
1125
#
1126
# For modules to have access to the global datastore
1127
# i.e. set -g DOMAIN test.com
1128
#
1129
framework.datastore.each do |gkey, gval|
1130
modopts[gkey] = gval
1131
end
1132
1133
#
1134
# Parameters passed in hash xref
1135
#
1136
1137
modopts['RHOST'] = selected_host
1138
modopts['RHOSTS'] = selected_host
1139
modopts['RPORT'] = selected_port.to_s
1140
modopts['SSL'] = selected_ssl
1141
modopts['VHOST'] = selected_vhost.to_s
1142
modopts['VERBOSE'] = moduleverbose
1143
modopts['ShowProgress'] = showprogress
1144
modopts['RunAsJob'] = jobify
1145
1146
#
1147
# Run the plugins that only need to be
1148
# launched once.
1149
#
1150
1151
begin
1152
if execmod
1153
rpc_round_exec(xref[0], xref[1], modopts, njobs)
1154
end
1155
rescue ::Exception
1156
print_status(" >> Exception during launch from #{xref[0]}: #{$ERROR_INFO}")
1157
end
1158
end
1159
rescue ::Exception
1160
print_status(" >> Exception from #{xref[0]}: #{$ERROR_INFO}")
1161
end
1162
end
1163
1164
if (mode & wmap_expl != 0)
1165
print_line '+' * sizeline
1166
1167
if !(runlocal && execmod)
1168
rpc_list_nodes
1169
print_status('Note: Use wmap_nodes -l to list node status for completion')
1170
end
1171
1172
print_line("Launch completed in #{Time.now.to_f - stamp} seconds.")
1173
print_line '+' * sizeline
1174
end
1175
1176
print_status('Done.')
1177
end
1178
1179
# EOM
1180
end
1181
1182
def view_targets
1183
if targets.nil? || targets.keys.empty?
1184
print_status 'No targets have been defined'
1185
return
1186
end
1187
1188
indent = ' '
1189
1190
tbl = Rex::Text::Table.new(
1191
'Indent' => indent.length,
1192
'Header' => 'Defined targets',
1193
'Columns' =>
1194
[
1195
'Id',
1196
'Vhost',
1197
'Host',
1198
'Port',
1199
'SSL',
1200
'Path',
1201
]
1202
)
1203
1204
targets.each_with_index do |t, idx|
1205
tbl << [ idx.to_s, t[1][:vhost], t[1][:host], t[1][:port], t[1][:ssl], "\t" + t[1][:path].to_s ]
1206
end
1207
1208
print_status tbl.to_s + "\n"
1209
end
1210
1211
def delete_sites(wmap_index)
1212
idx = 0
1213
to_del = {}
1214
# Rebuild the index from wmap_sites -l
1215
framework.db.hosts.each do |bdhost|
1216
bdhost.services.each do |serv|
1217
serv.web_sites.each do |web|
1218
# If the index of this site matches any deletion index,
1219
# add to our hash, saving the index for later output
1220
to_del[idx] = web if wmap_index.any? { |w| w.to_i == idx }
1221
idx += 1
1222
end
1223
end
1224
end
1225
to_del.each do |widx, wsite|
1226
if wsite.delete
1227
print_status("Deleted #{wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
1228
else
1229
print_error("Could note delete {wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
1230
end
1231
end
1232
end
1233
1234
def view_sites
1235
# Clean temporary sites list
1236
self.lastsites = []
1237
1238
indent = ' '
1239
1240
tbl = Rex::Text::Table.new(
1241
'Indent' => indent.length,
1242
'Header' => 'Available sites',
1243
'Columns' =>
1244
[
1245
'Id',
1246
'Host',
1247
'Vhost',
1248
'Port',
1249
'Proto',
1250
'# Pages',
1251
'# Forms',
1252
]
1253
)
1254
1255
idx = 0
1256
framework.db.hosts.each do |bdhost|
1257
bdhost.services.each do |serv|
1258
serv.web_sites.each do |web|
1259
c = web.web_pages.count
1260
f = web.web_forms.count
1261
tbl << [ idx.to_s, bdhost.address, web.vhost, serv.port, serv.name, c.to_s, f.to_s ]
1262
idx += 1
1263
1264
turl = web.vhost + ',' + serv.name + '://' + bdhost.address.to_s + ':' + serv.port.to_s + '/'
1265
lastsites << turl
1266
end
1267
end
1268
end
1269
1270
print_status tbl.to_s + "\n"
1271
end
1272
1273
# Reusing code from hdmoore
1274
#
1275
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
1276
# should be used. This allows for things like:
1277
# localhost,http://192.168.0.2/admin/
1278
1279
def add_web_site(url)
1280
vhost = nil
1281
1282
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
1283
# should be used. This allows for things like:
1284
# localhost,http://192.168.0.2/admin/
1285
1286
if url !~ /^http/
1287
vhost, url = url.split(',', 2)
1288
if url.to_s.empty?
1289
url = vhost
1290
vhost = nil
1291
end
1292
end
1293
1294
# Prefix http:// when the URL has no specified parameter
1295
if url !~ %r{^[a-z0-9A-Z]+://}
1296
url = 'http://' + url
1297
end
1298
1299
uri = begin
1300
URI.parse(url)
1301
rescue StandardError
1302
nil
1303
end
1304
if !uri
1305
print_error("Could not understand URL: #{url}")
1306
return
1307
end
1308
1309
vhost = uri.hostname if vhost.nil?
1310
1311
if uri.scheme !~ /^https?/
1312
print_error("Only http and https URLs are accepted: #{url}")
1313
return
1314
end
1315
1316
ssl = false
1317
if uri.scheme == 'https'
1318
ssl = true
1319
end
1320
1321
site = begin
1322
framework.db.report_web_site(wait: true, host: uri.host, port: uri.port, vhost: vhost, ssl: ssl, workspace: framework.db.workspace)
1323
rescue SocketError => e
1324
elog("Could not get address for #{uri.host}", 'wmap', error: e)
1325
print_status("Could not get address for #{uri.host}.")
1326
nil
1327
end
1328
1329
return site
1330
end
1331
1332
# Code by hdm. Modified two lines by et
1333
#
1334
def process_urls(urlstr)
1335
target_whitelist = []
1336
1337
urls = urlstr.to_s.split(/\s+/)
1338
1339
urls.each do |url|
1340
next if url.to_s.strip.empty?
1341
1342
vhost = nil
1343
1344
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
1345
# should be used. This allows for things like:
1346
# localhost,http://192.168.0.2/admin/
1347
1348
if url !~ /^http/
1349
vhost, url = url.split(',', 2)
1350
if url.to_s.empty?
1351
url = vhost
1352
vhost = nil
1353
end
1354
end
1355
1356
# Prefix http:// when the URL has no specified parameter
1357
if url !~ %r{^[a-z0-9A-Z]+://}
1358
url = 'http://' + url
1359
end
1360
1361
uri = begin
1362
URI.parse(url)
1363
rescue StandardError
1364
nil
1365
end
1366
if !uri
1367
print_error("Could not understand URL: #{url}")
1368
next
1369
end
1370
1371
if uri.scheme !~ /^https?/
1372
print_error("Only http and https URLs are accepted: #{url}")
1373
next
1374
end
1375
1376
target_whitelist << [vhost || uri.host, uri]
1377
end
1378
1379
# Skip the DB entirely if no matches
1380
return if target_whitelist.empty?
1381
1382
if !targets
1383
# First time targets are defined
1384
self.targets = Hash.new
1385
end
1386
1387
target_whitelist.each do |ent|
1388
vhost, target = ent
1389
1390
begin
1391
address = Rex::Socket.getaddress(target.host, true)
1392
rescue SocketError => e
1393
elog("Could not get address for #{target.host}", 'wmap', error: e)
1394
print_status("Could not get address for #{target.host}. Skipping.")
1395
next
1396
end
1397
1398
host = framework.db.workspace.hosts.find_by_address(address)
1399
if !host
1400
print_error("No matching host for #{target.host}")
1401
next
1402
end
1403
serv = host.services.find_by_port_and_proto(target.port, 'tcp')
1404
if !serv
1405
print_error("No matching service for #{target.host}:#{target.port}")
1406
next
1407
end
1408
1409
sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)
1410
1411
sites.each do |site|
1412
# Initial default path
1413
inipath = target.path
1414
if target.path.empty?
1415
inipath = '/'
1416
end
1417
1418
# site.web_forms.where(path: target.path).each do |form|
1419
ckey = [ site.vhost, host.address, serv.port, inipath].join('|')
1420
1421
if !targets[ckey]
1422
targets[ckey] = WebTarget.new
1423
targets[ckey].merge!({
1424
vhost: site.vhost,
1425
host: host.address,
1426
port: serv.port,
1427
ssl: (serv.name == 'https'),
1428
path: inipath
1429
})
1430
# self.targets[ckey][inipath] = []
1431
else
1432
print_status('Target already set in targets list.')
1433
end
1434
1435
# Store the form object in the hash for this path
1436
# self.targets[ckey][inipath] << inipath
1437
# end
1438
end
1439
end
1440
end
1441
1442
# Code by hdm. Modified two lines by et
1443
# lastsites contains a temporary array with vhost,url strings so the id can be
1444
# referenced in the array and prevent new sites added in the db to corrupt previous id list.
1445
def process_ids(idsstr)
1446
if !lastsites || lastsites.empty?
1447
view_sites
1448
print_status('Web sites ids. referenced from previous table.')
1449
end
1450
1451
target_whitelist = []
1452
ids = idsstr.to_s.split(/,/)
1453
1454
ids.each do |id|
1455
next if id.to_s.strip.empty?
1456
1457
if id.to_i > lastsites.length
1458
print_error("Skipping id #{id}...")
1459
else
1460
target_whitelist << lastsites[id.to_i]
1461
print_status("Loading #{lastsites[id.to_i]}.")
1462
end
1463
end
1464
1465
# Skip the DB entirely if no matches
1466
return if target_whitelist.empty?
1467
1468
if !targets
1469
self.targets = Hash.new
1470
end
1471
1472
target_whitelist.each do |ent|
1473
process_urls(ent)
1474
end
1475
end
1476
1477
def view_site_tree(urlstr, md, ld)
1478
if !urlstr
1479
return
1480
end
1481
1482
site_whitelist = []
1483
1484
urls = urlstr.to_s.split(/\s+/)
1485
1486
urls.each do |url|
1487
next if url.to_s.strip.empty?
1488
1489
vhost = nil
1490
1491
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
1492
# should be used. This allows for things like:
1493
# localhost,http://192.168.0.2/admin/
1494
1495
if url !~ /^http/
1496
vhost, url = url.split(',', 2)
1497
1498
if url.to_s.empty?
1499
url = vhost
1500
vhost = nil
1501
end
1502
end
1503
1504
# Prefix http:// when the URL has no specified parameter
1505
if url !~ %r{^[a-z0-9A-Z]+://}
1506
url = 'http://' + url
1507
end
1508
1509
uri = begin
1510
URI.parse(url)
1511
rescue StandardError
1512
nil
1513
end
1514
if !uri
1515
print_error("Could not understand URL: #{url}")
1516
next
1517
end
1518
1519
if uri.scheme !~ /^https?/
1520
print_error("Only http and https URLs are accepted: #{url}")
1521
next
1522
end
1523
1524
site_whitelist << [vhost || uri.host, uri]
1525
end
1526
1527
# Skip the DB entirely if no matches
1528
return if site_whitelist.empty?
1529
1530
site_whitelist.each do |ent|
1531
vhost, target = ent
1532
1533
host = framework.db.workspace.hosts.find_by_address(target.host)
1534
unless host
1535
print_error("No matching host for #{target.host}")
1536
next
1537
end
1538
serv = host.services.find_by_port_and_proto(target.port, 'tcp')
1539
unless serv
1540
print_error("No matching service for #{target.host}:#{target.port}")
1541
next
1542
end
1543
1544
sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)
1545
1546
sites.each do |site|
1547
t = load_tree(site)
1548
print_tree(t, target.host, md, ld)
1549
print_line("\n")
1550
end
1551
end
1552
end
1553
1554
# Private function to avoid duplicate code
1555
def load_tree_core(req, wtree)
1556
pathchr = '/'
1557
tarray = req.path.to_s.split(pathchr)
1558
tarray.delete('')
1559
tpath = Pathname.new(pathchr)
1560
tarray.each do |df|
1561
wtree.add_at_path(tpath.to_s, df)
1562
tpath += Pathname.new(df.to_s)
1563
end
1564
end
1565
1566
#
1567
# Load website structure into a tree
1568
#
1569
def load_tree(s)
1570
wtree = Tree.new(s.vhost)
1571
1572
# Load site pages
1573
s.web_pages.order('path asc').each do |req|
1574
if req.code != 404
1575
load_tree_core(req, wtree)
1576
end
1577
end
1578
1579
# Load site forms
1580
s.web_forms.each do |req|
1581
load_tree_core(req, wtree)
1582
end
1583
1584
wtree
1585
end
1586
1587
def print_file(filename)
1588
ext = File.extname(filename)
1589
if %w[.txt .md].include? ext
1590
print '%bld%red'
1591
elsif %w[.css .js].include? ext
1592
print '%grn'
1593
end
1594
1595
print_line("#{filename}%clr")
1596
end
1597
1598
#
1599
# Recursive function for printing the tree structure
1600
#
1601
def print_tree_recursive(tree, max_level, indent, prefix, is_last, unicode)
1602
if !tree.nil? && (tree.depth <= max_level)
1603
print(' ' * indent)
1604
1605
# Prefix serve to print the superior hierarchy
1606
prefix.each do |bool|
1607
if unicode
1608
print (bool ? ' ' : '│') + (' ' * 3)
1609
else
1610
print (bool ? ' ' : '|') + (' ' * 3)
1611
end
1612
end
1613
if unicode
1614
# The last children is special
1615
print (is_last ? '└' : '├') + ('─' * 2) + ' '
1616
else
1617
print (is_last ? '`' : '|') + ('-' * 2) + ' '
1618
end
1619
1620
c = tree.children.count
1621
1622
if c > 0
1623
print_line "%bld%blu#{tree.name}%clr (#{c})"
1624
else
1625
print_file tree.name
1626
end
1627
1628
i = 1
1629
new_prefix = prefix + [is_last]
1630
tree.children.each_pair do |_, child|
1631
is_last = i >= c
1632
print_tree_recursive(child, max_level, indent, new_prefix, is_last, unicode)
1633
i += 1
1634
end
1635
end
1636
end
1637
1638
#
1639
# Print Tree structure. Less ugly
1640
# Modified by Jon P.
1641
#
1642
def print_tree(tree, ip, max_level, unicode)
1643
indent = 4
1644
if !tree.nil? && (tree.depth <= max_level)
1645
if tree.depth == 0
1646
print_line "\n" + (' ' * indent) + "%cya[#{tree.name}] (#{ip})%clr"
1647
end
1648
1649
i = 1
1650
c = tree.children.count
1651
tree.children.each_pair do |_, child|
1652
print_tree_recursive(child, max_level, indent, [], i >= c, unicode)
1653
i += 1
1654
end
1655
1656
end
1657
end
1658
1659
#
1660
# Signature of the form ',p1,p2,pn' then to be appended to path: path,p1,p2,pn
1661
#
1662
def signature(fpath, fquery)
1663
hsig = queryparse(fquery)
1664
fpath + ',' + hsig.map { |p| p[0].to_s }.join(',')
1665
end
1666
1667
def queryparse(query)
1668
params = Hash.new
1669
1670
query.split(/[&;]/n).each do |pairs|
1671
key, value = pairs.split('=', 2)
1672
if params.key?(key)
1673
# Error
1674
else
1675
params[key] = value
1676
end
1677
end
1678
params
1679
end
1680
1681
def rpc_add_node(host, port, ssl, user, pass, bypass_exist)
1682
if !rpcarr
1683
self.rpcarr = Hash.new
1684
end
1685
1686
istr = "#{host}|#{port}|#{ssl}|#{user}|#{pass}"
1687
1688
if rpcarr.key?(istr) && !bypass_exist && !rpcarr[istr].nil?
1689
print_error("Connection already exists #{istr}")
1690
return
1691
end
1692
1693
begin
1694
temprpc = ::Msf::RPC::Client.new(
1695
host: host,
1696
port: port,
1697
ssl: ssl
1698
)
1699
rescue StandardError
1700
print_error 'Unable to connect'
1701
# raise ConnectionError
1702
return
1703
end
1704
1705
res = temprpc.login(user, pass)
1706
1707
if !res
1708
print_error("Unable to authenticate to #{host}:#{port}.")
1709
return
1710
end
1711
1712
res = temprpc.call('core.version')
1713
print_status("Connected to #{host}:#{port} [#{res['version']}].")
1714
rpcarr[istr] = temprpc
1715
rescue StandardError
1716
print_error('Unable to connect')
1717
end
1718
1719
def local_module_exec(mod, mtype, opts, _nmaxjobs)
1720
jobify = false
1721
1722
modinst = framework.modules.create(mod)
1723
1724
if !modinst
1725
print_error('Unknown module')
1726
return
1727
end
1728
1729
sess = nil
1730
1731
case mtype
1732
when 'auxiliary'
1733
Msf::Simple::Auxiliary.run_simple(modinst, {
1734
'Action' => opts['ACTION'],
1735
'LocalOutput' => driver.output,
1736
'RunAsJob' => jobify,
1737
'Options' => opts
1738
})
1739
when 'exploit'
1740
if !(opts['PAYLOAD'])
1741
opts['PAYLOAD'] = WmapCommandDispatcher::Exploit.choose_payload(modinst, opts['TARGET'])
1742
end
1743
1744
sess = Msf::Simple::Exploit.exploit_simple(modinst, {
1745
'Payload' => opts['PAYLOAD'],
1746
'Target' => opts['TARGET'],
1747
'LocalOutput' => driver.output,
1748
'RunAsJob' => jobify,
1749
'Options' => opts
1750
})
1751
else
1752
print_error('Wrong mtype.')
1753
end
1754
1755
if sess
1756
if ((jobify == false) && sess.interactive?)
1757
print_line
1758
driver.run_single("sessions -q -i #{sess.sid}")
1759
else
1760
print_status("Session #{sess.sid} created in the background.")
1761
end
1762
end
1763
end
1764
1765
def rpc_round_exec(mod, mtype, opts, nmaxjobs)
1766
res = nil
1767
idx = 0
1768
1769
if active_rpc_nodes == 0
1770
if !runlocal
1771
print_error('All active nodes not working or removed')
1772
return
1773
end
1774
res = true
1775
else
1776
rpc_reconnect_nodes
1777
end
1778
1779
if masstop
1780
return
1781
end
1782
1783
until res
1784
if active_rpc_nodes == 0
1785
print_error('All active nodes not working or removed')
1786
return
1787
end
1788
1789
# find the node with less jobs load.
1790
minjobs = nmaxjobs
1791
minconn = nil
1792
nid = 0
1793
rpcarr.each do |k, rpccon|
1794
if !rpccon
1795
print_error("Skipping inactive node #{nid} #{k}")
1796
nid += 1
1797
end
1798
1799
begin
1800
currentjobs = rpccon.call('job.list').length
1801
1802
if currentjobs < minjobs
1803
minconn = rpccon
1804
minjobs = currentjobs
1805
end
1806
1807
if currentjobs == nmaxjobs && (nmaxdisplay == false)
1808
print_error("Node #{nid} reached max number of jobs #{nmaxjobs}")
1809
print_error('Waiting for available node/slot...')
1810
self.nmaxdisplay = true
1811
end
1812
# print_status("Node #{nid} #currentjobs #{currentjobs} #min #{minjobs}")
1813
rescue StandardError
1814
print_error("Unable to connect. Node #{tarr[0]}:#{tarr[1]}")
1815
rpcarr[k] = nil
1816
1817
if active_rpc_nodes == 0
1818
print_error('All active nodes, not working or removed')
1819
return
1820
else
1821
print_error('Sending job to next node')
1822
next
1823
end
1824
end
1825
1826
nid += 1
1827
end
1828
1829
if minjobs < nmaxjobs
1830
res = minconn.call('module.execute', mtype, mod, opts)
1831
self.nmaxdisplay = false
1832
# print_status(">>>#{res} #{mod}")
1833
1834
if res
1835
if res.key?('job_id')
1836
return
1837
else
1838
print_error("Unable to execute module in node #{k} #{res}")
1839
end
1840
end
1841
end
1842
1843
# print_status("Max number of jobs #{nmaxjobs} reached in node #{k}") if minjobs >= nmaxjobs
1844
1845
idx += 1
1846
end
1847
1848
if runlocal && !masstop
1849
local_module_exec(mod, mtype, opts, nmaxjobs)
1850
end
1851
end
1852
1853
def rpc_db_nodes(host, port, user, pass, name)
1854
rpc_reconnect_nodes
1855
1856
if active_rpc_nodes == 0
1857
print_error('No active nodes at this time')
1858
return
1859
end
1860
1861
rpcarr.each do |k, v|
1862
if v
1863
v.call('db.driver', { driver: 'postgresql' })
1864
v.call('db.connect', { database: name, host: host, port: port, username: user, password: pass })
1865
1866
res = v.call('db.status')
1867
1868
if res['db'] == name
1869
print_status("db_connect #{res} #{host}:#{port} OK")
1870
else
1871
print_error("Error db_connect #{res} #{host}:#{port}")
1872
end
1873
else
1874
print_error("No connection to node #{k}")
1875
end
1876
end
1877
end
1878
1879
def rpc_reconnect_nodes
1880
# Sucky 5 mins token timeout.
1881
1882
idx = nil
1883
rpcarr.each do |k, rpccon|
1884
next unless rpccon
1885
1886
idx = k
1887
begin
1888
rpccon.call('job.list').length
1889
rescue StandardError
1890
tarr = k.split('|')
1891
1892
res = rpccon.login(tarr[3], tarr[4])
1893
1894
raise ConnectionError unless res
1895
1896
print_error("Reauth to node #{tarr[0]}:#{tarr[1]}")
1897
break
1898
end
1899
end
1900
rescue StandardError
1901
print_error("ERROR CONNECTING TO NODE. Disabling #{idx} use wmap_nodes -a to reconnect")
1902
rpcarr[idx] = nil
1903
if active_rpc_nodes == 0
1904
print_error('No active nodes')
1905
self.masstop = true
1906
end
1907
end
1908
1909
def rpc_kill_node(i, j)
1910
if !i
1911
print_error('Nodes not defined')
1912
return
1913
end
1914
1915
if !j
1916
print_error('Node jobs defined')
1917
return
1918
end
1919
1920
rpc_reconnect_nodes
1921
1922
if active_rpc_nodes == 0
1923
print_error('No active nodes at this time')
1924
return
1925
end
1926
1927
idx = 0
1928
rpcarr.each do |_k, rpccon|
1929
if (idx == i.to_i) || (i.upcase == 'ALL')
1930
# begin
1931
if !rpccon
1932
print_error("No connection to node #{idx}")
1933
else
1934
n = rpccon.call('job.list')
1935
n.each do |id, name|
1936
if (j == id.to_s) || (j.upcase == 'ALL')
1937
rpccon.call('job.stop', id)
1938
print_status("Node #{idx} Killed job id #{id} #{name}")
1939
end
1940
end
1941
end
1942
# rescue
1943
# print_error("No connection")
1944
# end
1945
end
1946
idx += 1
1947
end
1948
end
1949
1950
def rpc_view_jobs
1951
indent = ' '
1952
1953
rpc_reconnect_nodes
1954
1955
if active_rpc_nodes == 0
1956
print_error('No active nodes at this time')
1957
return
1958
end
1959
1960
idx = 0
1961
rpcarr.each do |k, rpccon|
1962
if !rpccon
1963
print_status("[Node ##{idx}: #{k} DISABLED/NO CONNECTION]")
1964
else
1965
1966
arrk = k.split('|')
1967
print_status("[Node ##{idx}: #{arrk[0]} Port:#{arrk[1]} SSL:#{arrk[2]} User:#{arrk[3]}]")
1968
1969
begin
1970
n = rpccon.call('job.list')
1971
1972
tbl = Rex::Text::Table.new(
1973
'Indent' => indent.length,
1974
'Header' => 'Jobs',
1975
'Columns' =>
1976
[
1977
'Id',
1978
'Job name',
1979
'Target',
1980
'PATH',
1981
]
1982
)
1983
1984
n.each do |id, name|
1985
jinfo = rpccon.call('job.info', id)
1986
dstore = jinfo['datastore']
1987
tbl << [ id.to_s, name, dstore['VHOST'] + ':' + dstore['RPORT'], dstore['PATH']]
1988
end
1989
1990
print_status tbl.to_s + "\n"
1991
rescue StandardError
1992
print_status("[Node ##{idx} #{k} DISABLED/NO CONNECTION]")
1993
end
1994
end
1995
idx += 1
1996
end
1997
end
1998
1999
# Modified from http://stackoverflow.com/questions/946738/detect-key-press-non-blocking-w-o-getc-gets-in-ruby
2000
def quit?
2001
while (c = driver.input.read_nonblock(1))
2002
print_status('Quited')
2003
return true if c == 'Q'
2004
end
2005
false
2006
rescue Errno::EINTR
2007
false
2008
rescue Errno::EAGAIN
2009
false
2010
rescue EOFError
2011
true
2012
end
2013
2014
def rpc_mon_nodes
2015
# Pretty monitor
2016
2017
color = begin
2018
opts['ConsoleDriver'].output.supports_color?
2019
rescue StandardError
2020
false
2021
end
2022
2023
colors = [
2024
'%grn',
2025
'%blu',
2026
'%yel',
2027
'%whi'
2028
]
2029
2030
# begin
2031
loop do
2032
rpc_reconnect_nodes
2033
2034
idx = 0
2035
rpcarr.each do |_k, rpccon|
2036
v = 'NOCONN'
2037
n = 1
2038
c = '%red'
2039
2040
if !rpccon
2041
v = 'NOCONN'
2042
n = 1
2043
c = '%red'
2044
else
2045
begin
2046
v = ''
2047
c = '%blu'
2048
rescue StandardError
2049
v = 'ERROR'
2050
c = '%red'
2051
end
2052
2053
begin
2054
n = rpccon.call('job.list').length
2055
c = '%blu'
2056
rescue StandardError
2057
n = 1
2058
v = 'NOCONN'
2059
c = '%red'
2060
end
2061
end
2062
2063
# begin
2064
if !@stdio
2065
@stdio = Rex::Ui::Text::Output::Stdio.new
2066
end
2067
2068
if color == true
2069
@stdio.auto_color
2070
else
2071
@stdio.disable_color
2072
end
2073
msg = "[#{idx}] #{"%bld#{c}||%clr" * n} #{n} #{v}\n"
2074
@stdio.print_raw(@stdio.substitute_colors(msg))
2075
2076
# rescue
2077
# blah
2078
# end
2079
sleep(2)
2080
idx += 1
2081
end
2082
end
2083
# rescue
2084
# print_status("End.")
2085
# end
2086
end
2087
2088
def rpc_list_nodes
2089
indent = ' '
2090
2091
tbl = Rex::Text::Table.new(
2092
'Indent' => indent.length,
2093
'Header' => 'Nodes',
2094
'Columns' =>
2095
[
2096
'Id',
2097
'Host',
2098
'Port',
2099
'SSL',
2100
'User',
2101
'Pass',
2102
'Status',
2103
'#jobs',
2104
]
2105
)
2106
2107
idx = 0
2108
2109
rpc_reconnect_nodes
2110
2111
rpcarr.each do |k, rpccon|
2112
arrk = k.split('|')
2113
2114
if !rpccon
2115
v = 'NOCONN'
2116
n = ''
2117
else
2118
begin
2119
v = rpccon.call('core.version')['version']
2120
rescue StandardError
2121
v = 'ERROR'
2122
end
2123
2124
begin
2125
n = rpccon.call('job.list').length
2126
rescue StandardError
2127
n = ''
2128
end
2129
end
2130
2131
tbl << [ idx.to_s, arrk[0], arrk[1], arrk[2], arrk[3], arrk[4], v, n]
2132
idx += 1
2133
end
2134
2135
print_status tbl.to_s + "\n"
2136
end
2137
2138
def active_rpc_nodes
2139
return 0 if rpcarr.empty?
2140
2141
idx = 0
2142
rpcarr.each do |_k, conn|
2143
if conn
2144
idx += 1
2145
end
2146
end
2147
2148
idx
2149
end
2150
2151
def view_modules
2152
indent = ' '
2153
2154
wmaptype = %i[
2155
wmap_ssl
2156
wmap_server
2157
wmap_dir
2158
wmap_file
2159
wmap_unique_query
2160
wmap_query
2161
wmap_generic
2162
]
2163
2164
if !wmapmodules
2165
load_wmap_modules(true)
2166
end
2167
2168
wmaptype.each do |modt|
2169
tbl = Rex::Text::Table.new(
2170
'Indent' => indent.length,
2171
'Header' => modt.to_s,
2172
'Columns' =>
2173
[
2174
'Name',
2175
'OrderID',
2176
]
2177
)
2178
2179
idx = 0
2180
wmapmodules.each do |w|
2181
oid = w[3]
2182
if w[3] == 0xFFFFFF
2183
oid = ':last'
2184
end
2185
2186
if w[2] == modt
2187
tbl << [w[0], oid]
2188
idx += 1
2189
end
2190
end
2191
2192
print_status tbl.to_s + "\n"
2193
end
2194
end
2195
2196
# Sort hash by orderid
2197
# Yes sorting hashes dont make sense but actually it does when you are enumerating one. And
2198
# sort_by of a hash returns an array so this is the reason for this ugly piece of code
2199
def sort_by_orderid(matches)
2200
temphash = Hash.new
2201
2202
temparr = matches.sort_by do |xref, _v|
2203
xref[3]
2204
end
2205
2206
temparr.each do |b|
2207
temphash[b[0]] = b[1]
2208
end
2209
2210
temphash
2211
end
2212
2213
# Load all wmap modules
2214
def load_wmap_modules(reload)
2215
if reload || !wmapmodules
2216
print_status('Loading wmap modules...')
2217
2218
self.wmapmodules = []
2219
2220
idx = 0
2221
[ [ framework.auxiliary, 'auxiliary' ], [framework.exploits, 'exploit' ] ].each do |mtype|
2222
# Scan all exploit modules for matching references
2223
mtype[0].each_module do |n, m|
2224
e = m.new
2225
2226
# Only include wmap_enabled plugins
2227
next unless e.respond_to?('wmap_enabled')
2228
2229
penabled = e.wmap_enabled
2230
2231
if penabled
2232
wmapmodules << [mtype[1] + '/' + n, mtype[1], e.wmap_type, e.orderid]
2233
idx += 1
2234
end
2235
end
2236
end
2237
print_status("#{idx} wmap enabled modules loaded.")
2238
end
2239
end
2240
2241
def view_vulns
2242
framework.db.hosts.each do |host|
2243
host.services.each do |serv|
2244
serv.web_sites.each do |site|
2245
site.web_vulns.each do |wv|
2246
print_status("+ [#{host.address}] (#{site.vhost}): #{wv.category} #{wv.path}")
2247
print_status("\t#{wv.name} #{wv.description}")
2248
print_status("\t#{wv.method} #{wv.proof}")
2249
end
2250
end
2251
end
2252
end
2253
end
2254
end
2255
2256
class WebTarget < ::Hash
2257
def to_url
2258
proto = self[:ssl] ? 'https' : 'http'
2259
"#{proto}://#{self[:host]}:#{self[:port]}#{self[:path]}"
2260
end
2261
end
2262
2263
def initialize(framework, opts)
2264
super
2265
2266
if framework.db.active == false
2267
raise 'Database not connected (try db_connect)'
2268
end
2269
2270
color = begin
2271
self.opts['ConsoleDriver'].output.supports_color?
2272
rescue StandardError
2273
false
2274
end
2275
2276
wmapversion = '1.5.1'
2277
2278
wmapbanner = "%red\n.-.-.-..-.-.-..---..---.%clr\n"
2279
wmapbanner += "%red| | | || | | || | || |-'%clr\n"
2280
wmapbanner += "%red`-----'`-'-'-'`-^-'`-'%clr\n"
2281
wmapbanner += "[WMAP #{wmapversion}] === et [ ] metasploit.com 2012\n"
2282
2283
if !@stdio
2284
@stdio = Rex::Ui::Text::Output::Stdio.new
2285
end
2286
2287
if color == true
2288
@stdio.auto_color
2289
else
2290
@stdio.disable_color
2291
end
2292
2293
@stdio.print_raw(@stdio.substitute_colors(wmapbanner))
2294
2295
add_console_dispatcher(WmapCommandDispatcher)
2296
# print_status("#{wmapbanner}")
2297
end
2298
2299
def cleanup
2300
remove_console_dispatcher('wmap')
2301
end
2302
2303
def name
2304
'wmap'
2305
end
2306
2307
def desc
2308
'Web assessment plugin'
2309
end
2310
2311
end
2312
end
2313
2314