Skip to content

Instantly share code, notes, and snippets.

@martinbydefault
Forked from thiezn/hackerone_programs.py
Created August 13, 2021 05:01
Show Gist options
  • Select an option

  • Save martinbydefault/5aaa992850a9b2efe2658f7a72bbb700 to your computer and use it in GitHub Desktop.

Select an option

Save martinbydefault/5aaa992850a9b2efe2658f7a72bbb700 to your computer and use it in GitHub Desktop.
HackerOne API Program and scope retrieval
#!/usr/bin/env python3
"""Interact with HackerOne Hacker API.
First generate an API token through the Hackerone website and initialize the class:
>>> username = "YOUR_USER_NAME"
>>> token = "GENERATE_AN_API_TOKEN_THROUGH_HACKERONE_WEBSITE"
>>> session = HackerOneSession(username, token)
To retrieve a single program
>>> hackerone_program = session.get_program("security")
>>> print(hackerone_program)
<HackerOneProgram HackerOne, 16 assets>
>>> print(hackerone_program.program.offers_bounties)
True
To list all programs run the following. Note it will take some time to retrieve
all programs. Please be concious of your fellow hackers and limit the amount of
API calls you make.
Note that when listing programs, the assets won't be returned. Use the get_program() or
get_assets() API call for this.
>>> all_programs = session.list_programs()
>>> for program in all_programs:
>>> print(program)
<HackerOneProgram Node.js third-party modules, 0 assets>
<HackerOneProgram Internet Freedom (IBB), 0 assets>
...truncated output...
>>> for asset in session.list_assets(all_programs[0]):
>>> print(asset)
<HackerOneAsset URL api.example.com>
"""
import requests
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, Set
from enum import Enum
class HackerOneAssetType(Enum):
"""Class representing known types in HackerOne assets."""
URL = "URL"
OTHER = "OTHER"
GOOGLE_PLAY_APP_ID = "GOOGLE_PLAY_APP_ID"
APPLE_STORE_APP_ID = "APPLE_STORE_APP_ID"
WINDOWS_APP_STORE_APP_ID = "WINDOWS_APP_STORE_APP_ID"
CIDR = "CIDR"
SOURCE_CODE = "SOURCE_CODE"
DOWNLOADABLE_EXECUTABLES = "DOWNLOADABLE_EXECUTABLES"
HARDWARE = "HARDWARE"
OTHER_APK = "OTHER_APK"
OTHER_IPA = "OTHER_IPA"
@dataclass
class HackerOneAsset:
"""Class representing an asset of a HackerOne Program."""
id: str
type: HackerOneAssetType
identifier: str
eligible_for_bounty: bool
eligible_for_submission: bool
max_severity: str
created_at: datetime
updated_at: datetime
instuction: Optional[str]
reference: Optional[str]
confidentiality_requirement: Optional[str]
integrity_requirement: Optional[str]
availability_requirement: Optional[str]
def __repr__(self) -> str:
"""Pretty representation of class instance."""
return f"<HackerOneAsset {self.type} {len(self.identifier)}>"
@classmethod
def load_from_dict(cls, asset_dict: dict):
"""Initialize class instance from Dictionary object."""
return cls(
asset_dict["id"],
HackerOneAssetType(asset_dict["attributes"]["asset_type"]),
asset_dict["attributes"]["asset_identifier"],
asset_dict["attributes"]["eligible_for_bounty"],
asset_dict["attributes"]["eligible_for_submission"],
asset_dict["attributes"]["max_severity"],
datetime.fromisoformat(asset_dict["attributes"]["created_at"].rstrip("Z")),
datetime.fromisoformat(asset_dict["attributes"]["updated_at"].rstrip("Z")),
asset_dict["attributes"].get("instruction"),
asset_dict["attributes"].get("reference"),
asset_dict["attributes"].get("confidentiality_requirement"),
asset_dict["attributes"].get("integrity_requirement"),
asset_dict["attributes"].get("availability_requirement"),
)
def __hash__(self):
"""Allow for use in Python Sets."""
return hash(self.id)
def __eq__(self, other):
"""Compare two class instances."""
if other.id == self.id:
return True
return False
@dataclass
class HackerOneProgram:
"""Class representing a single HackerOne Program."""
id: str
# Program attributes
handle: str
name: str
currency: str
profile_picture: str
submission_state: str
triage_active: str
state: str
started_accepting_at: datetime
number_of_reports_for_user: int
number_of_valid_reports_for_user: int
bounty_earned_for_user: float
last_invitation_accepted_at_for_user: Optional[str]
bookmarked: bool
allows_bounty_splitting: bool
offers_bounties: bool
# Assets
assets: Set[HackerOneAsset]
def __repr__(self) -> str:
"""Pretty representation of class instance."""
return f"<HackerOneProgram {self.name}, {len(self.assets)} assets>"
@property
def program_url(self) -> str:
"""The URL to the program on HackerOne."""
return f"https://hackerone.com/{self.handle}?type=team"
@classmethod
def load_from_dict(cls, program_dict: dict):
"""Initialize class instance from Dictionary object."""
try:
assets = {
HackerOneAsset.load_from_dict(asset)
for asset in program_dict["relationships"]["structured_scopes"]["data"]
}
except KeyError:
# When listing programs the assets are not returned.
assets = set()
return cls(
program_dict["id"],
program_dict["attributes"]["handle"],
program_dict["attributes"]["name"],
program_dict["attributes"]["currency"],
program_dict["attributes"]["profile_picture"],
program_dict["attributes"]["submission_state"],
program_dict["attributes"]["triage_active"],
program_dict["attributes"]["state"],
datetime.fromisoformat(
program_dict["attributes"]["started_accepting_at"].rstrip("Z")
),
program_dict["attributes"]["number_of_reports_for_user"],
program_dict["attributes"]["number_of_valid_reports_for_user"],
program_dict["attributes"]["bounty_earned_for_user"],
program_dict["attributes"]["last_invitation_accepted_at_for_user"],
program_dict["attributes"]["bookmarked"],
program_dict["attributes"]["allows_bounty_splitting"],
program_dict["attributes"]["offers_bounties"],
assets,
)
def __hash__(self):
"""Allow for use in Python Sets."""
return hash(self.id)
def __eq__(self, other):
"""Compare two class instances."""
if other.id == self.id:
return True
return False
class HackerOneSession:
"""Class to interact with the Hacker API of HackerOne."""
def __init__(self, username, token, version="v1"):
self._session = requests.session()
self.version = version
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"Hello": "HackerOne!",
}
self._session.auth = (username, token)
self._session.headers.update(headers)
def _process_response(self, response):
"""Process HTTP response returned from API."""
if not response.ok:
# TODO: Shall we sleep and retry on 'response.status_code == 429'?
raise IOError(f"HTTP {response.status_code} {response.request.url}")
return response.json()
def _get(self, endpoint, params: dict = None):
"""Retrieve a HTTP GET endpoint."""
url = self._url(endpoint)
response = self._session.get(url, params=params)
return self._process_response(response)
def _url(self, endpoint) -> str:
"""Generate full API url."""
url = f"https://api.hackerone.com/{self.version}/hackers/{endpoint}"
return url
def list_programs(self) -> Set[HackerOneProgram]:
"""Retrieve a list of programs."""
endpoint = "/programs"
programs = set()
page_number = 1
while True:
response = self._get(endpoint, params={"page[number]": page_number})
if not response["links"].get("next") or not response.get("data"):
break
else:
page_number += 1
programs.update(
[
HackerOneProgram.load_from_dict(program)
for program in response["data"]
]
)
return programs
def get_program(self, program_handle) -> HackerOneProgram:
"""Retrieve a program by handle."""
endpoint = f"/programs/{program_handle}"
response = self._get(endpoint)
return HackerOneProgram.load_from_dict(response)
def get_assets(self, program_handle) -> Set[HackerOneAsset]:
"""Get the assets of given program.
This is a helper function to return only the assets on a program. Useful
when you have retrieved a list of programs as this doesn't include assets.
"""
endpoint = f"/programs/{program_handle}"
response = self._get(endpoint)
try:
assets = {
HackerOneAsset.load_from_dict(asset)
for asset in response["relationships"]["structured_scopes"]["data"]
}
except KeyError:
# When listing programs the assets are not returned.
assets = set()
return assets
if __name__ == "__main__":
from getpass import getpass
username = input("Hackerone username: ").strip()
token = getpass(f"{username} token: ").strip()
session = HackerOneSession(username, token)
print(session.get_program("security"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment