Module: Msf::DBManager::Import::Netsparker

Included in:
Msf::DBManager::Import
Defined in:
lib/msf/core/db_manager/import/netsparker.rb

Instance Method Summary collapse

Instance Method Details

#import_netsparker_xml(args = {}, &block) ⇒ Object

Process NetSparker XML


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/msf/core/db_manager/import/netsparker.rb', line 5

def import_netsparker_xml(args={}, &block)
  data = args[:data]
  wspace = Msf::Util::DBManager.process_opts_workspace(args, framework).name
  bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  addr = nil
  parser = Rex::Parser::NetSparkerXMLStreamParser.new
  parser.on_found_vuln = Proc.new do |vuln|
    data = {:workspace => wspace}

    # Parse the URL
    url  = vuln['url']
    return if not url

    # Crack the URL into a URI
    uri = URI(url) rescue nil
    return if not uri

    # Resolve the host and cache the IP
    if not addr
      baddr = Rex::Socket.addr_aton(uri.host) rescue nil
      if baddr
        addr = Rex::Socket.addr_ntoa(baddr)
        yield(:address, addr) if block
      end
    end

    # Bail early if we have no IP address
    if not addr
      raise Interrupt, "Not a valid IP address"
    end

    if bl.include?(addr)
      raise Interrupt, "IP address is on the blacklist"
    end

    data[:host]  = addr
    data[:vhost] = uri.host
    data[:port]  = uri.port
    data[:ssl]   = (uri.scheme == "ssl")

    body = nil
    # First report a web page
    if vuln['response']
      headers = {}
      code    = 200
      head,body = vuln['response'].to_s.split(/\r?\n\r?\n/, 2)
      if body

        if head =~ /^HTTP\d+\.\d+\s+(\d+)\s*/
          code = $1.to_i
        end

        headers = {}
        head.split(/\r?\n/).each do |line|
          hname,hval = line.strip.split(/\s*:\s*/, 2)
          next if hval.to_s.strip.empty?
          headers[hname.downcase] ||= []
          headers[hname.downcase] << hval
        end

        info = {
          :path     => uri.path,
          :query    => uri.query,
          :code     => code,
          :body     => body,
          :headers  => headers,
          :task     => args[:task]
        }
        info.merge!(data)

        if headers['content-type']
          info[:ctype] = headers['content-type'][0]
        end

        if headers['set-cookie']
          info[:cookie] = headers['set-cookie'].join("\n")
        end

        if headers['authorization']
          info[:auth] = headers['authorization'].join("\n")
        end

        if headers['location']
          info[:location] = headers['location'][0]
        end

        if headers['last-modified']
          info[:mtime] = headers['last-modified'][0]
        end

        # Report the web page to the database
        report_web_page(info)

        yield(:web_page, url) if block
      end
    end # End web_page reporting


    details = netsparker_vulnerability_map(vuln)

    method = netsparker_method_map(vuln)
    pname  = netsparker_pname_map(vuln)
    params = netsparker_params_map(vuln)

    proof  = ''

    if vuln['info'] and vuln['info'].length > 0
      proof << vuln['info'].map{|x| "#{x[0]}: #{x[1]}\n" }.join + "\n"
    end

    if proof.empty?
      if body
        proof << body + "\n"
      else
        proof << vuln['response'].to_s + "\n"
      end
    end

    if params.empty? and pname
      params = [[pname, vuln['vparam_name'].to_s]]
    end

    info = {
      # XXX: There is a :request attr in the model, but report_web_vuln
      # doesn't seem to know about it, so this gets ignored.
      #:request  => vuln['request'],
      :path        => uri.path,
      :query       => uri.query,
      :method      => method,
      :params      => params,
      :pname       => pname.to_s,
      :proof       => proof,
      :risk        => details[:risk],
      :name        => details[:name],
      :blame       => details[:blame],
      :category    => details[:category],
      :description => details[:description],
      :confidence  => details[:confidence],
      :task        => args[:task]
    }
    info.merge!(data)

    next if vuln['type'].to_s.empty?

    report_web_vuln(info)
    yield(:web_vuln, url) if block
  end

  # We throw interrupts in our parser when the job is hopeless
  begin
    REXML::Document.parse_stream(data, parser)
  rescue ::Interrupt => e
    wlog("The netsparker_xml_import() job was interrupted: #{e}")
  end
end

#import_netsparker_xml_file(args = {}) ⇒ Object

Process a NetSparker XML file


162
163
164
165
166
167
168
169
170
# File 'lib/msf/core/db_manager/import/netsparker.rb', line 162

def import_netsparker_xml_file(args={})
  filename = args[:filename]

  data = ""
  ::File.open(filename, 'rb') do |f|
    data = f.read(f.stat.size)
  end
  import_netsparker_xml(args.merge(:data => data))
end

#netsparker_method_map(vuln) ⇒ Object


172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/msf/core/db_manager/import/netsparker.rb', line 172

def netsparker_method_map(vuln)
  case vuln['vparam_type']
  when "FullQueryString"
    "GET"
  when "Querystring"
    "GET"
  when "Post"
    "POST"
  when "RawUrlInjection"
    "GET"
  else
    "GET"
  end
end

#netsparker_params_map(vuln) ⇒ Object


187
188
189
# File 'lib/msf/core/db_manager/import/netsparker.rb', line 187

def netsparker_params_map(vuln)
  []
end

#netsparker_pname_map(vuln) ⇒ Object


191
192
193
194
195
196
197
198
# File 'lib/msf/core/db_manager/import/netsparker.rb', line 191

def netsparker_pname_map(vuln)
  case vuln['vparam_name']
  when "URI-BASED", "Query Based"
    "PATH"
  else
    vuln['vparam_name']
  end
end

#netsparker_vulnerability_map(vuln) ⇒ Object


200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/msf/core/db_manager/import/netsparker.rb', line 200

def netsparker_vulnerability_map(vuln)
  res = {
    :risk => 1,
    :name  => 'Information Disclosure',
    :blame => 'System Administrator',
    :category => 'info',
    :description => "This is an information leak",
    :confidence => 100
  }

  # Risk is a value from 1-5 indicating the severity of the issue
  #	Examples: 1, 4, 5

  # Name is a descriptive name for this vulnerability.
  #	Examples: XSS, ReflectiveXSS, PersistentXSS

  # Blame indicates who is at fault for the vulnerability
  #	Examples: App Developer, Server Developer, System Administrator

  # Category indicates the general class of vulnerability
  #	Examples: info, xss, sql, rfi, lfi, cmd

  # Description is a textual summary of the vulnerability
  #	Examples: "A reflective cross-site scripting attack"
  #             "The web server leaks the internal IP address"
  #             "The cookie is not set to HTTP-only"

  #
  # Confidence is a value from 1 to 100 indicating how confident the
  # software is that the results are valid.
  #	Examples: 100, 90, 75, 15, 10, 0

  case vuln['type'].to_s
  when "ApacheDirectoryListing"
    res = {
      :risk => 1,
      :name  => 'Directory Listing',
      :blame => 'System Administrator',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "ApacheMultiViewsEnabled"
    res = {
      :risk => 1,
      :name  => 'Apache MultiViews Enabled',
      :blame => 'System Administrator',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "ApacheVersion"
    res = {
      :risk => 1,
      :name  => 'Web Server Version',
      :blame => 'System Administrator',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "PHPVersion"
    res = {
      :risk => 1,
      :name  => 'PHP Module Version',
      :blame => 'System Administrator',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "AutoCompleteEnabled"
    res = {
      :risk => 1,
      :name  => 'Form AutoComplete Enabled',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "CookieNotMarkedAsHttpOnly"
    res = {
      :risk => 1,
      :name  => 'Cookie Not HttpOnly',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "EmailDisclosure"
    res = {
      :risk => 1,
      :name  => 'Email Address Disclosure',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "ForbiddenResource"
    res = {
      :risk => 1,
      :name  => 'Forbidden Resource',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "FileUploadFound"
    res = {
      :risk => 1,
      :name  => 'File Upload Form',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "PasswordOverHTTP"
    res = {
      :risk => 2,
      :name  => 'Password Over HTTP',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "MySQL5Identified"
    res = {
      :risk => 1,
      :name  => 'MySQL 5 Identified',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "PossibleInternalWindowsPathLeakage"
    res = {
      :risk => 1,
      :name  => 'Path Leakage - Windows',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "PossibleInternalUnixPathLeakage"
    res = {
      :risk => 1,
      :name  => 'Path Leakage - Unix',
      :blame => 'App Developer',
      :category => 'info',
      :description => "",
      :confidence => 100
    }
  when "PossibleXSS", "LowPossibilityPermanentXSS", "XSS", "PermanentXSS"
    conf = 100
    conf = 25  if vuln['type'].to_s == "LowPossibilityPermanentXSS"
    conf = 50  if vuln['type'].to_s == "PossibleXSS"
    res = {
      :risk => 3,
      :name  => 'Cross-Site Scripting',
      :blame => 'App Developer',
      :category => 'xss',
      :description => "",
      :confidence => conf
    }

  when "ConfirmedBlindSQLInjection", "ConfirmedSQLInjection", "HighlyPossibleSqlInjection", "DatabaseErrorMessages"
    conf = 100
    conf = 90  if vuln['type'].to_s == "HighlyPossibleSqlInjection"
    conf = 25  if vuln['type'].to_s == "DatabaseErrorMessages"
    res = {
      :risk => 5,
      :name  => 'SQL Injection',
      :blame => 'App Developer',
      :category => 'sql',
      :description => "",
      :confidence => conf
    }
  else
  conf = 100
  res = {
    :risk => 1,
    :name  => vuln['type'].to_s,
    :blame => 'App Developer',
    :category => 'info',
    :description => "",
    :confidence => conf
  }
  end

  res
end