def find_programs(self):
     for program in settings.H1_PROGRAMS:
         client = HackerOneClient(program.api_username, program.api_password)
         resp = client.request_json('/me/programs')
         program_ids = (p['id'] for p in resp['data'])
         for program_id in program_ids:
             yield client.request_json(f'/programs/{program_id}')
示例#2
0
    def test_get_resource(self):
        report_text = json.dumps(load_resource_blob("responses/report"))
        with requests_mock.mock() as m:
            m.get(HackerOneClient.BASE_URL + "/reports/1", text=report_text)

            client = HackerOneClient("a", "b")
            report = client.get_resource(Report, 1)
            self.assertEqual(report.title, "XSS in login form")
示例#3
0
    def find_reports(self, **kwargs):
        '''
        Find all HackerOne reports for the program, passing any
        keyword arguments on to HackerOneClient.find_resources().
        '''

        client = HackerOneClient(self.api_username, self.api_password)
        return client.find_resources(h1_models.Report,
                                     program=[self.handle],
                                     **kwargs)
示例#4
0
    def test_request_json(self):
        report_blob = load_resource_blob("responses/report")
        client = HackerOneClient("a", "b")
        json_url = HackerOneClient.BASE_URL + "/reports/1"
        with requests_mock.mock() as m:
            m.get(json_url, text=json.dumps(report_blob))

            report_json = client.request_json("/reports/1")
            self.assertDictEqual(report_blob, report_json)
            # Make sure absolute URLs work as well
            report_json = client.request_json(json_url)
            self.assertDictEqual(report_blob, report_json)
示例#5
0
    def test_find_resources(self):
        listing_text = json.dumps(
            load_resource_blob("responses/report-list-onepage"))
        with requests_mock.mock() as m:
            filters = {"foo_gt": "bar"}
            filter_str = "filter%5Bprogram%5D%5B%5D=foo&filter%5Bfoo_gt%5D=bar"
            m.get(HackerOneClient.BASE_URL + "/reports?" + filter_str,
                  text=listing_text)

            client = HackerOneClient("a", "b")
            listing = client.find_resources(Report, program=["foo"], **filters)

            # The listing should contain two reports
            self.assertEqual(len(listing), 2)
            self.assertTrue(all(isinstance(r, Report) for r in listing))
示例#6
0
    def test_find_resources_yield_pages(self):
        page_one = json.dumps(
            load_resource_blob("responses/report-list-twopage-0"))
        page_two = json.dumps(
            load_resource_blob("responses/report-list-twopage-1"))
        with requests_mock.mock() as m:
            filter_str = "filter%5Bprogram%5D%5B%5D=foo"
            m.get(HackerOneClient.BASE_URL + "/reports?" + filter_str,
                  text=page_one)
            m.get(HackerOneClient.BASE_URL + "/reports/nextpage",
                  text=page_two)

            client = HackerOneClient("a", "b")
            listing = client.find_resources(Report,
                                            program=["foo"],
                                            yield_pages=True)

            # The listing should contain two pages each with one report
            self.assertEqual(len(listing), 2)
            # Flatten the pages into a single page
            all_items = list(itertools.chain(*listing))
            self.assertEqual(len(all_items), 2)
            self.assertTrue(all(isinstance(r, Report) for r in all_items))
示例#7
0
 def __init__(self, identifier, token):
     self.verbose = True
     self.client = HackerOneClient(identifier, token)
