├── Burp-MissingScannerChecks.py
└── README.md
/Burp-MissingScannerChecks.py:
--------------------------------------------------------------------------------
1 | # Burp Additonal Scanner Checks Extension
2 | # Copyright 2017 Thomas Patzke
3 | #
4 | # Parts of this code (DOMXSS REs) are based on work licensed under LGPL
5 | # and can be found here:
6 | # https://code.google.com/p/domxsswiki/wiki/FindingDOMXSS
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from burp import (IBurpExtender, IScannerCheck, IScanIssue, ITab)
22 | from javax.swing import (GroupLayout, JPanel, JCheckBox, JTextField, JLabel, JButton)
23 | from array import array
24 | import re
25 | import pickle
26 |
27 | STSMinimum = 60 * 60 * 24 * 90 # Minimum for Strict Transport Security: 90 days
28 | issueTypeDOMXSS = 2097930
29 | issueNameDOMXSS = "Possible DOM-based Cross-site scripting"
30 | issueTypeSTS = 5245380
31 | issueNameSTS = "Strict Transport Security Misconfiguration"
32 | issueTypeXCTO = 8389890
33 | issueNameXCTO = "Content Sniffing not disabled"
34 | issueTypeRedirectFromHTTP2HTTPS = 5244500
35 | issueNameRedirectFromHTTP2HTTPS = "Redirection from HTTP to HTTPS"
36 | issueTypeXXP = 5245361
37 | issueNameXXP = "Browser cross-site scripting filter misconfiguration"
38 |
39 | class BurpExtender(IBurpExtender, IScannerCheck, IScanIssue, ITab):
40 | def registerExtenderCallbacks(self, callbacks):
41 | self.callbacks = callbacks
42 | self.helpers = callbacks.getHelpers()
43 | callbacks.setExtensionName("Missing Scanner Checks")
44 | self.out = callbacks.getStdout()
45 |
46 | # define all checkboxes
47 | self.cbPassiveChecks = self.defineCheckBox("Passive Scanner Checks")
48 | self.cbDOMXSS = self.defineCheckBox("DOM XSS", False)
49 | self.cbDOMXSSSources = self.defineCheckBox("Sources", False)
50 | self.cbDOMXSSSinks = self.defineCheckBox("Sinks")
51 | self.cbDOMXSSjQuerySinks = self.defineCheckBox("jQuery Sinks", False)
52 | self.grpDOMXSSSettings = JPanel()
53 | self.grpDOMXSSSettings.add(self.cbDOMXSSSources)
54 | self.grpDOMXSSSettings.add(self.cbDOMXSSSinks)
55 | self.grpDOMXSSSettings.add(self.cbDOMXSSjQuerySinks)
56 | self.cbSTS = self.defineCheckBox("Strict Transport Security")
57 | self.lblSTSMin = JLabel("Minimum acceptable max-age")
58 | self.inSTSMin = JTextField(str(STSMinimum), 9, actionPerformed=self.setSTSMinimum) # TODO: actionPerformed only fires on enter key - focus lost would be better
59 | self.inSTSMin.setToolTipText("Enter the minimum max-age value which is considered as acceptable. Press return to change setting!")
60 | self.grpSTSSettings = JPanel()
61 | self.grpSTSSettings.add(self.lblSTSMin)
62 | self.grpSTSSettings.add(self.inSTSMin)
63 | self.cbXCTO = self.defineCheckBox("Content Sniffing")
64 | self.cbXXP = self.defineCheckBox("Client-side XSS Filter Configuration")
65 | self.cbRedirToHTTPS = self.defineCheckBox("Redirection from HTTP to HTTPS")
66 | self.btnSave = JButton("Set as default", actionPerformed=self.saveConfig)
67 | self.btnRestore = JButton("Restore", actionPerformed=self.restoreConfig)
68 | self.grpConfig = JPanel()
69 | self.grpConfig.add(self.btnSave)
70 | self.grpConfig.add(self.btnRestore)
71 | self.restoreConfig()
72 |
73 | # definition of config tab
74 | self.tab = JPanel()
75 | layout = GroupLayout(self.tab)
76 | self.tab.setLayout(layout)
77 | layout.setAutoCreateGaps(True)
78 | layout.setAutoCreateContainerGaps(True)
79 | layout.setHorizontalGroup(
80 | layout.createSequentialGroup()
81 | .addGroup(layout.createParallelGroup()
82 | .addComponent(self.cbPassiveChecks)
83 | )
84 | .addGroup(layout.createParallelGroup()
85 | .addComponent(self.cbDOMXSS)
86 | .addComponent(self.cbSTS)
87 | .addComponent(self.cbXCTO)
88 | .addComponent(self.cbXXP)
89 | .addComponent(self.cbRedirToHTTPS)
90 | )
91 | .addGroup(layout.createParallelGroup()
92 | .addComponent(self.grpDOMXSSSettings, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
93 | .addComponent(self.grpSTSSettings, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
94 | .addComponent(self.grpConfig, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
95 | )
96 | )
97 | layout.setVerticalGroup(
98 | layout.createSequentialGroup()
99 | .addGroup(layout.createParallelGroup()
100 | .addComponent(self.cbPassiveChecks)
101 | .addComponent(self.cbDOMXSS)
102 | .addComponent(self.grpDOMXSSSettings, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
103 | )
104 | .addGroup(layout.createParallelGroup()
105 | .addComponent(self.cbSTS)
106 | .addComponent(self.grpSTSSettings, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
107 | )
108 | .addComponent(self.cbXCTO)
109 | .addComponent(self.cbXXP)
110 | .addComponent(self.cbRedirToHTTPS)
111 | .addComponent(self.grpConfig, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
112 | )
113 |
114 | self.domXSSSourcesRE = re.compile("(location\s*[\[.])|([.\[]\s*[\"']?\s*(arguments|dialogArguments|innerHTML|write(ln)?|open(Dialog)?|showModalDialog|cookie|URL|documentURI|baseURI|referrer|name|opener|parent|top|content|self|frames)\W)|(localStorage|sessionStorage|Database)")
115 | # NOTE: done some optimizations here, original RE caused too much noise
116 | # - added leading dot in the first part - original recognized " 0: # One of the DOMXSS REs matched
211 | scanIssues.append(DOMXSSScanIssue(
212 | baseRequestResponse,
213 | domXSSSourcesPos,
214 | domXSSSinksPos,
215 | domXSSjQuerySinksPos,
216 | self.helpers,
217 | self.callbacks
218 | ))
219 |
220 | # Identify missing, wrong or multiple occurring HTTP headers
221 | headersSTS = list()
222 | headersXCTO = list()
223 | headersXXP = list()
224 | headersLocationHTTPS = list()
225 |
226 | offset = 0
227 | for header in responseHeaders:
228 | if self.cbSTS.isSelected():
229 | match = self.headerSTSRE.match(header)
230 | if match:
231 | headersSTS.append((match, offset))
232 |
233 | if self.cbXCTO.isSelected():
234 | match = self.headerXCTORE.match(header)
235 | if match:
236 | headersXCTO.append(match)
237 |
238 | if self.cbXXP.isSelected():
239 | match = self.headerXXP.match(header)
240 | if match:
241 | headersXXP.append((match, offset))
242 |
243 | if self.cbRedirToHTTPS.isSelected() and requestProtocol == 'http':
244 | match = self.headerLocationHTTPS.match(header)
245 | if match:
246 | headersLocationHTTPS.append((match, offset))
247 |
248 | offset += len(header) + 2 # TODO: assumption that CRLF is always used. The world is ugly, make a real check.
249 |
250 | if requestProtocol != "https":
251 | pass # HSTS only valid in HTTPS responses.
252 | elif self.cbSTS.isSelected():
253 | if len(headersSTS) == 0: # No HSTS header
254 | scanIssues.append(STSScanIssue(
255 | baseRequestResponse,
256 | STSScanIssue.caseNoHeader,
257 | None,
258 | self.helpers,
259 | self.callbacks
260 | ))
261 | elif len(headersSTS) == 1 and int(headersSTS[0][0].group(1)) < STSMinimum: # HSTS header present, but time frame too short
262 | scanIssues.append(STSScanIssue(
263 | baseRequestResponse,
264 | STSScanIssue.caseTooLow,
265 | (int(headersSTS[0][0].group(1)), headersSTS[0][1] + headersSTS[0][0].start(1), headersSTS[0][1] + headersSTS[0][0].end(1)),
266 | self.helpers,
267 | self.callbacks
268 | ))
269 | elif len(headersSTS) > 1: # multiple HSTS headers
270 | scanIssues.append(STSScanIssue(
271 | baseRequestResponse,
272 | STSScanIssue.caseMultipleHeaders,
273 | headersSTS,
274 | self.helpers,
275 | self.callbacks
276 | ))
277 |
278 | # Redirection from HTTP to HTTPS
279 | if self.cbRedirToHTTPS.isSelected() and len(headersLocationHTTPS) > 0:
280 | scanIssues.append(RedirectFromHTTP2HTTPSScanIssue(
281 | baseRequestResponse,
282 | headersLocationHTTPS,
283 | self.helpers,
284 | self.callbacks
285 | ))
286 |
287 | if self.cbXXP.isSelected():
288 | if len(headersXXP) == 0: # No XSS protection header
289 | scanIssues.append(XXPScanIssue(
290 | baseRequestResponse,
291 | XXPScanIssue.caseNoHeader,
292 | None,
293 | self.helpers,
294 | self.callbacks
295 | ))
296 | elif len(headersXXP) == 1 and int(headersXXP[0][0].group(1)) == 1 and headersXXP[0][0].group(2) != "block": # Activated but not in block mode
297 | scanIssues.append(XXPScanIssue(
298 | baseRequestResponse,
299 | XXPScanIssue.caseNoBlockMode,
300 | headersXXP,
301 | self.helpers,
302 | self.callbacks
303 | ))
304 | elif len(headersXXP) > 1: # Multiple XXP headers
305 | scanIssues.append(XXPScanIssue(
306 | baseRequestResponse,
307 | XXPScanIssue.caseMultipleHeaders,
308 | headersXXP,
309 | self.helpers,
310 | self.callbacks
311 | ))
312 | # "X-XSS-Protection: 0" already catched by Burp
313 |
314 |
315 | # X-Content-Type-Options missing
316 | # NOTE: it is assumed that multiple "X-Content-Type-Options: nosniff" headers can't cause confusion at browser side because they all have the same meaning.
317 | if self.cbXCTO.isSelected() and len(headersXCTO) == 0:
318 | scanIssues.append(XCTOScanIssue(
319 | baseRequestResponse,
320 | self.helpers,
321 | self.callbacks
322 | ))
323 |
324 | return scanIssues
325 |
326 | def consolidateDuplicateIssues(self, existingIssue, newIssue):
327 | if existingIssue.getIssueName() == newIssue.getIssueName():
328 | if newIssue.getIssueName() == issueNameDOMXSS: # DOMXSS issues are different if response content is different.
329 | responseExisting = existingIssue.getHttpMessages()[0].getResponse()
330 | analyzedResponseExisting = self.helpers.analyzeResponse(responseExisting)
331 | bodyOffsetExisting = analyzedResponseExisting.getBodyOffset()
332 | responseBodyExisting = responseExisting.getResponse()[analyzedResponseExisting.getBodyOffset():].tostring()
333 |
334 | responseNew = newIssue.getHttpMessages()[0].getResponse()
335 | analyzedResponseNew = self.helpers.analyzeResponse(responseNew)
336 | bodyOffsetNew = analyzedResponseNew.getBodyOffset()
337 | responseBodyNew = responseNew.getResponse()[analyzedResponseNew.getBodyOffset():].tostring()
338 |
339 | if responseBodyExisting == responseBodyNew:
340 | return -1
341 | else:
342 | return 0
343 | elif newIssue.getIssueName() == issueNameRedirectFromHTTP2HTTPS: # Redirection issues are different if target URLs differ
344 | if existingIssue.getIssueDetail() == newIssue.getIssueDetail():
345 | return -1
346 | else:
347 | return 0
348 | else: # In all other cases: keep existing issue
349 | return -1
350 | return 0
351 |
352 | class DOMXSSScanIssue(IScanIssue):
353 | def __init__(self, requestResponse, sourcesPos, sinksPos, jQuerySinksPos, helpers, callbacks):
354 | analyzedRequest = helpers.analyzeRequest(requestResponse)
355 | self.findingUrl = analyzedRequest.getUrl()
356 | self.sourcesPos = sourcesPos
357 | self.sinksPos = sinksPos + jQuerySinksPos
358 | self.requestResponse = callbacks.applyMarkers(requestResponse, None, normalizePositions(self.sourcesPos + self.sinksPos))
359 |
360 | def getUrl(self):
361 | return self.findingUrl
362 |
363 | def getIssueName(self):
364 | return issueNameDOMXSS
365 |
366 | def getIssueType(self):
367 | return issueTypeDOMXSS
368 |
369 | def getSeverity(self):
370 | if len(self.sinksPos) > 0:
371 | return "High"
372 | else:
373 | return "Low"
374 |
375 | def getConfidence(self):
376 | return "Tentative"
377 |
378 | def getIssueBackground(self):
379 | msg = "DOM-based cross-site scripting (XSS) is a variant of the well-known XSS vulnerabilities where the issue is located in client-side JavaScript code. \
380 | As in classical XSS, the vulnerability causes code execution in the context of the users session within the application. An attacker can perform a wide \
381 | variety of actions, like session hijacking etc. The vulnerability is caused by copying data from non-trustworthy sources (user input, URL) into the DOM with \
382 | insecure methods.
\
383 | Such vulnerabilities are hard to detect with classical methods like checking the response for occurrences of previous inputs or parameters. This scan \
384 | issue has detected the usage of insecure sources or sinks in the JavaScript code of the page. Further manual checks must be performed to verify the impact \
385 | of their usage.
"
386 | return msg
387 |
388 | def getRemediationBackground(self):
389 | msg = "DOM-based XSS can be prevented by usage of secure methods that only operate on the content text of the DOM instead of modifying the DOM structure. E.g. \
390 | usage of the innerText attribute instead of innerHTML or the text() method of jQuery instead of html() to insert user content into the DOM. If these methods are \
391 | not applicable in a particular use case, the user input has to be filtered and sanitized before it is passed into the DOM by insecure methods."
392 | return msg
393 |
394 | def getIssueDetail(self):
395 | msg = "The scanner check has detected occurrences of unsafe "
396 | if len(self.sinksPos) > 0 and len(self.sourcesPos) > 0:
397 | msg += "sources and sinks. If data flows from unsafe sources into unsafe sinks without being sanitized then it is quite certain that the web application is \
398 | vulnerable against DOM-based XSS. "
399 | elif len(self.sinksPos) > 0:
400 | msg += "sinks. Be aware that there are potential unsafe sources which are not detected by the patterns used in the scanner check, e.g. data loaded by the \
401 | XmlHttpRequest API. Furthermore there is the possibility that the sources and sinks are distributed in different files. "
402 | elif len(self.sourcesPos) > 0:
403 | msg += "sources. Generally there must be a sink for code execution caused by unsafe sources. This could be located in a different part of the web application \
404 | or be unrecognized by the scanner module. "
405 | msg += "See the response tab to review the occurrences."
406 | return msg
407 |
408 | def getRemediationDetail(self):
409 | return None
410 |
411 | def getHttpMessages(self):
412 | return [self.requestResponse]
413 |
414 | def getHttpService(self):
415 | return self.requestResponse.getHttpService()
416 |
417 |
418 | class STSScanIssue(IScanIssue):
419 | caseNoHeader = 1
420 | caseTooLow = 2
421 | caseMultipleHeaders = 3
422 |
423 | def __init__(self, requestResponse, case, data, helpers, callbacks):
424 | analyzedRequest = helpers.analyzeRequest(requestResponse)
425 | self.findingUrl = analyzedRequest.getUrl()
426 | self.case = case
427 | self.data = data
428 | self.STSMinimum = STSMinimum
429 | if case == self.caseNoHeader:
430 | self.requestResponse = requestResponse
431 | elif case == self.caseTooLow:
432 | self.requestResponse = callbacks.applyMarkers(requestResponse, None, [array('i', (data[1], data[2]))])
433 | elif case == self.caseMultipleHeaders:
434 | self.requestResponse = callbacks.applyMarkers(requestResponse, None, normalizePositions(extractMatchPositions(data)))
435 |
436 | def getUrl(self):
437 | return self.findingUrl
438 |
439 | def getIssueName(self):
440 | return issueNameSTS
441 |
442 | def getIssueType(self):
443 | return issueTypeSTS
444 |
445 | def getSeverity(self):
446 | return "Medium"
447 |
448 | def getConfidence(self):
449 | return "Certain"
450 |
451 | def getIssueBackground(self):
452 | return "The HTTP Strict Transport Security policy defines a timeframe where a browser must connect to the web server via HTTPS. Without a Strict Transport \
453 | Security policy the web application may be vulnerable against several attacks:
\
454 | \
455 | - If the web application mixes usage of HTTP and HTTPS, an attacker can manipulate pages in the unsecured area of the application or change redirection targets \
456 | in a manner that the switch to the secured page is not performed or done in a manner, that the attacker remains between client and server.
\
457 | - If there is no HTTP server, an attacker in the same network could simulate a HTTP server and motivate the user to click on a prepared URL by a scoial \
458 | engineering attack.
\
459 |
\
460 | The protection is effective only for the given amount of time. Multiple occurrence of this header could cause undefined behaviour in browsers and should be avoided.
"
461 | return msg
462 |
463 | def getRemediationBackground(self):
464 | return None
465 |
466 | def getIssueDetail(self):
467 | msg = None
468 | if self.case == self.caseNoHeader:
469 | msg = "There was no \"Strict-Transport-Security\" header in the server response."
470 | elif self.case == self.caseTooLow:
471 | msg = "A \"Strict-Transport-Security\" header was set in the server response and the time frame was set to " + str(self.data[0]) + ". This is considered as too low."
472 | elif self.case == self.caseMultipleHeaders:
473 | msg = "Multiple occurrences of the \"Strict-Transport-Security\" header were seen in the HTTP response. This could cause undefined behaviour with browsers, \
474 | because it is unclear, which header is used."
475 | return msg
476 |
477 | def getRemediationDetail(self):
478 | msg = None
479 | if self.case == self.caseNoHeader:
480 | msg = "A Strict-Transport-Security HTTP header should be sent with each HTTPS response. The syntax is as follows:
\
481 | Strict-Transport-Security: max-age=<seconds>[; includeSubDomains]
\
482 | The parameter max-age gives the time frame for requirement of HTTPS in seconds and should be chosen quite high, e.g. several months.\
483 | A value below " + str(self.STSMinimum) + " is considered as too low by this scanner check. \
484 | The flag includeSubDomains defines that the policy applies also for sub domains of the sender of the response.
"
485 | elif self.case == self.caseTooLow:
486 | msg = "The given time frame should be increased to a minimum of " + str(self.STSMinimum) + " seconds."
487 | elif self.case == self.caseMultipleHeaders:
488 | msg = "There should be only one header defining a strict transport security policy. The Time frame should be set at minimum to " + str(self.STSMinimum) + "."
489 | return msg
490 |
491 | def getHttpMessages(self):
492 | return [self.requestResponse]
493 |
494 | def getHttpService(self):
495 | return self.requestResponse.getHttpService()
496 |
497 |
498 | class RedirectFromHTTP2HTTPSScanIssue(IScanIssue):
499 | def __init__(self, requestResponse, headers, helpers, callbacks):
500 | analyzedRequest = helpers.analyzeRequest(requestResponse)
501 | self.findingUrl = analyzedRequest.getUrl()
502 | self.redirectURLs = map(lambda(header): header[0].group(1), headers)
503 | self.redirectURLs.sort()
504 | self.requestResponse = callbacks.applyMarkers(requestResponse, None, normalizePositions(extractMatchPositions(headers)))
505 |
506 | def getUrl(self):
507 | return self.findingUrl
508 |
509 | def getIssueName(self):
510 | return issueNameRedirectFromHTTP2HTTPS
511 |
512 | def getIssueType(self):
513 | return issueTypeRedirectFromHTTP2HTTPS
514 |
515 | def getSeverity(self):
516 | return "Medium"
517 |
518 | def getConfidence(self):
519 | return "Certain"
520 |
521 | def getIssueBackground(self):
522 | msg = "The redirection to a HTTPS URL is transmitted over the insecure HTTP protocol. This makes the redirection itself vulnerable against Man-in-the-Middle attacks. \
523 | An attacker could redirect the user to a slightly different HTTPS URL which is under his control or keep the connection unencrypted by stripping down to HTTP and relaying \
524 | between client and server."
525 | return msg
526 |
527 | def getRemediationBackground(self):
528 | msg = "Usage of HTTP should be kept at a minimum in web applications where security matters. Users which enter the web application via HTTP, e.g. by entering only the domain name in the \
529 | URL bar of their browser should be redirected directly to a secure HTTPS URL. All HTTPS resources should provide a Strict-Transport-Security header which ensures that the \
530 | browser uses only HTTPS for a given amount of time. The syntax for this header is as follows:
\
531 | Strict-Transport-Security: max-age=<seconds>[; includeSubDomains]
\
532 | The parameter max-age gives the time frame for requirement of HTTPS in seconds and should be chosen quite high, e.g. several months. Except the initial redirection the \
533 | application should be used completely with HTTPS.
"
534 | return msg
535 |
536 | def getIssueDetail(self):
537 | msg = "The web application redirects the browser from HTTP to the following HTTPS URL"
538 | if len(self.redirectURLs) == 1:
539 | msg += ": " + self.redirectURLs[0] + ""
540 | else:
541 | msg += "s: "
542 | for redirectURL in self.redirectURLs:
543 | msg += "- " + redirectURL + "
"
544 | msg += "
Multiple headers were given. The redirection target depends on the used browser.
"
545 | return msg
546 |
547 | def getRemediationDetail(self):
548 | return None
549 |
550 | def getHttpMessages(self):
551 | return [self.requestResponse]
552 |
553 | def getHttpService(self):
554 | return self.requestResponse.getHttpService()
555 |
556 |
557 | class XXPScanIssue(IScanIssue):
558 | caseNoHeader = 1
559 | caseNoBlockMode = 2
560 | caseMultipleHeaders = 3
561 |
562 | def __init__(self, requestResponse, case, headers, helpers, callbacks):
563 | analyzedRequest = helpers.analyzeRequest(requestResponse)
564 | self.findingUrl = analyzedRequest.getUrl()
565 | self.case = case
566 | self.headers = headers
567 | if case == self.caseNoHeader:
568 | self.requestResponse = requestResponse
569 | elif case == self.caseNoBlockMode:
570 | self.requestResponse = callbacks.applyMarkers(requestResponse, None, [array('i', (headers[0][1], headers[0][1] + headers[0][0].end(0)))])
571 | elif case == self.caseMultipleHeaders:
572 | self.requestResponse = callbacks.applyMarkers(requestResponse, None, normalizePositions(extractMatchPositions(headers)))
573 |
574 | def getUrl(self):
575 | return self.findingUrl
576 |
577 | def getIssueName(self):
578 | return issueNameXXP
579 |
580 | def getIssueType(self):
581 | return issueTypeXXP
582 |
583 | def getSeverity(self):
584 | return "Low"
585 |
586 | def getConfidence(self):
587 | return "Certain"
588 |
589 | def getIssueBackground(self):
590 | return "Cross-site scripting (XSS) filters in browsers check if the URL contains possible harmful XSS payloads and if they are reflected in the response page. If \
591 | such a condition is recognized, the injected code is changed in a way, that it is not executed anymore to prevent a succesful XSS attack. The downside of these filters \
592 | is, that the browser has no possibility to distinguish between code fragments which were reflected by a vulnerable web application in an XSS attack and these which are \
593 | already present on the page. In the past, these filters were used by attackers to deactivate JavaScript code on the attacked web page. Sometimes the XSS filters itself are \
594 | vulnerable in a way, that web applications which were protected properly against XSS attacks became vulnerable under certain conditions."
595 |
596 | def getRemediationBackground(self):
597 | return "It is considered as better practice to instruct the browser XSS filter to never render the web page if an XSS attack is detected."
598 |
599 | def getIssueDetail(self):
600 | msg = None
601 | if self.case == self.caseNoHeader:
602 | msg = "No X-XSS-Protection header was set in the response. This means that the browser uses default behaviour that detection of a cross-site scripting attack never prevents rendering."
603 | elif self.case == self.caseNoBlockMode:
604 | msg = "A X-XSS-Protection header is set and XSS protection is enabled, but blocking mode is not set."
605 | elif self.case == self.caseMultipleHeaders:
606 | msg = "Multiple occurrences of the \"X-XSS-Protection\" header were seen in the HTTP response. This could cause undefined behaviour with browsers, \
607 | because it is unclear, which header is used."
608 | return msg
609 |
610 | def getRemediationDetail(self):
611 | msg = None
612 | if self.case == self.caseNoHeader:
613 | msg = "The following header should be set:
\
614 | X-XSS-Protection: 1; mode=block
"
615 | elif self.case == self.caseNoBlockMode:
616 | msg = "Set the option \"mode=block\" in the X-XSS-Protection HTTP header as follows:
\
617 | X-XSS-Protection: 1; mode=block
"
618 | elif self.case == self.caseMultipleHeaders:
619 | msg = "There should be only one X-XSS-Protection header present."
620 | return msg
621 |
622 | def getHttpMessages(self):
623 | return [self.requestResponse]
624 |
625 | def getHttpService(self):
626 | return self.requestResponse.getHttpService()
627 |
628 |
629 | class XCTOScanIssue(IScanIssue):
630 | def __init__(self, requestResponse, helpers, callbacks):
631 | analyzedRequest = helpers.analyzeRequest(requestResponse)
632 | self.findingUrl = analyzedRequest.getUrl()
633 | self.requestResponse = requestResponse
634 |
635 | def getUrl(self):
636 | return self.findingUrl
637 |
638 | def getIssueName(self):
639 | return issueNameXCTO
640 |
641 | def getIssueType(self):
642 | return issueTypeXCTO
643 |
644 | def getSeverity(self):
645 | return "Low"
646 |
647 | def getConfidence(self):
648 | return "Certain"
649 |
650 | def getIssueBackground(self):
651 | msg = "There was no \"X-Content-Type-Options\" HTTP header with the value nosniff set in the response. The lack of this header causes that certain browsers, \
652 | try to determine the content type and encoding of the response even when these properties are defined correctly. This can make the web application \
653 | vulnerable against Cross-Site Scripting (XSS) attacks. E.g. the Internet Explorer and Safari treat responses with the content type text/plain as HTML, if they contain \
654 | HTML tags."
655 | return msg
656 |
657 | def getRemediationBackground(self):
658 | msg = "Set the following HTTP header at least in all responses which contain user input: X-Content-Type-Options: nosniff
"
659 | return msg
660 |
661 | def getIssueDetail(self):
662 | return None
663 |
664 | def getRemediationDetail(self):
665 | return None
666 |
667 | def getHttpMessages(self):
668 | return [self.requestResponse]
669 |
670 | def getHttpService(self):
671 | return self.requestResponse.getHttpService()
672 |
673 |
674 | ### Helpers ###
675 | # Sort and merge overlapping match ranges - needed by Burp for scan issue markers
676 | def normalizePositions(uPos):
677 | sortedPos = sorted(uPos)
678 | normPos = list()
679 | prevPos = None
680 | for pos in sortedPos:
681 | if not prevPos:
682 | prevPos = pos
683 | elif prevPos[1] > pos[0]:
684 | if prevPos[1] < pos[1]:
685 | prevPos[1] = pos[1]
686 | else:
687 | normPos.append(prevPos)
688 | prevPos = pos
689 | normPos.append(prevPos)
690 | return normPos
691 |
692 | # extract match positions from an array of matches as expected by Burp for scan issue markers
693 | def extractMatchPositions(matches, bodyOffset = 0):
694 | try:
695 | if isinstance(matches, list) and isinstance(matches[0], tuple):
696 | return map(lambda(match, offset): array('i', (match.start() + bodyOffset + offset, match.end() + bodyOffset + offset)), matches)
697 | else:
698 | return map(lambda(match): array('i', (match.start() + bodyOffset, match.end() + bodyOffset)), matches)
699 | except IndexError:
700 | return list()
701 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IMPORTANT: Archived Project
2 |
3 | This project is not maintained anymore, please fork and do changes on your own.
4 |
5 | # Burp Missing Scanner Checks
6 |
7 | This burp extension implements some passive scanner checks which are
8 | missing in Burp suite:
9 |
10 | * DOM-based XSS (REs are based on those from https://code.google.com/p/domxsswiki/wiki/FindingDOMXSS)
11 |
12 | * Missing HTTP headers:
13 |
14 | * Strict-Transport-Security
15 |
16 | * X-Content-Type-Options: nosniff
17 |
18 | * X-XSS-Protection
19 |
20 | * Multiple occurrences of the checked headers.
21 |
22 | * Redirection from HTTP to HTTPS
23 |
24 | All checks can be enabled separately in an own extension tab and a default config can be stored.
25 |
26 | ## TODO
27 |
28 | * See TODO markers in the code.
29 |
30 | * Further possibilities to redirect from HTTP to HTTPS (meta refresh, links, referer checking)
31 |
32 | * Active scanner check: Actively test directories for listings
33 |
34 | * Active scanner check: Add parameters like debug, admin, test etc. and check if something
35 | interesting appears on the page.
36 |
37 | * Active Scanner check: Reaction of the web application and server to
38 | requests with different/missing host headers.
39 |
--------------------------------------------------------------------------------