TheHive

Turn Stairwell trigger notifications into TheHive alerts, with a worked Cloud Function that translates the webhook into TheHive's alerting API.

TheHive is an open-source security incident management platform. Its alerting API accepts alerts from outside systems, which is how Stairwell findings become items in TheHive's triage queue.

TheHive does not accept Stairwell's webhook directly, so this integration needs a small piece of middleware to translate between the two. The example below uses a Google Cloud Function, but nothing about the approach is specific to Google: any HTTP endpoint you can run will do, and the translation is about thirty lines.

What do I need before I start?

  • A webhook URI from TheHive.
  • Somewhere to run a small function that Stairwell can reach.
  • A TheHive API key.

How does the translation work?

Stairwell POSTs its trigger notification to your function. The function authenticates the request, pulls the matched hashes out of the payload, builds a TheHive alert with those hashes as observables, and creates it through TheHive's API.

Configure Stairwell with a webhook address of the shape:

https://us-central1-<YOUR PROJECT>.cloudfunctions.net/<YOUR FUNCTION NAME>?key=<SOME RANDOM KEY>

The key parameter is your own shared secret. Cloud Functions can be invoked by anyone who knows the URL, so the function checks this value before doing anything. Choose something long and random, and treat it as a credential.

A worked function, Python 3.9

import json
import uuid

from thehive4py.api import TheHiveApi
from thehive4py.models import Alert, AlertArtifact


def main(request):
    # JSON payload POSTed by the Stairwell webhook.
    request_json = request.get_json()
    key = request.args.get('key')

    # The key can be anything you like. It is what stops an arbitrary
    # caller from creating alerts in your Hive instance.
    if key == '<RANDOM KEY USED IN URL>':
        print('Authenticated')
        print(request_json)
        trigger_name = request_json["name"]
        return alert_the_hive(trigger_name, request_json)

    return 'Unknown key'


def alert_the_hive(alert_title, json_payload):
    THEHIVE_URL = 'http://<YOUR HIVE INSTANCE>:9000'
    THEHIVE_API_KEY = '<YOUR HIVE API KEY>'
    api = TheHiveApi(THEHIVE_URL, THEHIVE_API_KEY)

    # One observable per matched object.
    artifacts = []
    for h in json_payload['matches']:
        artifacts.append(AlertArtifact(dataType='hash', data=h['sha256']))

    alert = Alert(
        title=alert_title,
        tlp=3,
        description=json.dumps(json_payload, indent=2),
        type='external',
        source='Stairwell',
        sourceRef=str(uuid.uuid4())[0:6],
        artifacts=artifacts,
    )

    try:
        response = api.create_alert(alert)
        return json.dumps(response.json(), indent=4, sort_keys=True)
    except Exception as e:
        return "Alert create error: {}".format(e)

Three things in that function are worth understanding before you adapt it.

json_payload['matches'] is an array. A single trigger notification can carry many matched objects, which is why the loop exists. One notification becomes one alert holding many observables, rather than many alerts.

The whole payload becomes the description. Dumping the JSON verbatim is crude and it is also the right default, because it means no field Stairwell sends is lost in translation. Once you know which fields your analysts actually read, replace it with something shaped.

tlp=3 is TLP:RED. That is the example's choice, not a recommendation. Set it to whatever your sharing policy calls for.

How do I configure Stairwell?

  1. Log into Stairwell.
  2. Select the settings icon.
  3. Open the Event notifications tab.
  4. Select Create event notification.
  5. Give it a name.
  6. Select the conditions that should notify through this webhook. At least one is required.
  7. Paste your function's URI, including the key parameter, into Webhook URI. Set Version to V1.
  8. Select Create.

Step 6 is where you decide how noisy this becomes. See Event Notifications and, once it is running, Trigger Silences.

How do I test it?

Point the notification at a request inspector first, confirm you are getting the payload shape the function expects, then switch to the function. Debugging JSON parsing inside a Cloud Function is considerably less pleasant than reading the request that would have gone into it.

Where do I get help?

[email protected].

What should I read next?


Did this page help you?