Documentation Index
Fetch the complete documentation index at: https://specterops-enable-tls-feedback.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
This is the API that drives BloodHound Enterprise and Community Edition.
Use it to extend the use of the BloodHound product to work with other tools in your environment.
Endpoint availability is noted using the Community and Enterprise tags.
To get help with BloodHound Community Edition, join our Slack community.
BloodHound Enterprise customers can submit tickets.
Authentication
The BloodHound API supports two kinds of authentication: JWT bearer tokens and signed requests.
For quick tests or one-time calls, the JWT used by your browser may be the simplest route.
For more secure and long lived API integrations, we recommend using signed requests.
JWT bearer token
The API accepts calls using the following header structure in the HTTP request:
Authorization: Bearer $JWT_TOKEN
If you open the Network tab in your browser, you’ll see calls against the API made using
this structure. JWT bearer tokens are supported by the BloodHound API, but they
should only be used for temporary access. JWT tokens expire after a set amount of time and require
re-authentication using secret credentials.
Signed requests
Signed requests are the recommended form of authentication for the BloodHound API. Not only are
signed requests better for long lived integrations, they also provide more security for the
requests being sent. They provide authentication of the client, as well as verification of request
integrity when received by the server.
Signed requests consist of three main parts: The client token ID, the request timestamp, and a
base64 encoded HMAC signature. These three pieces of information are sent with the request using
the following header structure:
Authorization: bhesignature $TOKEN_ID
RequestDate: $RFC3339_DATETIME
Signature: $BASE64ENCODED_HMAC_SIGNATURE
To use signed requests, you need to generate an API token. Each API token generated in the
BloodHound API comes with two parts: The Token ID, which is used in the Authorization header,
and the Token Key, which is used as part of the HMAC hashing process. The token ID should be
considered as public (like a username) and the token key should be considered secret (like a
password). Once an API token is generated, you can use the key to sign requests.
For more documentation about how to work with authentication in the API, including examples
of how to generate an API token in the BloodHound UI, see Working With the BloodHound API.
Example: Signed request pseudo-code
First, a digest is initiated with HMAC-SHA-256 using the token key as the digest key:
digester = hmac.new(sha256, api_token_key)
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.write(request_method + request_uri)
# Update the digester for further chaining
digester = hmac.New(sha256, digester.hash())
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
request_datetime = date.now()
digester.write(request_datetime[:13])
# Update the digester for further chaining
digester = hmac.New(sha256, digester.hash())
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:
if request.body is not empty:
digester.write(request.body)
Finally, base64 encode the final hash and write the three required headers before
sending the request:
encoded_hash = base64_encode(digester.hash())
request.header.write('Authorization', 'bhesignature ' + token_id)
request.header.write('RequestDate', request_datetime)
request.header.write('Signature', encoded_hash)