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}')
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")
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)
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)
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))
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))
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_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)
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)
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))
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)