Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Enterprise Security Adaptive Response

Adaptive response actions in Enterprise Security run from a notable event, either automatically or when an analyst picks them from the incident review menu. Splunk ships a handful. Writing your own normally means building an add-on.

With BabySOARus, an adaptive response action is a saved search containing exec.

How It Fits Together

ES runs an adaptive response action by dispatching a saved search with the notable's fields available as tokens. If that saved search contains exec, your Python runs with the notable's context and can do whatever the action needs: enrich, decide, call an API, write back to a lookup.

correlation search  ->  notable event  ->  adaptive response  ->  saved search
                                                                       |
                                                                    | exec
                                                                       |
                                                          enrich, decide, act

Registering the Action

default/savedsearches.conf in your ES content app:

[Enrich and triage notable]
action.notable = 0
dispatch.earliest_time = -5m
dispatch.latest_time = now
search = | makeresults \
| eval src = "$src$", dest = "$dest$", user = "$user$", \
       rule_name = "$rule_name$", urgency = "$urgency$" \
| exec inline="..."

[action_adaptive_response]
disabled = 0

And default/alert_actions.conf to make it selectable:

[enrich_and_triage_notable]
is_custom = 1
label = Enrich and triage (BabySOARus)
description = Enrich a notable with asset, identity and intelligence context, and set a recommended action
payload_format = json
param._cam = {"category": ["Information Gathering"], \
              "task": ["update"], \
              "subject": ["endpoint"], \
              "technology": [{"vendor": "BabySOARus", "product": "BabySOARus"}], \
              "supports_adhoc": true, \
              "drilldown_uri": "search?q=..."}

The param._cam block is what makes ES show the action in the incident review menu and in the correlation search editor.

The Action Itself

| makeresults
| eval src="$src$", dest="$dest$", user="$user$", rule_name="$rule_name$"
| exec inline="
findings = []
for e in events:
    src, dest, user = e.get('src', ''), e.get('dest', ''), e.get('user', '')

    # 1. Asset context, from ES's own asset framework.
    asset = {}
    r = splunk_request('GET',
        f'/servicesNS/nobody/SA-IdentityManagement/storage/collections/data/asset_lookup?query={{\"ip\":\"{dest}\"}}&output_mode=json')
    if r['status'] == 200:
        rows = json.loads(r['body'])
        asset = rows[0] if rows else {}

    e['asset_owner'] = asset.get('owner', 'unassigned')
    e['asset_priority'] = asset.get('priority', 'unknown')
    e['asset_category'] = asset.get('category', '')

    # 2. Have we seen this pair before?
    r = splunk_request('GET',
        f'/services/search/jobs/export?output_mode=json&search='
        f'search index=notable src={src} dest={dest} earliest=-30d | stats count')
    seen_before = 0
    if r['status'] == 200:
        for line in r['body'].decode().splitlines():
            if line.strip():
                try:
                    seen_before = int(json.loads(line)['result']['count'])
                except (KeyError, ValueError):
                    pass
    e['prior_notables_30d'] = seen_before

    # 3. Decide.
    if e['asset_priority'] in ('critical', 'high') and seen_before == 0:
        e['recommendation'] = 'escalate to tier 2 immediately'
        e['new_urgency'] = 'critical'
    elif seen_before > 5:
        e['recommendation'] = 'likely known-good pattern, review suppression'
        e['new_urgency'] = 'low'
    else:
        e['recommendation'] = 'standard triage'
        e['new_urgency'] = 'medium'

    findings.append(e)

events = findings"
| table src dest user asset_owner asset_priority prior_notables_30d recommendation new_urgency

Three enrichments and a decision, in one place, readable end to end.

Writing Back to the Notable

Adaptive response actions commonly update the notable's status or urgency. The ES REST endpoint takes the notable's event ID:

r = splunk_request(
    'POST',
    '/services/notable_update',
    body={
        'ruleUIDs': [e['event_id']],
        'status': '2',                       # in progress
        'urgency': e['new_urgency'],
        'comment': f"BabySOARus triage: {e['recommendation']}",
    },
)
e['notable_updated'] = r['status'] == 200

Pass $event_id$ into the search alongside the other tokens to get it.

Risk-based Alerting

If you use RBA, the same mechanism assigns risk scores with logic rather than a static number per rule.

| exec inline="
for e in events:
    score = 20

    if e.get('asset_priority') == 'critical':
        score += 40
    elif e.get('asset_priority') == 'high':
        score += 25

    if e.get('user', '').startswith('svc_'):
        score += 15          # service accounts should not do this

    if int(e.get('prior_notables_30d', 0)) == 0:
        score += 20          # never seen before

    hour = int(e.get('_time_hour', 12))
    if hour < 6 or hour > 20:
        score += 10          # out of hours

    e['risk_score'] = min(100, score)
    e['risk_object'] = e.get('dest', '')
    e['risk_object_type'] = 'system'"
| collect index=risk

The contributing factors are visible in the code, which is the thing that makes an RBA implementation maintainable a year later.

Ad-hoc Actions from Incident Review

Setting "supports_adhoc": true in param._cam lets an analyst run the action manually from a notable. That is where BabySOARus is most useful: a menu of small, purposeful actions, each a few lines of Python.

Actions worth having:

ActionWhat it does
Enrich indicatorsLook up every IP, domain and hash on the notable in one API call
Check asset ownershipResolve owner, business unit and criticality
Historical contextCount similar notables in the last 30 days
Decode payloadBase64 and URL decode anything embedded in the notable
Draft ticketBuild a formatted summary ready to paste, or post it directly

Each is a saved search with an exec snippet, and each takes a few minutes to write.

Testing Before You Register It

An adaptive response action is just a search, so test it as one. Substitute real values for the tokens and run it interactively:

| makeresults
| eval src="10.20.0.17", dest="10.20.0.51", user="d.harrington", rule_name="Test"
| exec inline="..."

That is the whole development loop. No add-on to reinstall, no ES restart.