<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: correlating-threat-campaigns
description: >-
  Correlate disparate threat events into unified campaigns using temporal,
  infrastructure, TTP, and victimology clustering techniques.
domain: cybersecurity
subdomain: threat-intelligence
tags:
  - campaign-correlation
  - threat-clustering
  - temporal-analysis
  - infrastructure-pivoting
  - mitre-attack
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: []
---
# Correlating Threat Campaigns

## Overview

Campaign correlation links separate intrusion events into unified campaigns by
identifying shared indicators across time, infrastructure, TTPs, and targeting
patterns. This skill covers temporal clustering, infrastructure overlap analysis,
TTP fingerprinting, victimology correlation, and producing campaign timelines
with activity boundaries for strategic intelligence reporting.

## Prerequisites

- Python 3.10+ with `pandas`, `networkx`, `stix2`
- MISP or OpenCTI for event data
- ATT&CK Navigator for TTP visualization
- Understanding of activity threading (Diamond Model)

```bash
pip install pandas networkx stix2 matplotlib
```

## Key Concepts

### Correlation Dimensions

```
Events → Campaign
├── Temporal:        Events within operational tempo window
├── Infrastructure:  Shared C2 domains, IPs, registrants, certs
├── Capability:      Same malware family, tool configuration, code overlap
├── TTP Pattern:     Consistent ATT&CK technique sequences
├── Victimology:     Same sector, geography, or organization type
└── Operational:     Timezone-aligned activity, consistent work patterns
```

### Clustering Thresholds

| Dimension | Strong Signal | Weak Signal |
|-----------|--------------|-------------|
| Infrastructure | Same C2 domain/IP | Same ASN/registrar |
| Capability | Identical binary hash | Same malware family |
| TTP | 5+ matching sub-techniques | 2-3 matching tactics |
| Temporal | Events within 72 hours | Within same quarter |
| Victimology | Same organization | Same sector |

### Campaign Lifecycle Phases

```
Reconnaissance → Weaponization → Delivery → Exploitation →
Installation → C2 → Actions on Objectives → (Repeat/Pivot)
```

## Workflow

### Step 1: Event Ingestion and Normalization

```python
import pandas as pd
from datetime import datetime

def ingest_events(events: list[dict]) -> pd.DataFrame:
    """Normalize events into a DataFrame for correlation."""
    records = []
    for e in events:
        records.append({
            "event_id": e["id"],
            "timestamp": pd.to_datetime(e["timestamp"]),
            "domains": ",".join(e.get("infrastructure", {}).get("domains", [])),
            "ips": ",".join(e.get("infrastructure", {}).get("ips", [])),
            "malware": e.get("capability", {}).get("malware", ""),
            "techniques": ",".join(e.get("capability", {}).get("techniques", [])),
            "victim_sector": e.get("victim", {}).get("sector", ""),
            "victim_org": e.get("victim", {}).get("org", ""),
        })
    return pd.DataFrame(records)
```

### Step 2: Temporal Clustering

```python
def temporal_clusters(df: pd.DataFrame, window_hours: int = 72) -> dict:
    """Cluster events by temporal proximity."""
    df = df.sort_values("timestamp")
    clusters = []
    current_cluster = [df.iloc[0]["event_id"]]
    last_time = df.iloc[0]["timestamp"]

    for _, row in df.iloc[1:].iterrows():
        gap = (row["timestamp"] - last_time).total_seconds() / 3600
        if gap <= window_hours:
            current_cluster.append(row["event_id"])
        else:
            clusters.append(current_cluster)
            current_cluster = [row["event_id"]]
        last_time = row["timestamp"]
    clusters.append(current_cluster)

    return {"clusters": clusters, "count": len(clusters), "window_hours": window_hours}
```

### Step 3: Infrastructure Overlap Graph

```python
import networkx as nx

def build_infra_graph(df: pd.DataFrame) -> dict:
    """Build graph of events linked by shared infrastructure."""
    G = nx.Graph()
    for _, row in df.iterrows():
        G.add_node(row["event_id"])

    for i, row1 in df.iterrows():
        domains1 = set(row1["domains"].split(",")) - {""}
        ips1 = set(row1["ips"].split(",")) - {""}
        for j, row2 in df.iloc[i+1:].iterrows():
            domains2 = set(row2["domains"].split(",")) - {""}
            ips2 = set(row2["ips"].split(",")) - {""}
            shared_d = domains1 & domains2
            shared_i = ips1 & ips2
            if shared_d or shared_i:
                G.add_edge(row1["event_id"], row2["event_id"],
                           shared_domains=list(shared_d), shared_ips=list(shared_i))

    components = list(nx.connected_components(G))
    return {
        "nodes": G.number_of_nodes(),
        "edges": G.number_of_edges(),
        "campaigns": [sorted(c) for c in components],
        "campaign_count": len(components),
    }
```

### Step 4: TTP Fingerprinting

```python
def ttp_similarity(df: pd.DataFrame) -> list[dict]:
    """Calculate TTP overlap between all event pairs."""
    pairs = []
    for i, row1 in df.iterrows():
        t1 = set(row1["techniques"].split(",")) - {""}
        for j, row2 in df.iloc[i+1:].iterrows():
            t2 = set(row2["techniques"].split(",")) - {""}
            if t1 and t2:
                overlap = t1 & t2
                jaccard = len(overlap) / len(t1 | t2) if (t1 | t2) else 0
                if jaccard > 0.3:
                    pairs.append({
                        "event_a": row1["event_id"],
                        "event_b": row2["event_id"],
                        "jaccard": round(jaccard, 3),
                        "shared_techniques": sorted(overlap),
                    })
    return sorted(pairs, key=lambda x: -x["jaccard"])
```

### Step 5: Campaign Timeline Generation

```python
def generate_timeline(df: pd.DataFrame, campaign_events: list[str]) -> dict:
    """Generate campaign timeline from correlated events."""
    campaign_df = df[df["event_id"].isin(campaign_events)].sort_values("timestamp")
    return {
        "campaign_start": campaign_df["timestamp"].min().isoformat(),
        "campaign_end": campaign_df["timestamp"].max().isoformat(),
        "duration_days": (campaign_df["timestamp"].max() - campaign_df["timestamp"].min()).days,
        "event_count": len(campaign_df),
        "sectors_targeted": campaign_df["victim_sector"].unique().tolist(),
        "malware_used": campaign_df["malware"].unique().tolist(),
        "timeline": [
            {"event": row["event_id"], "time": row["timestamp"].isoformat(),
             "victim": row["victim_org"], "malware": row["malware"]}
            for _, row in campaign_df.iterrows()
        ],
    }
```

## Verification

- [ ] Events ingested and normalized to consistent schema
- [ ] Temporal clustering identifies event groupings within operational windows
- [ ] Infrastructure overlap graph reveals connected campaign components
- [ ] TTP similarity scoring identifies technique fingerprint matches
- [ ] Campaign timelines include start/end dates, scope, and event sequence
- [ ] Correlation results exported to MISP/OpenCTI as campaign objects

## References

- [Diamond Model Activity Threading](https://www.threatintel.academy/diamond/)
- [MITRE ATT&CK](https://attack.mitre.org/)
- [NetworkX Graph Library](https://networkx.org/)
- [OpenCTI Campaign Objects](https://docs.opencti.io/)
