@@ -24,6 +24,7 @@ module Parser
24
24
def start_document
25
25
@parse_warnings = [ ]
26
26
@resolv_cache = { }
27
+ @host_object = nil
27
28
end
28
29
29
30
def start_element ( name = nil , attrs = [ ] )
@@ -32,9 +33,12 @@ def start_element(name=nil,attrs=[])
32
33
@state [ :current_tag ] [ name ] = true
33
34
case name
34
35
when "Scan" # Start of the thing.
35
- when "Name" , "StartURL" , "Banner" , "Os"
36
+ @state [ :report_item ] = { }
37
+ when "Name" , "StartURL" , "StartTime" , "Banner" , "Os" , "Text" , "Severity" , "CWE" , "URL" , "Parameter"
36
38
@state [ :has_text ] = true
37
39
when "LoginSequence" # Skipping for now
40
+ when "ReportItem"
41
+ @state [ :report_item ] = { }
38
42
when "Crawler"
39
43
record_crawler ( attrs )
40
44
when "FullURL"
@@ -62,14 +66,56 @@ def end_element(name=nil)
62
66
# StartURL does not always include the scheme
63
67
@text . prepend ( "http://" ) unless URI . parse ( @text ) . scheme
64
68
collect_host
65
- collect_service
69
+ collect_service_from_url
66
70
@text = nil
67
71
handle_parse_warnings &block
68
- host_object = report_host &block
69
- if host_object
70
- report_starturl_service ( host_object , &block )
71
- db . report_import_note ( @args [ :wspace ] , host_object )
72
+ @ host_object = report_host &block
73
+ if @ host_object
74
+ report_starturl_service ( &block )
75
+ db . report_import_note ( @args [ :wspace ] , @ host_object)
72
76
end
77
+ when "StartTime"
78
+ @state [ :has_text ] = false
79
+ @state [ :timestamp ] = @text . to_s . tr! ( ',' , '' ) . tr! ( '/' , '-' )
80
+ @text = nil
81
+ when "Text"
82
+ @state [ :has_text ] = false
83
+ service = collect_service_from_kbitem_text
84
+ @text = nil
85
+ return unless service
86
+ handle_parse_warnings &block
87
+ if @host_object
88
+ report_kbitem_service ( service , &block )
89
+ end
90
+ when "Severity"
91
+ @state [ :has_text ] = false
92
+ collect_report_item_severity
93
+ @text = nil
94
+ when "CWE"
95
+ @state [ :has_text ] = false
96
+ collect_report_item_cwe
97
+ @text = nil
98
+ when "URL"
99
+ @state [ :has_text ] = false
100
+ collect_report_item_reference_url
101
+ @text = nil
102
+ when "Parameter"
103
+ @state [ :has_text ] = false
104
+ collect_report_item_parameter
105
+ @text = nil
106
+ when "ReportItem"
107
+ vuln = collect_vuln_from_report_item
108
+ if vuln . nil?
109
+ @state [ :page_request ] = @state [ :page_response ] = nil
110
+ return
111
+ end
112
+ handle_parse_warnings &block
113
+ if @state [ :vuln_info ] [ :refs ] . nil?
114
+ report_web_vuln ( &block )
115
+ else
116
+ report_other_vuln ( &block )
117
+ end
118
+ @state [ :page_request ] = @state [ :page_response ] = nil
73
119
when "Banner"
74
120
@state [ :has_text ] = false
75
121
collect_and_report_banner
@@ -134,7 +180,7 @@ def collect_host
134
180
@report_data [ :state ] = Msf ::HostState ::Alive
135
181
end
136
182
137
- def collect_service
183
+ def collect_service_from_url
138
184
return unless @report_data [ :host ]
139
185
return unless in_tag ( "Scan" )
140
186
return unless @text
@@ -146,6 +192,44 @@ def collect_service
146
192
@report_data [ :ports ] << @state [ :starturl_port ]
147
193
end
148
194
195
+ def collect_service_from_kbitem_text
196
+ return unless @host_object
197
+ return unless in_tag ( "Scan" )
198
+ return unless in_tag ( "KBase" )
199
+ return unless in_tag ( "KBItem" )
200
+ return unless @text
201
+ return if @text . strip . empty?
202
+ return unless @text =~ /server is running/
203
+ matched = / (?<name>\w +) server is running on (?<proto>\w +) port (?<portnum>\d +)\. / . match ( @text )
204
+ @report_data [ :ports ] ||= [ ]
205
+ @report_data [ :ports ] << matched [ :portnum ]
206
+ return matched
207
+ end
208
+
209
+ def collect_vuln_from_report_item
210
+ @state [ :vuln_info ] = nil
211
+ return unless @host_object
212
+ return unless in_tag ( "Scan" )
213
+ return unless in_tag ( "ReportItems" )
214
+ return unless in_tag ( "ReportItem" )
215
+ return unless @state [ :report_item ] [ :name ]
216
+ return unless @state [ :report_item ] [ :severity ]
217
+ return unless @state [ :report_item ] [ :severity ] . downcase == "high"
218
+
219
+ @state [ :vuln_info ] = { }
220
+ @state [ :vuln_info ] [ :name ] = @state [ :report_item ] [ :name ]
221
+ if @state [ :page_request_verb ] . nil? && @state [ :report_item ] [ :name ] =~ /deprecated/
222
+ # Treating this as a regular vuln, not web-specific
223
+ @state [ :vuln_info ] [ :refs ] = [ "ACX-#{ @state [ :report_item ] [ :reference_url ] } " ]
224
+ unless @state [ :report_item_cwe ] . nil?
225
+ @state [ :vuln_info ] [ :refs ] [ 0 ] << ",#{ @state [ :report_item ] [ :cwe ] } "
226
+ end
227
+ end
228
+ @state [ :vuln_info ] [ :severity ] = @state [ :report_item ] [ :severity ] . downcase
229
+ @state [ :vuln_info ] [ :cwe ] = @state [ :report_item ] [ :cwe ]
230
+ return @state [ :vuln_info ]
231
+ end
232
+
149
233
def collect_and_report_banner
150
234
return unless ( svc = @state [ :starturl_service_object ] ) # Yes i want assignment
151
235
return unless @text
@@ -165,7 +249,37 @@ def collect_report_item_name
165
249
return unless in_tag ( "ReportItem" )
166
250
return unless @text
167
251
return if @text . strip . empty?
168
- @state [ :report_item ] = @text
252
+ @state [ :report_item ] [ :name ] = @text
253
+ end
254
+
255
+ def collect_report_item_severity
256
+ return unless in_tag ( "ReportItem" )
257
+ return unless @text
258
+ return if @text . strip . empty?
259
+ @state [ :report_item ] [ :severity ] = @text
260
+ end
261
+
262
+ def collect_report_item_cwe
263
+ return unless in_tag ( "ReportItem" )
264
+ return unless @text
265
+ return if @text . strip . empty?
266
+ @state [ :report_item ] [ :cwe ] = @text
267
+ end
268
+
269
+ def collect_report_item_reference_url
270
+ return unless in_tag ( "ReportItem" )
271
+ return unless in_tag ( "References" )
272
+ return unless in_tag ( "Reference" )
273
+ return unless @text
274
+ return if @text . strip . empty?
275
+ @state [ :report_item ] [ :reference_url ] = @text
276
+ end
277
+
278
+ def collect_report_item_parameter
279
+ return unless in_tag ( "ReportItem" )
280
+ return unless @text
281
+ return if @text . strip . empty?
282
+ @state [ :report_item ] [ :parameter ] = @text
169
283
end
170
284
171
285
# @state[:fullurl] is set by report_web_site
@@ -211,20 +325,26 @@ def report_web_form(&block)
211
325
def report_web_page ( &block )
212
326
return if should_skip_this_page
213
327
return unless @state [ :web_site ]
328
+ @state [ :page_request_verb ] = nil
214
329
return unless @state [ :page_request ]
215
330
return if @state [ :page_request ] . strip . empty?
216
- return unless @state [ :page_response ]
217
- return if @state [ :page_response ] . strip . empty?
218
- path , query_string = parse_request ( @state [ :page_request ] )
331
+ verb , path , query_string = parse_request ( @state [ :page_request ] )
219
332
return unless path
220
- parsed_response = parse_response ( @state [ :page_response ] )
221
- return unless parsed_response
333
+ @state [ :page_request_verb ] = verb
222
334
web_page_info = { }
335
+ if @state [ :page_response ] . strip . blank?
336
+ web_page_info [ :code ] = ""
337
+ web_page_info [ :headers ] = { }
338
+ web_page_info [ :body ] = ""
339
+ else
340
+ parsed_response = parse_response ( @state [ :page_response ] )
341
+ return unless parsed_response
342
+ web_page_info [ :code ] = parsed_response [ :code ] . to_i
343
+ web_page_info [ :headers ] = parsed_response [ :headers ]
344
+ web_page_info [ :body ] = parsed_response [ :body ]
345
+ end
223
346
web_page_info [ :web_site ] = @state [ :web_site ]
224
347
web_page_info [ :path ] = path
225
- web_page_info [ :code ] = parsed_response [ :code ] . to_i
226
- web_page_info [ :headers ] = parsed_response [ :headers ]
227
- web_page_info [ :body ] = parsed_response [ :body ]
228
348
web_page_info [ :query ] = query_string || ""
229
349
url = ""
230
350
url << @state [ :web_site ] . service . name . to_s << "://"
@@ -234,13 +354,51 @@ def report_web_page(&block)
234
354
return unless uri # Sanity checker
235
355
db . emit ( :web_page , url , &block ) if block
236
356
web_page_object = db_report ( :web_page , web_page_info )
237
- @state [ :page_request ] = @state [ :page_response ] = nil
238
357
@state [ :web_page ] = web_page_object
239
358
end
240
359
360
+ def report_web_vuln ( &block )
361
+ return if should_skip_this_page
362
+ return unless @state [ :web_page ]
363
+ return unless @state [ :web_site ]
364
+ return unless @state [ :vuln_info ]
365
+
366
+ web_vuln_info = { }
367
+ web_vuln_info [ :web_site ] = @state [ :web_site ]
368
+ web_vuln_info [ :path ] = @state [ :web_page ] [ :path ]
369
+ web_vuln_info [ :query ] = @state [ :web_page ] [ :query ]
370
+ web_vuln_info [ :method ] = @state [ :page_request_verb ]
371
+ web_vuln_info [ :pname ] = ""
372
+ if @state [ :page_response ] . blank?
373
+ web_vuln_info [ :proof ] = "<empty response>"
374
+ else
375
+ web_vuln_info [ :proof ] = @state [ :page_response ]
376
+ end
377
+ web_vuln_info [ :risk ] = 5
378
+ web_vuln_info [ :params ] = [ ]
379
+ unless @state [ :report_item ] [ :parameter ] . blank?
380
+ # Acunetix only lists a single paramter...
381
+ web_vuln_info [ :params ] << [ @state [ :report_item ] [ :parameter ] . to_s , "" ]
382
+ end
383
+ web_vuln_info [ :category ] = "imported"
384
+ web_vuln_info [ :confidence ] = 100
385
+ web_vuln_info [ :name ] = @state [ :vuln_info ] [ :name ]
386
+
387
+ db . emit ( :web_vuln , web_vuln_info [ :name ] , &block ) if block
388
+ vuln = db_report ( :web_vuln , web_vuln_info )
389
+ end
390
+
391
+ def report_other_vuln ( &block )
392
+ return if should_skip_this_page
393
+ return unless @state [ :vuln_info ]
394
+
395
+ db . emit ( :vuln , @state [ :vuln_info ] [ :name ] , &block ) if block
396
+ db_report ( :vuln , @state [ :vuln_info ] . merge ( :host => @host_object ) )
397
+ end
398
+
241
399
# Reasons why we shouldn't collect a particular web page.
242
400
def should_skip_this_page
243
- if @state [ :report_item ] =~ /Unrestricted File Upload/
401
+ if @state [ :report_item ] [ :name ] =~ /Unrestricted File Upload/
244
402
# This means that the page being collected is something the
245
403
# auditor put there, so it's not useful to report on.
246
404
return true
@@ -259,6 +417,7 @@ def parse_request(request)
259
417
return unless verb
260
418
return unless req
261
419
path , query_string = req . split ( /\? / ) [ 0 , 2 ]
420
+ return verb , path , query_string
262
421
end
263
422
264
423
def parse_response ( response )
@@ -302,14 +461,14 @@ def report_host(&block)
302
461
303
462
# The service is super important, so we hang on to it for the
304
463
# rest of the scan.
305
- def report_starturl_service ( host_object , &block )
306
- return unless host_object
464
+ def report_starturl_service ( &block )
465
+ return unless @ host_object
307
466
return unless @state [ :starturl_uri ]
308
467
name = @state [ :starturl_uri ] . scheme
309
468
port = @state [ :starturl_uri ] . port
310
- addr = host_object . address
469
+ addr = @ host_object. address
311
470
svc = {
312
- :host => host_object ,
471
+ :host => @ host_object,
313
472
:port => port ,
314
473
:name => name . dup ,
315
474
:proto => "tcp"
@@ -320,6 +479,22 @@ def report_starturl_service(host_object,&block)
320
479
end
321
480
end
322
481
482
+ def report_kbitem_service ( service , &block )
483
+ return unless @host_object
484
+ return unless @state [ :starturl_uri ]
485
+ addr = @host_object . address
486
+ svc = {
487
+ :host => @host_object ,
488
+ :port => service [ :portnum ] . to_i ,
489
+ :name => service [ :name ] . dup . downcase ,
490
+ :proto => service [ :proto ] . dup . downcase
491
+ }
492
+ if service [ :name ] and service [ :portnum ]
493
+ db . emit ( :service , [ addr , service [ :portnum ] ] . join ( ":" ) , &block ) if block
494
+ db_report ( :service , svc )
495
+ end
496
+ end
497
+
323
498
def report_web_site ( url , &block )
324
499
return unless in_tag ( "Crawler" )
325
500
return unless url
0 commit comments