Example #1
0
def processStructured(report: ReportWrapper, token: str='') -> VulnTestInfo:
    """ Process the given report into a AutoTriageUtils.VulnTestInfo named
        tuple given that it contains structured data """
    info = extractJson(report.getLatestActivity())
    if info is None:
        return VulnTestInfo(reproduced=False,
                            message=('Failed to parse JSON! Please try again.'),
                            type='XSS',
                            info={'report': report.getLatestActivity()})

    # Pass it off to a helper that can try to handle any inconsistencies
    url, cookies, type, data = extractDataFromJson(info)

    if not AutoTriageUtils.isProgramURL(url):
        return VulnTestInfo(reproduced=False,
                            message=('The url provided (`%s`) is not a program URL!') % url,
                            type='XSS',
                            info={'src': url,
                                  'method': 'structured'})

    if type.lower() == 'post':
        results = testPOSTXSS(url, cookies, data)
    elif type.lower() == 'get':
        results = testGETXSS(url, cookies)
    else:
        return VulnTestInfo(reproduced=False,
                            message='Found an invalid value "type"=%s in the JSON blob!' % type,
                            type='XSS',
                            info={'src': url,
                                  'method': 'structured'})

    reproduced, alertBox, message, confirmedBrowsers, alertBrowsers = makeMarkdownTable(results, token)
    if reproduced:
        return VulnTestInfo(reproduced=True,
                            message='Successfully found and confirmed an XSS at `%s`!\n'
                                    '\n\n%s\n\n'
                                    'Metadata: {"vulnDomain": "%s"}' %
                                    (url, message, urlparse(url).hostname),
                            type='XSS',
                            info={'src': url,
                                  'method': 'unstructured',
                                  'confirmedBrowsers': confirmedBrowsers,
                                  'alertBrowsers': alertBrowsers,
                                  'httpType': type,
                                  'cookies': cookies})  # noqa
    elif alertBox:
        return VulnTestInfo(reproduced=False,
                            message=('Failed to confirm the vulnerability! Detected an alert box '
                                     'but the token: `"%s"` was not found!'
                                     '\n\n%s\n\n') % (token, message),
                            type='XSS',
                            info={'src': url,
                                  'method': 'unstructured'})
    else:
        return VulnTestInfo(reproduced=False,
                            message=("Failed to validate XSS at `%s` via structured data. Either try "
                                     "again or wait for manual review of your bug.") % url,
                            type='XSS',
                            info={'method': 'structured'})
Example #2
0
def isDuplicate(r1: ReportWrapper, r2: ReportWrapper) -> DuplicateResult:
    """ Returns a confidence rating on whether the two given reports are duplicates of each other """
    for module in modules:
        if (module.match(r1.getReportBody(), r1.getReportWeakness())
                and  # type: ignore
                module.match(r2.getReportBody(),
                             r2.getReportWeakness())):  # type: ignore
            return sameCategoryIsDuplicate(
                r1, r2, module.containsExploit)  # type: ignore
    return DuplicateResult((None, ID('A')))
Example #3
0
def process(report: ReportWrapper) -> Optional[VulnTestInfo]:
    """ Process the given report into a VulnTestInfo named tuple """
    # If the user has not yet been prompted for automatic triaging
    if not report.botHasCommented():
        token = AutoTriageUtils.generateToken()
        return VulnTestInfo(reproduced=False,
                            message=constants.initialMessage(token, 'redirect to a domain', 'Open Redirect'),
                            type='Open Redirect',
                            info={})
    elif report.shouldBackoff():
        if not report.hasPostedBackoffComment():
            addFailureToDB(report.getReporterUsername(), report.getReportID())
            return VulnTestInfo(reproduced=False,
                                message=('Automatic verification of vulnerability has failed, Backing off! Falling '
                                         'back to human verification. '),
                                type='Open Redirect',
                                info={})
        else:
            return None
    elif report.isVerified():
        return None
    try:
        if isStructured(report.getLatestActivity()):
            return processStructured(report, token=report.getToken())
        else:
            return processUnstructured(report, token=report.getToken())
    except Exception as e:
        print("Caught exception: %s" % str(e))
        traceback.print_exc()
        print("+" * 80)
        return VulnTestInfo(reproduced=False,
                            message=('Internal error detected! Backing off...'),
                            type='Open Redirect',
                            info={})
Example #4
0
def getMetadata(id: str):
    ser = requests.post('http://api:8080/v1/getReport',
                        json={
                            'id': id
                        },
                        auth=HTTPBasicAuth('AutoTriageBot',
                                           secrets.apiBoxToken)).text
    metadataComment = ReportWrapper().deserialize(
        ser).extractMetadata()  # type: ignore
    firstLine = metadataComment.splitlines()[0].replace('# ', '*') + '*'
    return '\n'.join([firstLine] + metadataComment.splitlines()[1:])
Example #5
0
def test_isVerified(monkeypatch):
    r = ReportWrapper()
    monkeypatch.setattr(r, '_ReportWrapper__getBody', lambda a: a)
    monkeypatch.setattr(r, '_getPublicCommentsByUsername', lambda u: ['Comment 1', 'Comment 2'])
    monkeypatch.setattr(r, 'getState', lambda: "new")
    assert r.isVerified() is False
    monkeypatch.setattr(r, 'getState', lambda: "triaged")
    assert r.isVerified() is True
    monkeypatch.setattr(r, 'getState', lambda: "new")
    monkeypatch.setattr(r, '_getPublicCommentsByUsername',
                        lambda u: ['Comment 1', 'Comment 2', "Message\nMetadata: {\"vulnDomain\": etc..."])
    assert r.isVerified() is True