示例#8
0
class HackerOneAlchemy(object):
    def __init__(self, identifier, token):
        self.verbose = True
        self.client = HackerOneClient(identifier, token)

    def find_reports(self, filters):
        return self.client.find_resources(
            Report, program=[settings["hackerone_program"]], **filters)

    def print_report_stats(self, reports, awarded_reports):
        stat_data = self.get_report_stats(reports, awarded_reports)

        print("\nTOTAL REPORT STATS\n==================")
        print("Total reports:", stat_data["total_reports"])
        for state, count in stat_data["state_counts"].items():
            print("Total ", state.title(), ": ", count, sep="")
        print("Mean resolution time:", stat_data["mean_resolution_time"])
        print("Mean first response time:",
              stat_data["mean_first_response_time"])
        print("Signal to Noise ratio:", stat_data["signal_to_noise_ratio"])
        print("Number of flagged reports:", stat_data["flagged_reports"])
        print("Total bounty amount awarded:",
              stat_data["total_bounties_awarded_amount"])
        print("Closing reports as 'Spam': Priceless")

    def get_report_stats(self, reports, awarded_reports):
        stat_data = {}
        stat_data["total_reports"] = len(reports)

        total_bounties = sum(r.total_bounty for r in awarded_reports)
        stat_data["total_bounties_awarded_amount"] = '${:,.2f}'.format(
            total_bounties)

        state_counts = dict((state, 0) for state in Report.STATES)
        for report in reports:
            state_counts[report.state] += 1
        stat_data["state_counts"] = state_counts

        total_good = sum(state_counts[x] for x in ("resolved", "triaged"))
        total_meh = sum(state_counts[x]
                        for x in ("informative", "spam", "not-applicable"))
        snr = 0.0
        if total_meh:
            snr = total_good / float(total_meh)
        stat_data["signal_to_noise_ratio"] = snr

        stat_data["flagged_reports"] = len(
            self.reports_containing_words(reports,
                                          settings["flagged_keywords"]))

        response_times = []
        for r in reports:
            if not r.time_to_first_response:
                continue
            response_times.append(r.time_to_first_response.total_seconds())
        mean_first_response_time = None
        if response_times:
            mean_first_response_secs = sum(response_times) / len(
                response_times)
            mean_first_response_time = dt.timedelta(
                seconds=mean_first_response_secs)
        stat_data["mean_first_response_time"] = mean_first_response_time

        resolution_times = []
        for r in reports:
            if r.state != "resolved":
                continue
            if not r.time_to_closed:
                continue
            resolution_times.append(r.time_to_closed.total_seconds())
        mean_resolution_time = None
        if resolution_times:
            mean_resolution_secs = sum(resolution_times) / len(
                resolution_times)
            mean_resolution_time = dt.timedelta(seconds=mean_resolution_secs)
        stat_data["mean_resolution_time"] = mean_resolution_time

        return stat_data

    def reports_containing_words(self, reports, word_list):
        matching_reports = []
        for report in reports:
            for word in word_list:
                if self.word_in_report(report, word):
                    matching_reports.append(report)
                    break
        return matching_reports

    def word_in_report(self, report, word):
        text_fields = (report.vulnerability_information, report.title)
        return any(word in field.lower() for field in text_fields)

    def get_bonus_information(self, reports):
        # Assumes `reports` is in reverse chronological order by creation date
        accepted_by_reporter = collections.defaultdict(list)

        for report in reports:
            if report.state in {"resolved", "triaged"}:
                accepted_by_reporter[report.reporter].append(report)

        reporter_rewards = {}
        for reporter, reports_by_reporter in accepted_by_reporter.items():
            if len(reports_by_reporter) > BONUS_REQUIRED_REPORTS:
                reporter_rewards[reporter] = self.calc_report_bonuses(
                    reports_by_reporter)

        return reporter_rewards

    def calc_report_bonuses(self, reports):
        report_bonuses = {}

        # The first four reports are not eligible
        eligible_reports = reports[:-BONUS_REQUIRED_REPORTS]

        for report in eligible_reports:
            other_reports = [
                r for r in reports if r is not report and r.total_bounty
            ]
            report_bonuses[report] = self.calc_average_bounty(
                other_reports) * Decimal('0.10')

        return report_bonuses

    def calc_average_bounty(self, reports):
        awarded_reports = [r for r in reports if r.total_bounty is not None]
        if not awarded_reports:
            return Decimal('0.00')
        return sum(r.total_bounty
                   for r in awarded_reports) / len(awarded_reports)

    def print_bonus_information(self, reports):
        bonuses_dict = self.get_bonus_information(reports)

        print("\nUSERS ELIGIBLE FOR BONUS\n==================")

        for reporter, reports_by_reporter in bonuses_dict.items():
            print("Username:"******"HackerOne Profile: https://hackerone.com/" +
                  reporter.username)
            print("Reports (All eligible bugs received since date): ")
            for report, bonus in reports_by_reporter.items():
                print("\t", '${:,.2f}'.format(bonus), report.html_url,
                      report.state, "'%s'\n" % report.title)

    def comments_since_last_response(self, report):
        comments_since_last_response = 0
        # Iterate in reverse order so it's in chronological order
        for activity in reversed(report.activities):
            by_reporter = (activity.actor == report.reporter)
            if isinstance(activity, ActivityComment):
                if by_reporter:
                    comments_since_last_response += 1
                elif not activity.internal:
                    comments_since_last_response = 0
            # Changing the report state counts as a response
            if isinstance(activity, ActivityStateChange) and not by_reporter:
                comments_since_last_response = 0

        return comments_since_last_response

    def statusmsg(self, msg):
        # TODO: replace this with `logging` module
        if self.verbose:
            print("[ STATUS ]", msg)
