├── BappManifest.bmf ├── BappDescription.html ├── README └── Burp-MissingScannerChecks.py /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: a158fd3fc9394253be3aa0bc4c181d1f 2 | ExtensionType: 2 3 | Name: Additional Scanner Checks 4 | RepoName: additional-scanner-checks 5 | ScreenVersion: 1.4 6 | SerialVersion: 6 7 | MinPlatformVersion: 0 8 | ProOnly: True 9 | Author: Thomas Patzke 10 | ShortDescription: Provides some additional passive Scanner checks. 11 | EntryPoint: Burp-MissingScannerChecks.py 12 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |
This extension provides some additional passive Scanner checks:
2 |All checks can be enabled separately in an extension tab and a default config can be stored.
15 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This burp extension implements some passive scanner checks which are 2 | missing in Burp suite: 3 | 4 | * DOM-based XSS (REs are based on those from https://code.google.com/p/domxsswiki/wiki/FindingDOMXSS) 5 | 6 | * Missing HTTP headers: 7 | 8 | * Strict-Transport-Security 9 | 10 | * X-Content-Type-Options: nosniff 11 | 12 | * X-XSS-Protection 13 | 14 | * Multiple occurrences of the checked headers. 15 | 16 | * Redirection from HTTP to HTTPS 17 | 18 | All checks can be enabled separately in an own extension tab and a default config can be stored. 19 | 20 | TODO 21 | ==== 22 | 23 | * See TODO markers in the code. 24 | 25 | * Further possibilities to redirect from HTTP to HTTPS (meta refresh, links, referer checking) 26 | 27 | * Active scanner check: Actively test directories for listings 28 | 29 | * Active scanner check: Add parameters like debug, admin, test etc. and check if something 30 | interesting appears on the page. 31 | 32 | * Active Scanner check: Reaction of the web application and server to 33 | requests with different/missing host headers. 34 | -------------------------------------------------------------------------------- /Burp-MissingScannerChecks.py: -------------------------------------------------------------------------------- 1 | # Burp Additonal Scanner Checks Extension 2 | # Copyright 2017 Thomas PatzkeDOM-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 |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: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 behavior 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 | --------------------------------------------------------------------------------