Example #6
0
def processStructured(report: ReportWrapper, token: str='') -> VulnTestInfo:
    """ Process the given report into a VulnTestInfo named tuple given that it contains structured data """
    info = extractJson(report.getLatestActivity())
    if info is None:
        return VulnTestInfo(reproduced=False,
                            message=('Failed to parse JSON! Please try again.'),
                            type='Open Redirect',
                            info={'report': report.getLatestActivity()})

    # Pass it off to a helper that can try to handle any inconsistencies
    url, cookies, type, data = extractDataFromJson(info)

    if not isProgramURL(url):
        return VulnTestInfo(reproduced=False,
                            message=('The url provided (`%s`) is not a program URL!') % url,
                            type='Open Redirect',
                            info={'src': url,
                                  'method': 'structured'})

    if type.lower() == 'post':
        res = testPOSTOpenRedirect(url, cookies, data)
    elif type.lower() == 'get':
        res = testGETOpenRedirect(url, cookies)
    else:
        return VulnTestInfo(reproduced=False,
                            message='Found an invalid value "type"=%s in the JSON blob!' % type,
                            type='Open Redirect',
                            info={'src': url,
                                  'method': 'structured'})

    if res and token.lower() in urlparse(res).hostname.lower():
        return VulnTestInfo(reproduced=True,
                            message=('Successfully found and confirmed an open redirect from `%s` to `%s`!\n'
                                     'Metadata: {"vulnDomain": "%s"}') % (url, res, urlparse(url).hostname),
                            type='Open Redirect',
                            info={'src': url,
                                  'redirect': res,
                                  'method': 'structured',
                                  'httpType': type,
                                  'cookies': cookies})
    elif res:
        return VulnTestInfo(reproduced=False,
                            message=tokenNotFoundMessage % (url, res, token, token),
                            type='Open Redirect',
                            info={'src': url,
                                  'redirect': res,
                                  'method': 'structured'})
    else:
        return VulnTestInfo(reproduced=False,
                            message=("Failed to validate open redirect at `%s` via structured data. Either try again "
                                     "or wait for manual review of your bug.") % url,
                            type='Open Redirect',
                            info={'method': 'structured'})
Example #7
0
def test_api():
    ids = json.loads(
        requests.post('http://api:8080/v1/getReportIDs',
                      json={
                          'time': '1970-01-01T00:00:00Z',
                          'openOnly': False
                      },
                      auth=HTTPBasicAuth('AutoTriageBot',
                                         secrets.apiBoxToken)).text)
    openIDs = json.loads(
        requests.post('http://api:8080/v1/getReportIDs',
                      json={
                          'time': '1970-01-01T00:00:00Z',
                          'openOnly': True
                      },
                      auth=HTTPBasicAuth('AutoTriageBot',
                                         secrets.apiBoxToken)).text)
    assert isinstance(ids, list)
    assert isinstance(openIDs, list)
    assert len(openIDs) <= len(
        ids
    )  # There should be an equal or lesser number of open bugs than all bugs
    assert all([(id in ids)
                for id in openIDs])  # All open ids should be in ids
    for id in ids:
        # They should be strings but they should be parseable into integers
        assert isinstance(id, str) and isinstance(int(id), int)
    # There should be no duplicate IDs
    assert len(set(ids)) == len(ids)

    for id in ids[:10]:
        ser = requests.post('http://api:8080/v1/getReport',
                            json={
                                'id': id
                            },
                            auth=HTTPBasicAuth('AutoTriageBot',
                                               secrets.apiBoxToken)).text
        try:
            r = ReportWrapper().deserialize(ser)
        except:
            assert False

    for serRep in json.loads(
            requests.post('http://api:8080/v1/getReports',
                          auth=HTTPBasicAuth('AutoTriageBot',
                                             secrets.apiBoxToken)).text)[:10]:
        try:
            r = ReportWrapper().deserialize(serRep)
        except:
            assert False
        assert r.getReportID() in ids
Example #8
0
def suggestPayout(report: ReportWrapper) -> Optional[BountyInfo]:
    """ Returns a BountyInfo containing a suggested payout and the standard deviation for the given report """
    if xss.match(report.getReportBody(), report.getReportWeakness()):
        return suggestPayoutGivenType(config.payoutDB['xss'],
                                      report.getVulnDomains())
    if openRedirect.match(report.getReportBody(), report.getReportWeakness()):
        return suggestPayoutGivenType(config.payoutDB['open redirect'],
                                      report.getVulnDomains())
    if sqli.match(report.getReportBody(), report.getReportWeakness()):
        return suggestPayoutGivenType(config.payoutDB['sqli'],
                                      report.getVulnDomains())
    return None
Example #9
0
def shouldProcessReport(report: ReportWrapper) -> bool:
    """ Whether the bot should process the given ReportWrapper """
    username = report.getReporterUsername()
    return (shouldProcess_blacklist(username)
            and shouldProcess_whitelist(username)
            and shouldProcess_failures(username)
            and shouldProcess_match(report))
Example #10
0
def getReport(id: str) -> ReportWrapper:
    """ Get the ReportWrapper describing the report with the given ID number """
    resp = requests.post('http://api:8080/v1/getReport',
                         json={'id': id},
                         auth=HTTPBasicAuth('AutoTriageBot',
                                            secrets.apiBoxToken))
    return ReportWrapper().deserialize(Serialized(resp.text))
Example #11
0
def test_match():
    assert xss.match(ReportWrapper(domXSSInitReport).getReportBody(),
                     ReportWrapper(domXSSInitReport).getReportWeakness())
    assert xss.match(ReportWrapper(genericXSSInitReport).getReportBody(),
                     ReportWrapper(genericXSSInitReport).getReportWeakness())
    assert xss.match(ReportWrapper(reflectedXSSInitReport).getReportBody(),
                     ReportWrapper(reflectedXSSInitReport).getReportWeakness())
    assert xss.match(ReportWrapper(storedXSSInitReport).getReportBody(),
                     ReportWrapper(storedXSSInitReport).getReportWeakness())