示例#9
0
 def _init_hacker_one_client(self):
     self.client = HackerOneClient(self.__identifier, self.__token)
     self.client.REQUEST_HEADERS['Content-Type'] = 'application/json'
     self.client.s.headers.update(self.client.REQUEST_HEADERS)
示例#10
0
class HackerOneReports:
    def __init__(self, identifier, token, program):
        """Inits hacker_one_client.  This requires that an API Token has been created via
           https://hackerone.com/{program}/api.

           The api token needs to be apart of the "standard" group to be able to have appropriate permissions
           to be able to change report state, post comments, ect..

        :type identifier: string
        :param identifier: api token identifier

        :type token: string
        :param token: token

        :type program: string
        :param program: HackerOne report
        """
        self.__identifier = identifier
        self.__token = token
        self.__program = program
        self._init_hacker_one_client()

    def _init_hacker_one_client(self):
        self.client = HackerOneClient(self.__identifier, self.__token)
        self.client.REQUEST_HEADERS['Content-Type'] = 'application/json'
        self.client.s.headers.update(self.client.REQUEST_HEADERS)

    def get_reports_activity_x_days_ago(self, days=30):
        """Get Reports that have a last_activity_date > days

        :type days: int
        :param days: represents the days ago

        :returns: returns list of HackerOne Report objects
        """
        day_ago = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=days)
        logging.info("Checking for Reports since: " + str(day_ago))

        return self.client.find_resources(Report,
                                          program=[self.program],
                                          last_activity_at__gt=day_ago)

    def get_reports_activity_since_x_datetime(self, datetime):
        """Get Reports that have a last_activity_date > date

        :type datetime: datetime
        :param datetime: represents the days ago
        :return: returns list of HackerOne Report objects
        """
        logging.info("Checking for Reports since: " + str(datetime))

        return self.client.find_resources(Report,
                                          program=[self.__program],
                                          last_activity_at__gt=datetime)

    def get_new_reports(self):
        """
        Pull list of HackerOne Reports in NEW state
        :return: returns list of HackerOne Report objects
        """
        logging.info("Checking for new Reports")
        return self.client.find_resources(Report,
                                          program=[self.program],
                                          state=["new"])

    def get_reports(self):
        """
        Return all reports in a program
        :return: returns list of HackerOne Report objects
        """
        logging.info("Getting All Reports")
        return self.client.find_resources(Report, program=[self.__program])

    def get_report(self, report_id):
        """
        Pulls individual report object.  This Report object will have more details vs a report object
        returned from filter query (ie get_new_reports, or get_reports_activity_x_days_ago).  This report object
        will include activities, and attachments to name a few

        :type report_id: int
        :param report_id:
        :return:
        """
        logging.debug("Checking Report: %s" % report_id)
        report = self.client.get_resource(Report, report_id)

        # uber's hacker one client does not set weakness and severity on the h1.models.report object
        # so well add it afterwards
        add_attributes_to_report(report)

        return report

    def set_issue_tracker_reference_id(self, report, jira_id):
        """Set Issue Tracker Reference ID field in Jira Report.  issue_tracker_reference_id is how we map hackerOne
        reportID to Jira Issue ID

        :type report: h1.models.Report
        :param report: HackerOne Report Object

        :type jira_id: string
        :param jira_id: Jira Issue ID
        :return: returns http response
        """

        json = '''
{
  "data": {
    "type": "issue-tracker-reference-id",
    "attributes": {
      "reference": "%s"
    }
  }
}''' % jira_id

        result = self.client.make_request(report.url +
                                          "/issue_tracker_reference_id",
                                          data=json,
                                          params=None,
                                          method="POST")

        # wait for the request to complete, if it hasn't already
        response = result.result()

        if response.status_code != 200:
            logger.error("failed updating issue-tracker-reference-id")
            raise Exception(
                'Error updating ReportID: %s to JiraID: %s - %s\n%s' %
                (report.id, jira_id, response.status_code, response.reason))
        else:
            logger.debug(
                "Updating HackerOne Report: %s to include %s Jira Reference ID"
                % (report.id, jira_id))
 def __init__(self, identifier, token):
     self.verbose = True
     self.client = HackerOneClient(identifier, token)
