Working with the BloodHound API

This article applies to BHCE and BHE

The BloodHound product family are API-first products, meaning everything functions on the underlying API layer. All data displayed in the portal, all commands given to SharpHound or AzureHound Enterprise collectors, and all data uploaded pass through the BloodHound APIs. Customers may utilize these APIs to extend the use of the BloodHound product to function with other tools in their environment. This article will show how to access the API and include some example use cases.

API Documentation

API documentation is hosted utilizing Swagger behind authentication within your tenant environment. You may access it by clicking the cog in the top right corner of your tenant after logging in, and clicking "API Explorer".

mceclip0.png

Using the API

The BloodHound API accepts two forms of authentication, each with its own limitations for security.

  • A JWT is generated through the login process utilizing your username, password, and 2FA token (or SAML-based authentication flow). These JWT tokens are active for 8 hours and are primarily for end-user access to the web-based application.
  • An API key/ID pair is generated within the Administration interface. These do not expire and are primarily for long-term API integrations.

Generating an API Key/ID Pair

Create an API key/ID pair in your "My Profile" section.

  1. Log in to your BloodHound tenant
  2. Click the cog in the top right, then "My Profile".
  3. Click on "API Key Management".
  4. Click "Create Token".
    mceclip4.png
  5. Give the token a descriptive name and click "Save".
    mceclip5.png
  6. Save the presented API key and ID and click "Close".

    Note: The API key will never be shown again. If you lose the API key, you must regenerate the API key/ID pair.
    mceclip6.png
  7. This key/ID pair may now be utilized in any integration work you plan to take on.

Calling the API

Once you have your token, you'll want to call the API to get data, make changes, or otherwise interact with the product.

Using a JWT/Bearer Token

For quick tests or one-time calls, the JWT used by your browser may be the simplest route. The API will accept calls using the following header structure in the HTTP request:

'Authorization': Bearer $JWT_TOKEN

If you open the Network tab within your browser, you will see calls against the API made utilizing this structure.

Using your API Key/ID Pair

For long-running API integrations, BloodHound's API utilize hash-based message authentication code (HMAC) authentication using the API key as the secret key to verify the authenticity and integrity of the request. Calls against the API must include the following in the signed hash:

  • API key
  • HTTP method and URI
  • Current time
  • Body content (if applicable to the request)

Calls against the API would need to include the following headers in the HTTP request:

'Authorization': bhesignature $TOKEN_ID
'RequestDate': $RFC3339_DATETIME
'Signature': $BASE64ENCODED_HMAC_SIGNATURE

By validating the hash signature against the request, the API can validate that the calls were made by the original requestor, within a reasonable timeframe, against the proper API endpoint, including the original content body, and that no replay or content modification has occurred.

The attached Python script is a full example implementation for calling the BloodHound APIs utilizing the API key/ID pair, however, the code block specific to calling the API has been extracted below.

def_request(self, method: str, uri: str, body: Optional[bytes] = None) -> requests.Response:
# Digester is initialized with HMAC-SHA-256 using the token key as the HMAC digest key.
digester = hmac.new(self._credentials.token_key.encode(), None, hashlib.sha256)

# OperationKey is the first HMAC digest link in the signature chain. This prevents replay attacks that seek to
# modify the request method or URI. It is composed of concatenating the request method and the request URI with
# no delimiter and computing the HMAC digest using the token key as the digest secret.
#
# Example: GET /api/v2/test/resource HTTP/1.1
# Signature Component: GET/api/v2/test/resource
digester.update(f'{method}{uri}'.encode())

# Update the digester for further chaining
digester = hmac.new(digester.digest(), None, hashlib.sha256)

# DateKey is the next HMAC digest link in the signature chain. This encodes the RFC3339 formatted datetime
# value as part of the signature to the hour to prevent replay attacks that are older than max two hours. This
# value is added to the signature chain by cutting off all values from the RFC3339 formatted datetime from the
# hours value forward:
#
# Example: 2020-12-01T23:59:60Z
# Signature Component: 2020-12-01T23
datetime_formatted = datetime.datetime.now().astimezone().isoformat('T')
digester.update(datetime_formatted[:13].encode())

# Update the digester for further chaining
digester = hmac.new(digester.digest(), None, hashlib.sha256)

# Body signing is the last HMAC digest link in the signature chain. This encodes the request body as part of
# the signature to prevent replay attacks that seek to modify the payload of a signed request. In the case
# where there is no body content the HMAC digest is computed anyway, simply with no values written to the
# digester.
ifbodyisnotNone:
digester.update(body)

# Perform the request with the signed and expected headers
return requests.request(
method=method,
url=self._format_url(uri),
headers={
'User-Agent': 'bhe-python-sdk 0001',
'Authorization': f'bhesignature {self._credentials.token_id}',
'RequestDate': datetime_formatted,
'Signature': base64.b64encode(digester.digest()),
'Content-Type': 'application/json',
},
data=body,
)

Updated