Example #12
0
def getBody(id: str):
    ser = requests.post('http://api:8080/v1/getReport',
                        json={
                            'id': id
                        },
                        auth=HTTPBasicAuth('AutoTriageBot',
                                           secrets.apiBoxToken)).text
    return ReportWrapper().deserialize(ser).getReportBody()  # type: ignore
Example #13
0
def processUnstructured(report: ReportWrapper,
                        token: str = '') -> VulnTestInfo:
    """ Process the given report into a VulnTestInfo named tuple given that it doesn't contain structured data """
    urls = extractURLs(report.getLatestActivity())
    if config.DEBUG:
        print("URLs=%s" % str(urls))
    if len(urls) > 5:
        if config.DEBUG:
            print("User submitted %s URLs. Skipping...")
        return VulnTestInfo(
            reproduced=False,
            message='Found %s URLs. Please resubmit with a single URL to test.',
            type='SQLi',
            info={
                'URLs': str(urls),
                'method': 'structured'
            })
    testedURLs = []
    for url in urls:
        if isProgramURL(url):
            # Unstructured reports are treated as a GET
            delay = testGETSQLDelay(url, {})
            if delay and abs(delay - int(token)) < maxTimeDiff:
                return VulnTestInfo(
                    reproduced=True,
                    message=('Successfully found and confirmed SQLi at `%s`!\n'
                             'Metadata: {"vulnDomain": "%s"}') %
                    (url, urlparse(url).hostname),
                    type='SQLi',
                    info={
                        'src': url,
                        'method': 'unstructured',
                        'delay': int(delay),
                        'httpType': 'GET',
                        'cookies': {}
                    })
            elif delay:
                return VulnTestInfo(reproduced=False,
                                    message=wrongDelayMessage %
                                    (str(int(delay)), token, token),
                                    type='SQLi',
                                    info={
                                        'src': url,
                                        'method': 'unstructured'
                                    })
            else:
                testedURLs.append(url)
    if len(testedURLs) > 0:
        return VulnTestInfo(reproduced=False,
                            message=constants.structuredDataMessage % ('SQLi'),
                            type='SQLi',
                            info={'method': 'unstructured'})
    else:
        return VulnTestInfo(reproduced=False,
                            message=constants.failedToFindURLsMessage,
                            type='SQLi',
                            info={'method': 'unstructured'})
Example #14
0
def getReport() -> str:
    """ Get the serialized version of the report at the given ID """
    data = request.get_json(force=True)
    id = data['id']

    if config.DEBUGVERBOSE:
        print("/v1/getReport: id=%s" % id)

    j = getEndpoint("https://api.hackerone.com/v1/reports/%s" % id)
    return ReportWrapper(j['data']).serialize()
Example #15
0
 def rdToRW(r: ReportData) -> ReportWrapper:
     """ Convert a ReportData to a ReportWrapper """
     rw = ReportWrapper()
     monkeypatch.setattr(rw, 'getReportTitle', lambda: r.title)
     monkeypatch.setattr(rw, 'getReportBody', lambda: r.body)
     monkeypatch.setattr(rw, 'getReportedTime', lambda: r.time)
     monkeypatch.setattr(rw, 'getState', lambda: r.state)
     monkeypatch.setattr(rw, 'getReportID', lambda: r.id)
     monkeypatch.setattr(rw, 'getReportWeakness', lambda: r.weakness)
     return rw
Example #16
0
def getAllOpenReports(time: datetime) -> List[ReportWrapper]:
    """ Get a list of all the open reports """
    reports = [
        ReportWrapper().deserialize(ser) for ser in json.loads(
            requests.post('http://api:8080/v1/getReports',
                          auth=HTTPBasicAuth('AutoTriageBot',
                                             secrets.apiBoxToken)).text)
    ]
    return list(
        filter(lambda r: r.getReportedTime() < time, (filter(
            lambda r: r.getState() in ['new', 'triaged', 'needs-more-info'],
            reports))))
Example #17
0
def processUnstructured(report: ReportWrapper, token: str='') -> AutoTriageUtils.VulnTestInfo:
    """ Process the given report into a AutoTriageUtils.VulnTestInfo named tuple
        given that it doesn't contain structured data """
    urls = extractURLs(report.getLatestActivity())
    if config.DEBUG:
        print("URLs=%s" % str(urls))
    if len(urls) > 5:
        if config.DEBUG:
            print("User submitted %s URLs. Skipping...")
        return VulnTestInfo(reproduced=False,
                            message='Found %s URLs. Please resubmit with a single URL to test.',
                            type='XSS',
                            info={'URLs': str(urls),
                                  'method': 'structured'})
    testedURLs = []
    for url in urls:
        if AutoTriageUtils.isProgramURL(url):
            testedURLs.append(url)
            results = testGETXSS(url, {})
            reproduced, alertBox, message, confirmedBrowsers, alertBrowsers = makeMarkdownTable(results, token)
            if reproduced:
                return VulnTestInfo(reproduced=True,
                                    message=('Successfully found and confirmed an XSS at `%s`!\n'
                                             '\n\n%s\n\n'
                                             'Metadata: {"vulnDomain": "%s"}') %
                                            (url, message, urlparse(url).hostname),
                                    type='XSS',
                                    info={'src': url,
                                          'method': 'unstructured',
                                          'confirmedBrowsers': confirmedBrowsers,
                                          'alertBrowsers': alertBrowsers,
                                          'httpType': 'GET',
                                          'cookies': {}})
            elif alertBox:
                return VulnTestInfo(reproduced=False,
                                    message=('Failed to confirm the vulnerability! Detected an alert '
                                             'box but the token: `"%s"` was not found!'
                                             '\n\n%s\n\n') % (token, message),
                                    type='XSS',
                                    info={'src': url,
                                          'method': 'unstructured'})
    if len(testedURLs) > 0:
        return VulnTestInfo(reproduced=False,
                            message=constants.structuredDataMessage % 'XSS',
                            type='XSS',
                            info={'method': 'unstructured'})
    else:
        return VulnTestInfo(reproduced=False,
                            message=constants.failedToFindURLsMessage,
                            type='XSS',
                            info={'method': 'unstructured'})