class HackerOneAlchemy(object):
    def __init__(self, identifier, token):
        self.verbose = True
        self.client = HackerOneClient(identifier, token)

    def find_reports(self, filters):
        return self.client.find_resources(
            Report,
            program=[settings["hackerone_program"]],
            **filters
        )

    def print_report_stats(self, reports, awarded_reports):
        stat_data = self.get_report_stats(reports, awarded_reports)

        print("\nTOTAL REPORT STATS\n==================")
        print("Total reports:", stat_data["total_reports"])
        for state, count in stat_data["state_counts"].items():
            print("Total ", state.title(), ": ", count, sep="")
        print("Mean resolution time:", stat_data["mean_resolution_time"])
        print("Mean first response time:", stat_data["mean_first_response_time"])
        print("Signal to Noise ratio:", stat_data["signal_to_noise_ratio"])
        print("Number of flagged reports:", stat_data["flagged_reports"])
        print("Total bounty amount awarded:", stat_data["total_bounties_awarded_amount"])
        print("Closing reports as 'Spam': Priceless")

    def get_report_stats(self, reports, awarded_reports):
        stat_data = {}
        stat_data["total_reports"] = len(reports)

        total_bounties = sum(r.total_payout for r in awarded_reports)
        stat_data["total_bounties_awarded_amount"] = '${:,.2f}'.format(total_bounties)

        state_counts = dict((state, 0) for state in Report.STATES)
        for report in reports:
            state_counts[report.state] += 1
        stat_data["state_counts"] = state_counts

        total_good = sum(state_counts[x] for x in ("resolved", "triaged"))
        total_meh = sum(state_counts[x] for x in ("informative", "spam", "not-applicable"))
        snr = 0.0
        if total_meh:
            snr = total_good / float(total_meh)
        stat_data["signal_to_noise_ratio"] = snr

        stat_data["flagged_reports"] = len(self.reports_containing_words(
            reports,
            settings["flagged_keywords"]
        ))

        response_times = []
        for r in reports:
            if not r.time_to_first_response:
                continue
            response_times.append(r.time_to_first_response.total_seconds())
        mean_first_response_time = None
        if response_times:
            mean_first_response_secs = sum(response_times) / len(response_times)
            mean_first_response_time = dt.timedelta(seconds=mean_first_response_secs)
        stat_data["mean_first_response_time"] = mean_first_response_time

        resolution_times = []
        for r in reports:
            if r.state != "resolved":
                continue
            if not r.time_to_closed:
                continue
            resolution_times.append(r.time_to_closed.total_seconds())
        mean_resolution_time = None
        if resolution_times:
            mean_resolution_secs = sum(resolution_times) / len(resolution_times)
            mean_resolution_time = dt.timedelta(seconds=mean_resolution_secs)
        stat_data["mean_resolution_time"] = mean_resolution_time

        return stat_data

    def reports_containing_words(self, reports, word_list):
        matching_reports = []
        for report in reports:
            for word in word_list:
                if self.word_in_report(report, word):
                    matching_reports.append(report)
                    break
        return matching_reports

    def word_in_report(self, report, word):
        text_fields = (report.vulnerability_information, report.title)
        return any(word in field.lower() for field in text_fields)

    def get_bonus_information(self, reports):
        resolved_by_reporter = collections.defaultdict(list)
        return_dict = {}

        for report in reports:
            if report.state == "resolved":
                resolved_by_reporter[report.reporter.username] = report

        for reporter, reports_by_reporter in resolved_by_reporter.items():
            if len(reports_by_reporter) >= 5:
                return_dict[reporter] = reports_by_reporter

        return return_dict

    def print_bonus_information(self, reports):
        bonuses_dict = self.get_bonus_information(reports)

        print("\nUSERS ELIGIBLE FOR BONUS\n==================")

        for reporter, reports_by_reporter in bonuses_dict.items():
            print("Username:"******"HackerOne Profile: https://hackerone.com/" + reporter)
            print("Reports (All Triaged/Resolved bugs received since date): ")
            for report in reports_by_reporter:
                print("\t" + report.html_url, report.state, "'%s'\n" % report.title)

    def comments_since_last_response(self, report):
        comments_since_last_response = 0
        # Iterate in reverse order so it's in chronological order
        for activity in reversed(report.activities):
            by_reporter = (activity.actor == report.reporter)
            if isinstance(activity, ActivityComment):
                if by_reporter:
                    comments_since_last_response += 1
                elif not activity.internal:
                    comments_since_last_response = 0
            # Changing the report state counts as a response
            if isinstance(activity, ActivityStateChange) and not by_reporter:
                comments_since_last_response = 0

        return comments_since_last_response

    def statusmsg(self, msg):
        # TODO: replace this with `logging` module
        if self.verbose:
            print("[ STATUS ]", msg)