Building a Threat Intelligence Programme: From Feed Consumption to Actionable Decisions
Problem
Many security teams subscribe to threat intelligence feeds. Fewer have a functioning threat intelligence programme — a systematic process for turning raw indicators and adversary data into prioritised, time-bounded security decisions.
The common failure is volume without consumption. A team signs up for three free OSINT feeds and a commercial subscription, routes the indicators into a SIEM, and watches the alert count double. Nobody can explain what threat actors they are tracking, whether any tracked actor targets their industry, or whether a given IOC is still active or two years stale. The feed adds noise, not signal.
Common failure modes in threat intelligence:
- Feed ingestion without analysis. IP blocklists and hash lists flow into the SIEM. Every match generates an alert. No analyst can tell whether the matching IOC belongs to a tracked campaign, a false positive, or a sinkholed domain from 2022.
- Tactical-only focus. Teams collect IOCs — IPs, hashes, domains — but have no strategic or operational layer. They know an IP is malicious; they do not know which threat actor uses it, what their objectives are, or what TTPs follow initial access.
- No lifecycle management. Indicators decay. An IP flagged as a C2 server in 2023 is likely reassigned or sinkholed. Blocklists filled with stale IOCs generate false positives, erode analyst trust, and obscure genuine hits.
- No environment mapping. Threat intelligence describes threats in general. Without mapping to your specific technology stack, industry, and geography, you cannot determine which threats are relevant to you.
- No feedback loop. Intelligence that cannot be validated against actual detections or hunt findings cannot be improved. Without feedback, feed quality is invisible.
Target systems: MISP (Malware Information Sharing Platform), OpenCTI, Elastic SIEM or Splunk for IOC ingestion, Sigma rules for TTP-based detection, MITRE ATT&CK Navigator for threat actor mapping.
Threat Model
- Adversary 1 — Opportunistic commodity threat: Ransomware-as-a-service affiliates using commodity tooling (Cobalt Strike, Metasploit, commodity stealers). They target known-unpatched internet-exposed services. Public IOC feeds contain their infrastructure.
- Adversary 2 — Targeted sector-specific threat actor: A threat group known to target your industry (finance, healthcare, energy). They use spear-phishing, living-off-the-land techniques, and custom implants. Their TTPs are documented in MITRE ATT&CK. Their infrastructure rotates frequently, making IOC-based detection unreliable; TTP-based detection is required.
- Adversary 3 — Supply chain threat: A threat actor compromising software dependencies, CI/CD pipelines, or managed service providers to reach downstream targets. Indicators are upstream in third parties; threat intelligence sharing groups and ISAC feeds carry early warning.
- Adversary 4 — Insider or credential abuse: An adversary using legitimate credentials obtained through phishing, credential stuffing, or infostealer malware. Internal telemetry and honeypot data, combined with leaked credential feeds, provide the intelligence signal.
- Access level: All adversaries achieve initial access via internet-exposed services, phishing, supply chain, or credential reuse. Lateral movement and privilege escalation follow.
- Objective: Data exfiltration, ransomware deployment, or persistent access for espionage.
- Blast radius: Undetected compromise at initial access allows adversaries to achieve their primary objective before detection. Early detection from threat intelligence-informed alerts limits dwell time.
Configuration
Step 1: Intelligence Types — What You Are Collecting
Threat intelligence operates at three levels. A mature programme consumes all three.
Strategic intelligence
├── Adversary intent: What motivates tracked threat actors?
├── Capability: What tools and techniques does this actor use?
├── Targeting: Which industries and geographies?
└── Consumers: CISO, board, risk team — decisions on investment and risk
Operational intelligence
├── Active campaigns: What is this actor doing right now?
├── TTPs: MITRE ATT&CK technique mappings for this campaign
├── Infrastructure patterns: C2 infrastructure patterns, staging domains
└── Consumers: Security engineers — detection rule development, threat hunting
Tactical intelligence
├── Indicators of Compromise (IOCs)
│ ├── IP addresses (C2, scanning sources, exfil destinations)
│ ├── Domains and URLs (phishing, C2, malware distribution)
│ ├── File hashes (SHA-256 of malware samples)
│ └── Email indicators (sender addresses, subject patterns)
└── Consumers: SIEM, EDR, firewalls — automated blocking and alerting
Tactical IOCs decay rapidly. An IP address has a useful life of days to weeks. A file hash may be valid for months if the malware variant is not recompiled. A domain has intermediate longevity. Always store the confidence and expiry date alongside every IOC.
Step 2: Source Selection and Curation
# threat-intel-sources.yaml — curated source inventory
sources:
osint_free:
- name: AlienVault OTX (Open Threat Exchange)
url: https://otx.alienvault.com
feed_type: [ip, domain, hash, url]
ingestion: MISP feed (STIX/JSON)
quality: medium # Large volume; variable quality; must filter by pulse rating
refresh_interval: 1h
- name: Abuse.ch (MalwareBazaar, URLhaus, ThreatFox)
url: https://abuse.ch
feed_type: [hash, url, ip]
ingestion: CSV/JSON API
quality: high # Focused on malware and botnet infrastructure
refresh_interval: 15m
- name: CISA Known Exploited Vulnerabilities
url: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
feed_type: [cve]
ingestion: JSON API
quality: high # Authoritative; confirmed exploitation in the wild
refresh_interval: 24h
- name: PhishTank
url: https://phishtank.org
feed_type: [url, domain]
ingestion: JSON/CSV download
quality: medium # Community-verified phishing URLs
refresh_interval: 1h
- name: Emerging Threats (Proofpoint ET Open)
url: https://rules.emergingthreats.net
feed_type: [ip, rule]
ingestion: Suricata/Snort rule format
quality: high # Well-maintained; mapped to threat categories
refresh_interval: 24h
isac:
- name: FS-ISAC (financial sector)
notes: "Membership required; feeds via STIX/TAXII; sector-specific campaigns"
- name: H-ISAC (healthcare)
notes: "Membership required; covers ransomware targeting healthcare"
- name: E-ISAC (energy/ICS)
notes: "Membership required; ICS-specific threat actors (Sandworm, Volt Typhoon)"
commercial:
- name: Recorded Future
ingestion: STIX/TAXII or API
differentiator: "Dark web monitoring, early warning on exploits, threat actor profiles"
- name: Mandiant Threat Intelligence
ingestion: API
differentiator: "First-party incident response data; high-confidence attribution"
- name: VirusTotal Intelligence
ingestion: API (requires paid tier)
differentiator: "Retrohunting on malware samples; YARA rule-based hunting"
internal:
- name: Honeypot network
notes: "Self-hosted Cowrie (SSH), Dionaea (SMB/FTP), or T-Pot platform"
value: "High-confidence; attackers interacting with honeypots have no legitimate reason"
- name: Incident data
notes: "IOCs and TTPs extracted from confirmed incidents; highest confidence"
- name: DNS sinkholes
notes: "Track what internal hosts attempt to resolve; flag malware C2 lookups"
Source selection must match your threat model. An energy company facing ICS-specific threat actors needs E-ISAC feeds and Dragos intelligence, not generic commodity feeds. A SaaS company primarily facing credential stuffing needs leaked credential feeds and ATO-focused intelligence.
Step 3: MISP Deployment
MISP (Malware Information Sharing Platform) is the standard self-hosted platform for aggregating, enriching, and sharing threat intelligence.
# Deploy MISP using Docker Compose.
git clone https://github.com/MISP/misp-docker
cd misp-docker
cp template.env .env
# Configure key settings in .env:
# MISP_BASEURL=https://misp.internal.example.com
# MISP_ADMIN_EMAIL=security@example.com
# MISP_ADMIN_PASSPHRASE=$(openssl rand -base64 32)
# MYSQL_PASSWORD=$(openssl rand -base64 32)
docker compose up -d
# Verify MISP is running.
docker compose ps
# misp-core Up 443/tcp, 80/tcp
# misp-mysql Up 3306/tcp
# misp-redis Up 6379/tcp
# Configure MISP feeds via API.
# misp_setup/configure_feeds.py
import requests
MISP_URL = "https://misp.internal.example.com"
MISP_KEY = "YOUR_MISP_API_KEY"
HEADERS = {
"Authorization": MISP_KEY,
"Content-Type": "application/json",
"Accept": "application/json",
}
# Add AlienVault OTX as a MISP feed.
otx_feed = {
"Feed": {
"name": "AlienVault OTX",
"url": "https://reputation.alienvault.com/reputation.generic",
"provider": "AlienVault",
"source_format": "misp",
"input_source": "network",
"enabled": True,
"caching_enabled": True,
"fixed_event": False,
"delta_merge": False,
"publish": False,
"tag_id": False,
"default_tag_id": False,
"lookup_visible": True,
"headers": "",
"distribution": 0, # Your organisation only.
}
}
resp = requests.post(
f"{MISP_URL}/feeds/add",
json=otx_feed,
headers=HEADERS,
verify=True,
)
print(resp.json())
# Fetch all enabled feeds immediately.
requests.post(f"{MISP_URL}/feeds/fetchFromAllFeeds", headers=HEADERS)
# MISP cron job: fetch all feeds every hour.
# /etc/cron.d/misp-feeds
0 * * * * www-data /var/www/MISP/app/Console/cake Server fetchFeeds 2>&1 | logger -t misp-feeds
# Decay old indicators daily.
0 2 * * * www-data /var/www/MISP/app/Console/cake Admin decayIndicators 2>&1 | logger -t misp-decay
Sharing groups control which intelligence is shared with external parties. Create granular groups:
# Create a sharing group for ISAC members only.
sharing_group = {
"SharingGroup": {
"name": "FS-ISAC Sharing Group",
"releasability": "Members of FS-ISAC only",
"description": "Threat intelligence shared with FS-ISAC member organisations",
"active": True,
"roaming": False, # Do not forward beyond defined members.
}
}
resp = requests.post(
f"{MISP_URL}/sharing_groups/add",
json=sharing_group,
headers=HEADERS,
)
Step 4: MITRE ATT&CK Threat Actor Profiling
MITRE ATT&CK provides a structured taxonomy of adversary techniques. Map each tracked threat actor to their known ATT&CK techniques to understand what detections you need.
# threat_actors/profile_builder.py
# Build a threat actor profile from ATT&CK STIX data.
from mitreattack.stix20 import MitreAttackData
# Download ATT&CK STIX bundle: https://github.com/mitre/cti
attack = MitreAttackData("enterprise-attack.json")
def build_actor_profile(actor_name: str) -> dict:
"""
Returns techniques used by a named threat actor,
grouped by tactic, with detection guidance.
"""
groups = attack.get_groups()
actor = next(
(g for g in groups if actor_name.lower() in g["name"].lower()),
None
)
if not actor:
raise ValueError(f"Actor '{actor_name}' not found in ATT&CK")
techniques = attack.get_techniques_used_by_group(actor["id"])
profile = {
"actor": actor["name"],
"description": actor.get("description", ""),
"aliases": actor.get("aliases", []),
"techniques_by_tactic": {},
}
for technique in techniques:
for phase in technique.get("kill_chain_phases", []):
tactic = phase["phase_name"]
profile["techniques_by_tactic"].setdefault(tactic, []).append({
"technique_id": technique["external_references"][0]["external_id"],
"technique_name": technique["name"],
"detection": technique.get("detection", ""),
})
return profile
# Example: profile APT29 (Cozy Bear).
profile = build_actor_profile("APT29")
for tactic, techniques in profile["techniques_by_tactic"].items():
print(f"\n{tactic.upper()}")
for t in techniques:
print(f" {t['technique_id']}: {t['technique_name']}")
# Visualise threat actor profiles in ATT&CK Navigator.
# Export a layer file for browser-based ATT&CK Navigator.
curl -s https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json \
| python3 generate_navigator_layer.py --actor "APT29" \
> apt29-layer.json
# Open https://mitre-attack.github.io/attack-navigator/
# Import apt29-layer.json to see which techniques APT29 uses highlighted.
Gap analysis: Compare the ATT&CK techniques used by your tracked threat actors against your detection coverage. Techniques without a corresponding detection rule or alert are blind spots.
# gap_analysis/coverage_check.py
def find_detection_gaps(actor_techniques: list, sigma_rules: list) -> list:
"""
Returns ATT&CK technique IDs used by actor that have no Sigma rule coverage.
"""
covered_techniques = set()
for rule in sigma_rules:
for tag in rule.get("tags", []):
if tag.startswith("attack.t"):
# e.g. "attack.t1059.001" → "T1059.001"
technique_id = tag.replace("attack.", "").upper()
covered_techniques.add(technique_id)
actor_technique_ids = {t["technique_id"] for t in actor_techniques}
return sorted(actor_technique_ids - covered_techniques)
gaps = find_detection_gaps(profile_techniques, loaded_sigma_rules)
print(f"Detection gaps for {actor_name}: {gaps}")
Step 5: IOC Integration into SIEM
Pull IOCs from MISP into your SIEM for automated matching. Use the MISP API to export by type and confidence.
# siem_integration/misp_exporter.py
# Export active, high-confidence IOCs from MISP for SIEM ingestion.
import requests
from datetime import datetime, timedelta
def export_iocs(
misp_url: str,
misp_key: str,
ioc_type: str, # "ip-dst", "domain", "md5", "sha256"
min_confidence: int = 70, # 0-100; filter noise
max_age_days: int = 30,
) -> list[str]:
"""
Returns a list of active IOC values for a given type.
"""
cutoff = (datetime.utcnow() - timedelta(days=max_age_days)).strftime("%Y-%m-%d")
payload = {
"returnFormat": "json",
"type": ioc_type,
"to_ids": True, # Only exportable, actionable IOCs.
"timestamp": cutoff,
"enforceWarninglist": True, # Exclude whitelisted values.
}
resp = requests.post(
f"{misp_url}/attributes/restSearch",
json=payload,
headers={
"Authorization": misp_key,
"Accept": "application/json",
},
)
resp.raise_for_status()
attributes = resp.json().get("response", {}).get("Attribute", [])
return [attr["value"] for attr in attributes]
# Export IP IOCs and write to a file Suricata or SIEM can consume.
malicious_ips = export_iocs(MISP_URL, MISP_KEY, "ip-dst")
with open("/etc/threat-intel/malicious-ips.txt", "w") as f:
f.write("\n".join(malicious_ips))
# Export domain IOCs.
malicious_domains = export_iocs(MISP_URL, MISP_KEY, "domain")
with open("/etc/threat-intel/malicious-domains.txt", "w") as f:
f.write("\n".join(malicious_domains))
# Elastic SIEM: ingest IOC list as a threat indicator index.
# Use Elastic's Threat Intel filebeat module.
filebeat.inputs:
- type: threat-intel
name: misp
enabled: true
url: "https://misp.internal.example.com"
api_key: "${MISP_API_KEY}"
ioc_expiration_duration: "30d" # Auto-expire IOCs after 30 days.
interval: 1h
output.elasticsearch:
hosts: ["https://elastic.internal.example.com:9200"]
index: "filebeat-threat-intel-%{+yyyy.MM}"
# Splunk: add threat intel lookup table from MISP export.
# Add to transforms.conf
[malicious_ips]
filename = malicious_ips.csv
case_sensitive_match = false
# Threat intel correlation search (scheduled every 15 minutes).
index=network sourcetype=firewall
| lookup malicious_ips ip AS dest_ip OUTPUT threat_description
| where isnotnull(threat_description)
| table _time, src_ip, dest_ip, dest_port, threat_description, action
TTP-based detection with Sigma: IOC-based detection is reactive and easily evaded. TTP-based detection using Sigma rules is more durable — an attacker must change their technique, not just their infrastructure.
# sigma/rules/apt29-spearphishing-attachment.yml
# Detects T1566.001 - Spearphishing Attachment, as used by APT29.
title: Suspicious Office Child Process (APT29 TTP)
id: a9c4e443-8d36-4e11-a5e2-d4b9e6d4a321
status: production
description: >
Detects Microsoft Office applications spawning unusual child processes,
consistent with macro-enabled document exploitation. Used by APT29
(T1566.001 Phishing: Spearphishing Attachment, T1059.005 VBScript).
references:
- https://attack.mitre.org/techniques/T1566/001/
- https://attack.mitre.org/groups/G0016/
author: security-team@example.com
date: 2026-05-07
tags:
- attack.initial_access
- attack.t1566.001
- attack.execution
- attack.t1059.005
- threat_actor.apt29
logsource:
category: process_creation
product: windows
detection:
selection:
ParentImage|endswith:
- '\WINWORD.EXE'
- '\EXCEL.EXE'
- '\POWERPNT.EXE'
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\wscript.exe'
- '\cscript.exe'
- '\mshta.exe'
- '\rundll32.exe'
condition: selection
falsepositives:
- Legitimate macros in controlled environments (very rare)
level: high
Step 6: Threat-Informed Vulnerability Prioritisation
Threat intelligence directly improves vulnerability management by identifying which CVEs are actively exploited by threat actors relevant to your organisation.
# threat_informed_vuln/prioritiser.py
# Cross-reference open vulnerabilities with threat actor TTPs and active exploitation.
from dataclasses import dataclass
@dataclass
class ThreatContext:
cve_id: str
exploited_by_actors: list[str] # Tracked actors exploiting this CVE
exploit_in_wild: bool # Confirmed exploitation in the wild
cisa_kev: bool # In CISA KEV catalog
exploit_kit_integration: bool # In commodity exploit kit (Metasploit, etc.)
active_campaign: bool # Currently active campaign using this CVE
def threat_adjusted_priority(
cvss_score: float,
threat_ctx: ThreatContext,
asset_is_internet_exposed: bool,
) -> tuple[str, str]:
"""
Returns (priority_tier, rationale).
Priority tiers: P0 (emergency), P1 (critical), P2 (high), P3 (standard)
"""
# P0: CISA KEV + internet exposed = patch within 24 hours.
if threat_ctx.cisa_kev and asset_is_internet_exposed:
return "P0", f"{threat_ctx.cve_id} is in CISA KEV and asset is internet-exposed"
# P0: Tracked threat actor actively exploiting this CVE.
if threat_ctx.active_campaign and threat_ctx.exploited_by_actors:
actors = ", ".join(threat_ctx.exploited_by_actors)
return "P0", f"Active campaign by {actors} exploiting {threat_ctx.cve_id}"
# P1: In CISA KEV (exploited in wild), even if not internet exposed.
if threat_ctx.cisa_kev:
return "P1", f"{threat_ctx.cve_id} confirmed exploited in the wild (CISA KEV)"
# P1: Tracked threat actor exploitation + high CVSS.
if threat_ctx.exploited_by_actors and cvss_score >= 7.0:
return "P1", f"{threat_ctx.cve_id} exploited by tracked actors; CVSS {cvss_score}"
# P2: Exploit kit integration (commodity; likely to be used against any target).
if threat_ctx.exploit_kit_integration and cvss_score >= 6.0:
return "P2", f"{threat_ctx.cve_id} in commodity exploit kit; CVSS {cvss_score}"
# P3: CVSS-based standard prioritisation.
if cvss_score >= 9.0:
return "P2", f"CVSS {cvss_score}; no active exploitation context"
return "P3", f"CVSS {cvss_score}; no active exploitation evidence"
# Correlate open vulnerability tickets against CISA KEV.
# Run daily; any match is P0 and triggers immediate Slack/PagerDuty alert.
#!/bin/bash
KEV_URL="https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
OPEN_CVES_FILE="/var/data/vuln-mgmt/open-cves.txt"
curl -s "$KEV_URL" -o /tmp/kev.json
while IFS= read -r cve; do
match=$(jq --arg cve "$cve" \
'.vulnerabilities[] | select(.cveID == $cve) | .cveID' \
/tmp/kev.json)
if [ -n "$match" ]; then
echo "P0 ALERT: $cve is unpatched and in CISA KEV"
# Page on-call.
python3 /opt/scripts/page_oncall.py \
--severity critical \
--message "Unpatched CISA KEV vulnerability: $cve — patch within 24 hours"
fi
done < "$OPEN_CVES_FILE"
Step 7: Intelligence Sharing
Sharing intelligence with trusted peers improves collective defences and often returns higher-quality intelligence than you contribute. Key principles:
# sharing-policy.yaml — outbound intelligence sharing policy
internal_sharing:
- audience: "Security Operations (SOC)"
types: [tactical_iocs, active_campaigns]
format: "MISP events via API; daily digest email"
frequency: real_time
- audience: "Security Engineering"
types: [ttp_analysis, detection_gaps, threat_actor_profiles]
format: "Confluence pages; Slack channel #threat-intel"
frequency: weekly
- audience: "Executive / Risk team"
types: [strategic_intelligence, threat_landscape_summary]
format: "Monthly briefing deck; one-page threat summary"
frequency: monthly
external_sharing:
- recipient: "FS-ISAC (sector ISAC)"
types: [high_confidence_iocs, campaign_reports]
tlp: "TLP:AMBER+STRICT" # Members only; no further sharing.
restrictions: "Remove internal asset references; anonymise victim data"
review_required: true
- recipient: "CISA (government partner)"
types: [novel_malware_samples, critical_infrastructure_threats]
tlp: "TLP:WHITE"
process: "Submit via CISA reporting portal; coordinate on public disclosure"
tlp_definitions:
TLP:RED: "Only named recipients; not for distribution"
TLP:AMBER: "Organisation and clients only"
TLP:AMBER+STRICT: "Organisation only; no client sharing"
TLP:GREEN: "Community; not for public release"
TLP:WHITE: "Unrestricted public release"
Responsible disclosure timeline for internally discovered vulnerabilities:
Day 0: Vulnerability discovered internally or by external researcher.
Day 1: Notify vendor privately (security@vendor.com or HackerOne programme).
Day 14: Follow up if no acknowledgement.
Day 45: If no patch progress, notify vendor of disclosure intent.
Day 90: Public disclosure, coordinated with vendor if patch available.
If vendor unresponsive: disclose with available mitigations.
Step 8: Measuring Intelligence Quality
Most threat intelligence programmes measure volume (number of IOCs ingested). Volume is the wrong metric. Measure precision, recall, and operational utility.
# metrics/intel_quality.py
def calculate_programme_metrics(
iocs_ingested: int,
iocs_matched_in_traffic: int,
matches_confirmed_malicious: int,
matches_false_positive: int,
detections_linked_to_intel: int,
total_detections: int,
hunts_conducted: int,
hunts_yielding_findings: int,
) -> dict:
return {
# Precision: of IOCs that matched, what fraction were genuinely malicious?
# Low precision → noisy feeds creating false positive alerts.
"ioc_precision": (
matches_confirmed_malicious /
(matches_confirmed_malicious + matches_false_positive)
if (matches_confirmed_malicious + matches_false_positive) > 0
else 0
),
# Match rate: what fraction of ingested IOCs ever matched traffic?
# Very low match rate suggests feeds irrelevant to your environment.
"ioc_match_rate": iocs_matched_in_traffic / iocs_ingested if iocs_ingested > 0 else 0,
# Intel-driven detection rate: what fraction of total detections
# were enabled by threat intelligence (IOC match or TTP-based Sigma rule)?
# This measures the programme's contribution to detection.
"intel_driven_detection_pct": (
detections_linked_to_intel / total_detections * 100
if total_detections > 0 else 0
),
# Hunt yield rate: what fraction of threat hunts produced findings?
# Intel-driven hunts should yield higher than baseline.
"hunt_yield_rate": (
hunts_yielding_findings / hunts_conducted
if hunts_conducted > 0 else 0
),
# Actionable intelligence ratio: IOCs with a match / total IOCs ingested.
# A ratio below 0.001 (0.1%) suggests the feed is not suited to your environment.
"actionable_ratio": iocs_matched_in_traffic / iocs_ingested if iocs_ingested > 0 else 0,
}
Step 9: Telemetry
threat_intel_iocs_ingested_total{source, type} counter
threat_intel_iocs_active_total{type} gauge
threat_intel_ioc_matches_total{type, outcome} counter # outcome: confirmed, fp, unknown
threat_intel_feed_last_update_timestamp{source} gauge
threat_intel_ioc_precision_ratio{source} gauge
threat_intel_detections_linked_total{} counter
threat_intel_hunts_conducted_total{} counter
threat_intel_hunts_with_findings_total{} counter
threat_intel_actor_profiles_tracked_total{} gauge
Alert on:
threat_intel_feed_last_update_timestampolder than2 * refresh_interval— a feed has stopped updating; investigate source availability.threat_intel_ioc_precision_ratio{source}< 0.5 — more than half of IOC matches from this source are false positives; suspend feed and review.threat_intel_ioc_matches_total{outcome="confirmed"}= 0 for 30 days — a feed has produced no confirmed detections; evaluate relevance to your environment.threat_intel_iocs_active_total{type="ip"}growing without bound — indicator expiry is not functioning; stale IOCs accumulating.
Expected Behaviour
| Signal | No programme | Mature programme |
|---|---|---|
| New threat actor targeting your sector | Unknown until incident | ISAC feed triggers actor profile update; detection rules reviewed within 48h |
| CISA KEV vulnerability disclosed | Treated as any other CVE | Automatically P0 priority; patch SLA 24h; on-call paged |
| Malicious IP in firewall logs | Alert may or may not fire | IOC matched in SIEM; alert fires with threat context including actor and campaign |
| Novel phishing campaign in sector | Unknown until internal victim | ISAC sharing provides early warning; email gateway rules updated pre-arrival |
| Commodity malware hash | Hash may not be in EDR | MISP feed provides hash within hours of public disclosure; EDR and SIEM updated |
| Threat hunt request | Ad-hoc; no direction | ATT&CK gap analysis identifies highest-value hunt hypotheses |
Trade-offs
| Aspect | Benefit | Cost | Mitigation |
|---|---|---|---|
| Self-hosted MISP | Full control; no data sharing with third party | Operational overhead; feed management burden | Use Docker deployment; automate feed fetches via cron |
| High-volume free feeds (OTX) | Broad coverage; no cost | High false positive rate; stale indicators | Filter by to_ids=true; enforce enforceWarninglist; set 30-day expiry |
| TTP-based Sigma rules | Actor-agnostic; durable against infrastructure rotation | More complex to develop; higher false positive risk | Tune with local context; test in detection-as-code pipeline before production |
| IOC expiry (30-day default) | Reduces stale-indicator false positives | May miss long-lived infrastructure | Adjust per IOC type: IPs 14d, domains 30d, hashes 90d |
| ISAC membership | High-quality sector-specific intelligence | Membership cost; requires reciprocal sharing | Start with free government-affiliated ISACs; join paid ISAC as programme matures |
Failure Modes
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| Feed stops updating | IOCs stale; new campaigns undetected | feed_last_update_timestamp alert |
Investigate source; switch to backup feed; open support ticket |
| IOC precision collapses | Alert fatigue; analysts stop investigating | ioc_precision_ratio < 0.5 |
Suspend low-precision source; enable warninglist filtering; review ingestion pipeline |
| No TTP coverage for tracked actors | ATT&CK gap analysis shows blind spots | Quarterly gap review | Develop or adopt Sigma rules for uncovered techniques |
| Stale indicators not expired | Growing false positive rate | iocs_active_total grows unbounded |
Enable MISP decay model; set ioc_expiration_duration in Filebeat |
| Intelligence not reaching consumers | Detections not intel-driven | intel_driven_detection_pct < 10% |
Review dissemination process; automate SIEM lookup table updates |
| No feedback from SOC | Feed quality invisible; no improvement | Analyst escalation rate = 0 | Implement structured feedback form; hold monthly intel-SOC review |