Example #18
0
def getReports() -> str:
    """ Get all of the reports on the program
         - For H1, getReports *is* different from [getReport(id) for id in getReportIDs(0)] because the
           /v1/getReports API endpoint returns all the reports at once, but the comments are not included.
           So if you need access to the comments, use getReport(id). But if you only need the report body
           then getReports is faster since it does not make as many requests.

         Returns string encoded JSON that is a list of serialized ReportWrappers """
    if config.DEBUGVERBOSE:
        print("/v1/getReports")

    url = "https://api.hackerone.com/v1/reports?filter[program][]=%s&page[size]=100" % config.programName
    return json.dumps(
        [ReportWrapper(j).serialize() for j in getEndpointPaginated(url)])
Example #19
0
def test_verifyProcess(monkeypatch):
    from AutoTriageBot import verify
    time = datetime.datetime.now()
    monkeypatch.setattr(verify, 'postComment', Counter())
    report = ReportWrapper()
    monkeypatch.setattr(report, 'needsBotReply', lambda: False)
    assert verify.postComment.count == 0
    assert verify.processReport(report, time) is None
    assert verify.postComment.count == 0
    monkeypatch.setattr(report, 'needsBotReply', lambda: True)
    monkeypatch.setattr(report, 'getReportedTime',
                        lambda: datetime.datetime.now())
    monkeypatch.setattr(
        verify.config, 'genesis',
        datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc))
    monkeypatch.setattr(verify.config, 'DEBUG', False)
    monkeypatch.setattr(report, 'getReportBody', lambda: "XSS report")
    monkeypatch.setattr(report, 'getReportTitle', lambda: "XSS report")
    monkeypatch.setattr(report, 'getReportWeakness', lambda: "XSS")
    monkeypatch.setattr(report, 'getReportID', lambda: '-1')
    vti = VulnTestInfo(reproduced=False, message="VTI", info={}, type='type')
    for module in verify.modules:
        monkeypatch.setattr(module, 'process', lambda r: vti)
        monkeypatch.setattr(module, 'match', lambda u, v: True)
    monkeypatch.setattr(report, 'needsBotReply', lambda: True)
    assert report.needsBotReply()
    assert verify.postComment.count == 0
    assert verify.processReport(report, time) == vti
    assert verify.postComment.count == 1
    assert verify.postComment.lastCall == (('-1', vti), {
        'addStopMessage': True
    })
    for module in verify.modules:
        monkeypatch.setattr(module, 'match', lambda b, w: False)
    assert verify.postComment.count == 1
    assert verify.processReport(report, time) is None
    assert verify.postComment.count == 1
Example #20
0
def generateMetadataVTI(report: ReportWrapper, vti: VulnTestInfo) -> VulnTestInfo:
    """ Given the results of a vulnerability test thar reproduced a vulnerability and a report, generate an internal
        VTI used to hold metadata about the vulnerability """
    internalMetadata = {'id': report.getReportID(),
                        'title': report.getReportTitle(),
                        'reportedTime': str(report.getReportedTime()),
                        'verifiedTime': str(datetime.now()),
                        'type': vti.type,
                        'exploitURL': vti.info['src'],
                        'method': vti.info['method']}
    if vti.type == 'XSS':
        internalMetadata['confirmedBrowsers'] = vti.info['confirmedBrowsers']
        internalMetadata['alertBrowsers'] = vti.info['alertBrowsers']
        internalMetadata['httpType'] = vti.info['httpType']
        internalMetadata['cookies'] = vti.info['cookies']
    elif vti.type == 'SQLi':
        internalMetadata['delay'] = vti.info['delay']
        internalMetadata['httpType'] = vti.info['httpType']
        internalMetadata['cookies'] = vti.info['cookies']
    elif vti.type == 'Open Redirect':
        internalMetadata['redirect'] = vti.info['redirect']
        internalMetadata['httpType'] = vti.info['httpType']
        internalMetadata['cookies'] = vti.info['cookies']
    message = '# Internal Metadata: \n\n```\n%s\n```\n' % json.dumps(internalMetadata,
                                                                     sort_keys=True,
                                                                     indent=4,
                                                                     separators=(',', ': '))

    if config.DEBUGVERBOSE:
        print(internalMetadata)

    internalVTI = VulnTestInfo(reproduced=False,
                               message=message,
                               info={},
                               type='')
    return internalVTI
Example #21
0
def processReport(report: ReportWrapper) -> None:
    """ Process the given report and post a private comment with a suggested bounty """
    if config.payoutDB:
        bountyInfo = suggestPayout(report)
        if bountyInfo:
            postComment(report.getReportID(),
                        VulnTestInfo(
                            reproduced=False,
                            info={},
                            message='Suggested bounty: %.2f with a σ of %.2f' %
                            (bountyInfo.average, bountyInfo.std),
                            type=''),
                        internal=True)
    else:
        if config.DEBUGVERBOSE:
            print("Not suggesting a payout beause config.payoutDB is falsy")
