Lesson 5 of 5 · 20 min read

Hunt Report to Detection — Converting Findings to Sigma

The hunt is only half the work. A confirmed finding that doesn’t produce a detection, an IR ticket, or a CTI deliverable has no lasting value. This lesson covers converting network hunt findings into durable detection rules — and knowing which findings are ready for conversion and which need more evidence.


The PEAK Act Phase: Three Outputs

Hunt finding confirmed

         ├─────────────────► (1) Detection Rule
         │                        Write Sigma → PR to detection repo → CI/CD test

         ├─────────────────► (2) IR Ticket
         │                        Scope, IOCs, ATT&CK chain → escalate to IR team

         └─────────────────► (3) CTI Deliverable
                                  Hunt report + IOC pack + ATT&CK Navigator layer

A high-quality hunt produces all three. A routine beacon hunt with a known-family implant produces (1) and (2). A novel C2 framework discovery produces all three.


ABLE Quality Gate

Before converting a finding to a production detection rule, run the ABLE check:

A — Assumptions

Every detection rule encodes assumptions. Name them explicitly:

Rule: "Cobalt Strike default JA3 on port 443"

Assumptions:
  - Cobalt Strike default JA3 (72a7c4de...) is not used by any legitimate application
    in our environment
  - Port 443 outbound with service=ssl is expected to use standard TLS
  - Hosts connecting to external IPs with this JA3 are not part of any authorized
    penetration test

Validation: Check with AppSec team — is there any approved tool with this JA3?
            Check with IR team — is there an authorized pentest ongoing?

B — Behaviors

The detection must target a specific behavior, not just an artifact:

Too artifact-focused (bad):
  "Alert when JA3 = 72a7c4de AND dst_ip = 91.213.50.22"
  Problem: IP changes every campaign. Rule dies in days.

Behavior-focused (good):
  "Alert when JA3 = 72a7c4de AND host_count_for_ja3 = 1 
   (rare JA3, seen only by this host)"
  Problem: slower to develop but works across campaigns.

L — Logging

Questions:
  - Is Zeek ssl.log collected from all network segments?
    Known gap: branch offices use a separate Zeek deployment with 7-day retention.
    Implication: detection coverage gap for branch office C2 that lasts >7 days.
  
  - Is the JA3 field populated by all Zeek versions in our fleet?
    Gap: Zeek < 3.0 doesn't compute JA3 — 2 of 8 sensors are version 2.6.
    Action: upgrade before shipping the rule, or add a version-conditional filter.

E — Exploitability

"Cobalt Strike default JA3" exploitability:
  - Trivially bypassed: any operator who reads this rule will change their JA3
    (Cobalt Strike malleable profile: one line to set custom ciphers/extensions)
  - Mitigation: pair JA3 rule with certificate metadata rule (harder to evade simultaneously)
  - Expected TP lifespan: 2-4 weeks before sophisticated operators evade it
  - Expected commodity-malware coverage: indefinite (most malware doesn't customize JA3)
  - Decision: ship rule with 90-day review trigger

Writing the Sigma Rule

From Hunt Finding to Rule Logic

Hunt finding: Host 10.1.2.33 beaconing with JA3 = a1b2c3d4, to self-signed cert, port 443, from svchost.exe in AppData.

What’s universal (include in rule):

What’s specific to this incident (do NOT include — too IOC-specific):

Sigma rule:

title: Outbound TLS with Self-Signed Certificate to Rare External Destination
id: c3f7a2e1-...
status: experimental
description: Detects TLS connections where the server certificate is self-signed
             (issuer == subject), which is uncommon for legitimate services and
             commonly seen in attacker-controlled C2 infrastructure.
references:
  - https://attack.mitre.org/techniques/T1071/001/
author: Threat Hunt Team
date: 2024-08-14
tags:
  - attack.command_and_control
  - attack.t1071.001
logsource:
  product: zeek
  category: ssl
detection:
  selection:
    local_orig: true
    local_resp: false
    destination.port: 443
  self_signed:
    tls.server.issuer|contains:
      - tls.server.subject  # issuer == subject = self-signed
  filter_legitimate:
    destination.ip|cidr:
      - '13.107.0.0/14'    # Microsoft
      - '52.96.0.0/14'     # Microsoft
      - '8.8.8.8/32'       # Google DNS
  condition: selection and self_signed and not filter_legitimate
fields:
  - src_ip
  - dst_ip
  - dst_port
  - ja3
  - tls.server.issuer
  - tls.server.subject
falsepositives:
  - Internal development/test servers with self-signed certs
  - Some legacy enterprise software
  - IoT devices
level: medium

The Hunt Journal Entry

Every hunt, even null results, deserves a journal entry:

## Hunt 2024-08-14 — Network Beacon (Cobalt Strike JA3) 

**Analyst:** M. Torres
**Hypothesis type:** Hypothesis-driven (known TTP)
**Hypothesis:** Cobalt Strike default JA3 profile (72a7c4de) is present in our environment,
targeting port 443, from a host not in any authorized pentest scope.

**Data source:** Zeek ssl.log, 30 days, Sentinel workspace TDL-PROD
**Data validation:** Full-fidelity Zeek confirmed (no sampling). JA3 field populated
all sensors. 30-day retention confirmed.

**Queries run:**
1. JA3 frequency baseline (30 days)
2. Rarity filter: JA3 host_count == 1, conn_count > 50
3. Peer cohort filter: dst host_count <= 3
4. CV calculation on top 10 candidates

**Findings:**
- 1 confirmed beacon: 10.1.2.33 → 185.220.101.9:443, JA3=a1b2c3d4
  - CV = 0.031 (very regular, 60s interval)
  - Cert: self-signed, issued 4 days ago
  - Endpoint: svchost.exe from C:\Users\jsmith\AppData\ (abnormal path)
  - Confidence: HIGH
- 3 null results for 72a7c4de specifically (no Cobalt Strike default JA3 found)

**Decision:**
1. IR ticket created (10.1.2.33 — active C2 confirmed)
2. Detection rule: see sigma/network/c3f7a2e1.yml (self-signed TLS hunt)
3. Next hypothesis: Are there other hosts in AppData with similar behavioral patterns?
   (Spawn: hypothesis 2024-08-14-02 — scheduled for next hunt window)

**Limitations:**
- Branch office Zeek sensors (old version) excluded — coverage gap remains
- High-jitter C2 (CV > 0.30) would not be caught by interval method

**Confidence in null result (no CS default JA3):** Medium
(Full data available; however branch office gap means 0% coverage for branch hosts)