Example #22
0
def processUnstructured(report: ReportWrapper, token: str='') -> VulnTestInfo:
    """ Process the given report into a VulnTestInfo named tuple given that it doesn't contain structured data """
    urls = extractURLs(report.getLatestActivity())
    if config.DEBUG:
        print("URLs=%s" % str(urls))
    if len(urls) > 5:
        if config.DEBUG:
            print("User submitted %s URLs. Skipping...")
        return VulnTestInfo(reproduced=False,
                            message=('Found %s URLs. Please resubmit with a single URL to test.'),
                            type='Open Redirect',
                            info={'URLs': str(urls),
                                  'method': 'structured'})
    testedURLs = []
    for url in urls:
        if isProgramURL(url):
            res = testGETOpenRedirect(url, {})
            print("res=%s" % str(urlparse(res).hostname))
            if res and token.lower() in urlparse(res).hostname.lower():
                return VulnTestInfo(reproduced=True,
                                    message=('Successfully found and confirmed an open redirect from `%s` to `%s`!\n'
                                             'Metadata: {"vulnDomain": "%s"}') % (url, res, urlparse(url).hostname),
                                    type='Open Redirect',
                                    info={'src': url,
                                          'redirect': res,
                                          'method': 'unstructured',
                                          'httpType': 'GET',
                                          'cookies': {}})  # nopep8
            elif res:
                return VulnTestInfo(reproduced=False,
                                    message=tokenNotFoundMessage % (url, res, token, token),
                                    type='Open Redirect',
                                    info={'src': url,
                                          'redirect': res,
                                          'method': 'unstructured'})
            else:
                testedURLs.append(url)
    if len(testedURLs) > 0:
        return VulnTestInfo(reproduced=False,
                            message=constants.structuredDataMessage % 'open redirect',
                            type='Open Redirect',
                            info={'method': 'unstructured'})
    else:
        return VulnTestInfo(reproduced=False,
                            message=constants.failedToFindURLsMessage,
                            type='Open Redirect',
                            info={'method': 'unstructured'})
Example #23
0
def processReport(report: ReportWrapper, startTime: datetime) -> Optional[VulnTestInfo]:
    """ Attempt to verify a given report """
    if report.needsBotReply():
        if startTime > report.getReportedTime():
            return None
        if config.DEBUG:
            print("Processing %s" % report.getReportTitle())
        for module in modules:
            if module.match(report.getReportBody(), report.getReportWeakness()):  # type: ignore
                if config.DEBUG:
                    print(module.__file__.split('/')[-1] + " matched id=%s!" % report.getReportID())
                vti = module.process(report)  # type: ignore
                if config.DEBUGVERBOSE:
                    print(vti)
                if vti:
                    postComment(report.getReportID(), vti, addStopMessage=True)
                    if vti.reproduced and config.metadataLogging:
                        metadataVTI = generateMetadataVTI(report, vti)
                        postComment(report.getReportID(), metadataVTI, internal=True)
                return vti
        if config.DEBUG:
            print("No matches")
    return None
Example #24
0
def test_processUnstructured(monkeypatch):
    monkeypatch.setattr(sqli, 'isProgramURL', lambda u: True)
    report = ReportWrapper()
    monkeypatch.setattr(report, 'isVerified', lambda: False)
    monkeypatch.setattr(report, 'botHasCommented', lambda: False)
    monkeypatch.setattr(sqli, 'getRandInt', lambda: '12')
    assert sqli.process(report) == VulnTestInfo(reproduced=False,
                                                message=sqli.initialMessage %
                                                ('12', '12', '12'),
                                                type='SQLi',
                                                info={})
    monkeypatch.setattr(report, 'botHasCommented', lambda: True)
    monkeypatch.setattr(report, 'shouldBackoff', lambda: True)
    monkeypatch.setattr(report, 'hasPostedBackoffComment', lambda: False)
    monkeypatch.setattr(report, 'getReporterUsername',
                        lambda: 'TestFailureUser')
    monkeypatch.setattr(report, 'getReportID', lambda: '-1')
    oldCount = sqlite.countFailures("TestFailureUser")
    assert sqli.process(report) == VulnTestInfo(
        reproduced=False,
        message=('Automatic verification of vulnerability has failed, Backing '
                 'off! Falling '
                 'back to human verification. '),
        type='SQLi',
        info={})
    assert sqlite.countFailures("TestFailureUser") == (oldCount + 1)
    monkeypatch.setattr(report, 'hasPostedBackoffComment', lambda: True)
    assert sqli.process(report) is None
    monkeypatch.setattr(report, 'shouldBackoff', lambda: False)
    monkeypatch.setattr(report, 'getLatestActivity', lambda: "")
    monkeypatch.setattr(report, 'getToken', lambda: "12")
    monkeypatch.setattr(report, 'isVerified', lambda: True)
    assert sqli.process(report) is None
    monkeypatch.setattr(report, 'isVerified', lambda: False)
    assert (sqli.process(report) == sqli.processUnstructured(
        report, token=report.getToken()) == VulnTestInfo(
            reproduced=False,
            message=constants.failedToFindURLsMessage,
            type='SQLi',
            info={'method': 'unstructured'}))
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ("```\n"
         "http://vulnserver/sqli.php?q=12\n"
         "```"))
    vti = sqli.process(report)
    assert vti.reproduced is True
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ("```\n"
         "http://vulnserver/noVulnerability.html"
         "```\n"
         "```\n"
         "http://vulnserver/sqli.php?q=12\n"
         "```"))
    vti = sqli.process(report)
    assert sqli.process(report) == sqli.processUnstructured(
        report, token=report.getToken()) == vti
    assert vti.reproduced is True
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ("```\n"
         "http://vulnserver/sqli.php?q=15\n"
         "```"))
    vti = sqli.process(report)
    assert vti.reproduced is False
    assert vti.message == (sqli.wrongDelayMessage % ('15', '12', '12'))
Example #25
0
def test_processStructured(monkeypatch):
    monkeypatch.setattr(sqli, 'isProgramURL', lambda u: True)
    report = ReportWrapper()
    monkeypatch.setattr(report, 'isVerified', lambda: False)
    monkeypatch.setattr(sqli, 'getRandInt', lambda: '12')
    monkeypatch.setattr(report, 'botHasCommented', lambda: True)
    monkeypatch.setattr(report, 'shouldBackoff', lambda: False)
    monkeypatch.setattr(report, 'getLatestActivity', lambda: "")
    monkeypatch.setattr(report, 'getToken', lambda: "12")
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ('# AutoTriage Structured Data: \n'
         '```\n'
         '{No JSON!}'
         '```\n\n'))
    vti = sqli.process(report)
    assert vti.reproduced is False
    assert 'Failed to parse JSON! Please try again.' in vti.message
    monkeypatch.setattr(sqli, 'isProgramURL', lambda u: False)
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ('# AutoTriage Structured Data: \n'
         '```\n'
         '{\n'
         '    "URL": "http://vulnserver/sqliIfCookie.php?q=12",\n'
         '    "cookies": {"NAME": "VALUE"}, \n'
         '    "type": "get" \n'
         '}\n'
         '```\n\n'))
    vti = sqli.process(report)
    assert vti.reproduced is False
    assert 'is not a program URL!' in vti.message
    monkeypatch.setattr(sqli, 'isProgramURL', lambda u: True)
    assert 12 < sqli.testGETSQLDelay('http://vulnserver/sqliIfCookie.php?q=12',
                                     {'NAME': 'VALUE'}) < 13
    vti = sqli.process(report)
    assert vti.reproduced is True
    assert 'Successfully found and confirmed SQLi at' in vti.message
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ('# AutoTriage Structured Data: \n'
         '```\n'
         '{\n'
         '    "URL": "http://vulnserver/sqliIfCookiePost.php",\n'
         '    "cookies": {"NAME": "VALUE"}, \n'
         '    "type": "post", \n'
         '    "data": {"q": "12"} \n'
         '}\n'
         '```\n'))
    vti = sqli.process(report)
    assert vti.reproduced is True
    assert 'Successfully found and confirmed SQLi at' in vti.message
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ('# AutoTriage Structured Data: \n'
         '```\n'
         '{\n'
         '    "URL": "http://vulnserver/sqliIfCookiePost.php",\n'
         '    "cookies": {"NAME": "VALUE"}, \n'
         '    "type": "INVALID", \n'
         '    "data": {"q": "12"} \n'
         '}\n'
         '```\n'))
    vti = sqli.process(report)
    assert vti.reproduced is False
    assert 'Found an invalid value' in vti.message
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ('# AutoTriage Structured Data: \n'
         '```\n'
         '{\n'
         '    "URL": "http://vulnserver/sqliIfCookiePost.php",\n'
         '    "cookies": {"NAME": "VALUE"}, \n'
         '    "type": "post", \n'
         '    "data": {"q": "18"} \n'
         '}\n'
         '```\n'))
    vti = sqli.process(report)
    assert vti.reproduced is False
    assert "In order to verify the vulnerability, it must have delayed for" in vti.message
    monkeypatch.setattr(
        report, 'getLatestActivity', lambda:
        ('# AutoTriage Structured Data: \n'
         '```\n'
         '{\n'
         '    "URL": "http://vulnserver/sqliIfCookiePost.php",\n'
         '    "cookies": {"NAME": "WRONG"}, \n'
         '    "type": "post", \n'
         '    "data": {"q": "12"} \n'
         '}\n'
         '```\n'))
    vti = sqli.process(report)
    assert vti.reproduced is False
    assert "Failed to validate SQLi at" in vti.message
Example #26
0
def sameCategoryIsDuplicate(r1: ReportWrapper, r2: ReportWrapper, containsExploit: Callable[[str], bool]) -> \
        DuplicateResult:
    """ Returns a confidence rating on whether the two given reports are duplicates of each other given that they are
        of the same type of vulnerability and that containsExploit returns whether or not a given URL is exploiting
        that class of vulnerability. """
    # The links are the only things we refer to in our current duplicate detection algorithm
    links1, links2 = getLinks(r1.getReportBody()), getLinks(r2.getReportBody())
    malLinks1 = [
        link for link in links1
        if containsExploit(link) or containsExploit(unquote(link))
    ]
    malLinks2 = [
        link for link in links2
        if containsExploit(link) or containsExploit(unquote(link))
    ]

    if set(malLinks1) & set(malLinks2):
        return DuplicateResult((99, ID('B')))
    if set(links1) & set(links2):
        return DuplicateResult((90, ID('C')))

    parsedMalLinks1 = list(
        filter(lambda n: n, map(AutoTriageUtils.parseURL, malLinks1)))
    parsedMalLinks2 = list(
        filter(lambda n: n, map(AutoTriageUtils.parseURL, malLinks2)))
    parsedLinks1 = list(
        filter(lambda n: n, map(AutoTriageUtils.parseURL, links1)))
    parsedLinks2 = list(
        filter(lambda n: n, map(AutoTriageUtils.parseURL, links2)))

    malDomainParameterTuples1 = flatten([[(x.domain, x.path, key)
                                          for key, val in x.queries.items()
                                          if containsExploit(val)]
                                         for x in parsedMalLinks1])
    malDomainParameterTuples2 = flatten([[(x.domain, x.path, key)
                                          for key, val in x.queries.items()
                                          if containsExploit(val)]
                                         for x in parsedMalLinks2])

    parametersInCommon = (
        set(flatten([parsed.queries.keys() for parsed in parsedLinks1]))
        & set(flatten([parsed.queries.keys() for parsed in parsedLinks2])))
    malParametersInCommon = (
        set(flatten([parsed.queries.keys() for parsed in parsedMalLinks1]))
        & set(flatten([parsed.queries.keys() for parsed in parsedMalLinks2])))

    injectionParametersInCommon = (
        set([param for domain, path, param in malDomainParameterTuples1])
        & set([param for domain, path, param in malDomainParameterTuples2]))

    malPathsInCommon = (set([
        path for domain, path, param in malDomainParameterTuples1 if path != ''
    ]) & set([
        path for domain, path, param in malDomainParameterTuples2 if path != ''
    ]))
    pathsInCommon = (
        set([parsed.path for parsed in parsedLinks1 if parsed.path != ''])
        & set([parsed.path for parsed in parsedLinks2 if parsed.path != '']))

    domains1 = set(
        [x.domain for x in parsedLinks1 if '[server]' not in x.domain])
    domains2 = set(
        [x.domain for x in parsedLinks2 if '[server]' not in x.domain])

    domainsInCommon = domains1 & domains2
    malDomainsInCommon = (
        set([x.domain
             for x in parsedMalLinks1 if '[server]' not in x.domain]) &
        set([x.domain for x in parsedMalLinks2 if '[server]' not in x.domain]))

    return decide(len(malLinks1), len(malLinks2), len(parametersInCommon),
                  len(malParametersInCommon), len(pathsInCommon),
                  len(malPathsInCommon), len(domainsInCommon),
                  len(malDomainsInCommon), len(injectionParametersInCommon),
                  len(domains1 ^ domains2))
Example #27
0
def shouldProcess_match(report: ReportWrapper) -> bool:
    """ Whether the bot should process the given ReportWrapper according to whether any of the modules match it """
    return any([
        m.match(report.getReportBody(), report.getReportWeakness())
        for m in modules
    ])  # type: ignore
Example #28
0
def processReport(report: ReportWrapper) -> bool:
    """ Process a report via searching for duplicates and posting comments based off of the confidence levels
          Returns whether or not the report was classified as a duplicate with a high confidence """
    if report.getState() == "new" and not report.hasDuplicateComment(
    ) and not report.isVerified():
        earlierReports = getAllOpenReports(
            report.getReportedTime())  # type: List[ReportWrapper]
        idConfTuples = []  # type: List[Tuple[str, int]]
        matches = []  # type: List[str]
        for earlierReport in earlierReports:
            for module in modules:
                if (module.match(report.getReportBody(),
                                 report.getReportWeakness())
                        and  # type: ignore
                        module.match(earlierReport.getReportBody(),
                                     earlierReport.getReportWeakness())
                    ):  # type: ignore
                    matches.append(earlierReport.getReportID())
            try:
                confidence = int(isDuplicate(earlierReport, report)[0])
            except TypeError:
                confidence = 0
            if confidence == 99:
                AutoTriageUtils.postComment(
                    report.getReportID(),
                    VulnTestInfo(
                        message='Found a duplicate with 99%% confidence: #%s' %
                        earlierReport.getReportID(),
                        info={},
                        reproduced=False,
                        type=''),
                    internal=True)
                if config.DEBUG:
                    print("Detected that %s (%s) is a duplicate of %s (%s)!" %
                          (report.getReportID(), report.getReportTitle(),
                           earlierReport.getReportID(),
                           earlierReport.getReportTitle()))
                return False  # Change to return True to make the bot stop interacting after finding a duplicate
            elif confidence > 50:
                idConfTuples.append((earlierReport.getReportID(), confidence))
        # If you update the phrases here, you must also update them in AutoTriageUtils.ReportWrapper.hasDuplicateComment
        if len(idConfTuples) > 0:

            def idConfToStr(tuple: Tuple) -> str:
                return (
                    'Detected a possible duplicate report with confidence of %s: #%s'
                    % (tuple[1], tuple[0]))

            AutoTriageUtils.postComment(report.getReportID(),
                                        VulnTestInfo(message='\n'.join([
                                            idConfToStr(t)
                                            for t in idConfTuples
                                        ]),
                                                     info={},
                                                     reproduced=False,
                                                     type=''),
                                        internal=True)
            if config.DEBUG:
                print('Found partial matches: %s' % str(idConfTuples))
        if len(matches) > 0 and len(matches) <= 5:
            AutoTriageUtils.postComment(
                report.getReportID(),
                VulnTestInfo(message=(
                    'There are currently %s open reports about this type of '
                    'vulnerability: %s' %
                    (str(len(matches)), ', '.join(['#' + id
                                                   for id in matches]))),
                             info={},
                             reproduced=False,
                             type=''),
                internal=True)
            if config.DEBUG:
                print(
                    'Found %s reports on the same type of vulnerability as %s: %s'
                    % (str(len(matches)), str(report.getReportID()), ', '.join(
                        ['#' + id for id in matches])))
    return False
Example #29
0
def test_metadataLogging(monkeypatch):
    from AutoTriageBot import verify
    mvti = VulnTestInfo(reproduced=True,
                        message='',
                        type='XSS',
                        info={
                            'src': 'AAA',
                            'method': 'BBB',
                            'confirmedBrowsers': 'CCC',
                            'alertBrowsers': 'DDD',
                            'httpType': 'EEE',
                            'cookies': 'FFF'
                        })
    r = ReportWrapper()
    monkeypatch.setattr(r, 'getReportID', lambda: 'GGG')
    monkeypatch.setattr(r, 'getReportTitle', lambda: 'HHH')
    monkeypatch.setattr(r, 'getReportedTime', lambda: 'III')
    ivti = verify.generateMetadataVTI(r, mvti)
    j = extractJson(ivti.message)

    def standardAsserts(j):
        assert j['id'] == 'GGG'
        assert j['title'] == 'HHH'
        assert j['reportedTime'] == 'III'
        assert 'verifiedTime' in j.keys(
        )  # we can't monkeypatch datetime, so just checking that it exists
        assert j['exploitURL'] == 'AAA'
        assert j['method'] == 'BBB'
        assert j['httpType'] == 'EEE'
        assert j['cookies'] == 'FFF'

    # XSS:
    standardAsserts(j)
    assert j['type'] == 'XSS'
    assert j['confirmedBrowsers'] == 'CCC'
    assert j['alertBrowsers'] == 'DDD'
    # SQLi:
    mvti = VulnTestInfo(reproduced=True,
                        message='',
                        type='SQLi',
                        info={
                            'src': 'AAA',
                            'method': 'BBB',
                            'delay': '12',
                            'httpType': 'EEE',
                            'cookies': 'FFF'
                        })
    ivti = verify.generateMetadataVTI(r, mvti)
    j = extractJson(ivti.message)
    standardAsserts(j)
    assert j['type'] == 'SQLi'
    assert j['delay'] == '12'
    # Open Redirect:
    mvti = VulnTestInfo(reproduced=True,
                        message='',
                        type='Open Redirect',
                        info={
                            'src': 'AAA',
                            'method': 'BBB',
                            'redirect': 'CCC',
                            'httpType': 'EEE',
                            'cookies': 'FFF'
                        })
    ivti = verify.generateMetadataVTI(r, mvti)
    j = extractJson(ivti.message)
    standardAsserts(j)
    assert j['type'] == 'Open Redirect'
    assert j['redirect'] == 'CCC'
Example #30
0
def test_process(monkeypatch):
    r = ReportWrapper()
    monkeypatch.setattr(r, 'getState', lambda: 'not new')
    assert duplicates.processReport(r) is False
    monkeypatch.setattr(r, 'getState', lambda: 'new')
    monkeypatch.setattr(r, 'hasDuplicateComment', lambda: True)
    assert duplicates.processReport(r) is False

    monkeypatch.setattr(r, 'getReportTitle', lambda: 'Title')
    monkeypatch.setattr(r, 'getReportBody', lambda: 'Body')
    monkeypatch.setattr(r, 'getReportedTime', lambda: None)
    monkeypatch.setattr(r, 'getReportID', lambda: '0')
    monkeypatch.setattr(r, 'getReportWeakness', lambda: 'Weakness')
    monkeypatch.setattr(r, 'isVerified', lambda: False)

    monkeypatch.setattr(r, 'hasDuplicateComment', lambda: False)
    monkeypatch.setattr(duplicates, 'getAllOpenReports', lambda a: [])
    c = Counter()
    monkeypatch.setattr(duplicates.AutoTriageUtils, 'postComment', c)
    assert c.count == 0
    assert duplicates.processReport(r) is False
    assert c.count == 0
    # We don't post comments when there are no duplicate reports
    r2 = ReportData(title='A',
                    body='A',
                    time=None,
                    state='new',
                    id='1',
                    weakness='XSS')

    def rdToRW(r: ReportData) -> ReportWrapper:
        """ Convert a ReportData to a ReportWrapper """
        rw = ReportWrapper()
        monkeypatch.setattr(rw, 'getReportTitle', lambda: r.title)
        monkeypatch.setattr(rw, 'getReportBody', lambda: r.body)
        monkeypatch.setattr(rw, 'getReportedTime', lambda: r.time)
        monkeypatch.setattr(rw, 'getState', lambda: r.state)
        monkeypatch.setattr(rw, 'getReportID', lambda: r.id)
        monkeypatch.setattr(rw, 'getReportWeakness', lambda: r.weakness)
        monkeypatch.setattr(rw, 'isVerified', lambda: False)
        return rw

    r2 = rdToRW(r2)

    monkeypatch.setattr(duplicates, 'getAllOpenReports', lambda a: [r2])
    monkeypatch.setattr(duplicates.modules[0], 'match', lambda a, b: True)
    monkeypatch.setattr(duplicates, 'isDuplicate', lambda a, b: (99, 'A'))
    c = Counter()
    monkeypatch.setattr(duplicates.AutoTriageUtils, 'postComment', c)
    assert c.count == 0
    assert duplicates.processReport(r) is False
    assert c.count == 1
    assert c.lastCall == (('0',
                           VulnTestInfo(message=(
                               'Found a duplicate with 99% confidence: #1'),
                                        info={},
                                        reproduced=False,
                                        type='')), {
                                            'internal': True
                                        })
    monkeypatch.setattr(duplicates, 'isDuplicate', lambda a, b: (50, 'A'))
    c = Counter()
    monkeypatch.setattr(duplicates.AutoTriageUtils, 'postComment', c)
    assert c.count == 0
    assert duplicates.processReport(r) is False
    assert c.count == 1
    assert c.lastCall == (('0',
                           VulnTestInfo(message=(
                               'There are currently 1 open reports about'
                               ' this type of vulnerability: #1'),
                                        info={},
                                        reproduced=False,
                                        type='')), {
                                            'internal': True
                                        })
    monkeypatch.setattr(duplicates.modules[0], 'match', lambda a, b: False)
    monkeypatch.setattr(duplicates.modules[1], 'match', lambda a, b: False)
    monkeypatch.setattr(duplicates.modules[2], 'match', lambda a, b: False)
    monkeypatch.setattr(duplicates, 'isDuplicate', lambda a, b: (0, 'A'))
    c = Counter()
    monkeypatch.setattr(duplicates.AutoTriageUtils, 'postComment', c)
    assert c.count == 0
    assert duplicates.processReport(r) is False
    assert c.count == 0