incident response

Collecting Indicators of Compromise

Systematically collects, categorizes, and distributes indicators of compromise (IOCs) during and after security incidents to enable detection, blocking, and threat intelligence sharing. Covers network, host, email, and behavioral indicators using STIX/TAXII formats and threat intelligence platforms. Activates for requests involving IOC collection, indicator extraction, threat indicator sharing, compromise indicators, STIX export, or IOC enrichment.

ioc-collectionmispstix-taxiithreat-indicatorsthreat-intelligence-sharing
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • During active incident response to identify and block adversary infrastructure
  • Post-incident to document all observed adversary artifacts for future detection
  • When sharing threat intelligence with ISACs, sector partners, or law enforcement
  • When building detection rules in SIEM, EDR, or network security tools
  • When enriching IOCs with threat intelligence context for risk scoring

Do not use for behavioral TTP analysis without accompanying technical indicators; use MITRE ATT&CK mapping for behavioral characterization.

Prerequisites

  • Access to incident evidence sources: SIEM logs, EDR telemetry, memory dumps, disk images, network captures
  • Threat intelligence platform (MISP, OpenCTI, ThreatConnect) for IOC management and sharing
  • IOC enrichment tools: VirusTotal, OTX (AlienVault Open Threat Exchange), Shodan, DomainTools
  • STIX 2.1 knowledge for structured IOC representation
  • Sharing agreements with relevant ISACs (FS-ISAC, H-ISAC, IT-ISAC) or sector partners

Workflow

Step 1: Identify IOC Categories

Collect indicators across all categories from incident evidence:

Network Indicators:

  • IP addresses (C2 servers, staging servers, exfiltration destinations)
  • Domain names (C2 domains, phishing domains, DGA domains)
  • URLs (malware download, C2 check-in, exfiltration endpoints)
  • JA3/JA3S hashes (TLS client/server fingerprints)
  • User-Agent strings (custom or unusual HTTP headers)
  • DNS query patterns (tunneling signatures, DGA patterns)

Host Indicators:

  • File hashes (MD5, SHA-1, SHA-256 of malware, tools, scripts)
  • File paths (known malware installation directories)
  • Registry keys (persistence mechanisms, configuration storage)
  • Scheduled tasks and service names (persistence)
  • Mutex/event names (malware instance synchronization)
  • Named pipes (C2 communication channels, e.g., Cobalt Strike)

Email Indicators:

  • Sender addresses and domains (spoofed or attacker-controlled)
  • Subject lines and body content patterns
  • Attachment names and hashes
  • Embedded URLs
  • Email header anomalies (SPF/DKIM/DMARC failures)

Step 2: Extract IOCs from Evidence Sources

Systematically extract indicators from each evidence source:

From SIEM/Log Analysis:

# Extract unique destination IPs from firewall logs
index=firewall action=blocked
| stats count by dest_ip
| where count > 100
 
# Extract domains from DNS query logs
index=dns query=*evil* OR query=*c2*
| stats count by query

From Memory Forensics:

# Extract network connections
vol -f memory.raw windows.netscan | grep ESTABLISHED
 
# Extract strings from suspicious process memory
vol -f memory.raw windows.memmap --pid 3847 --dump
strings -n 8 pid.3847.dmp | grep -E "(http|https)://"

From Malware Analysis:

Sandbox Report IOC Extraction:
- Dropped files:      3 (hashes extracted)
- DNS queries:        update.evil[.]com, cdn.malware[.]net
- HTTP connections:   POST to https://185.220.101[.]42/gate.php
- Registry modified:  HKCU\Software\Microsoft\Windows\CurrentVersion\Run\svcupdate
- Mutex created:      Global\MTX_0x1234ABCD
- Named pipe:         \\.\pipe\MSSE-1234-server

Step 3: Enrich IOCs with Context

Add threat intelligence context to each indicator:

IOC Enrichment Report:
━━━━━━━━━━━━━━━━━━━━━
IP: 185.220.101.42
  VirusTotal:     12/89 vendors flag as malicious
  Shodan:         Open ports: 443, 8443, 80
  Geolocation:    Netherlands, AS208476
  First Seen:     2025-10-01
  Threat Intel:   Associated with Qakbot C2 infrastructure
  Confidence:     High
  TLP:            AMBER
 
Domain: update.evil[.]com
  Registration:   2025-10-28 (recently registered)
  Registrar:      Namecheap
  WHOIS Privacy:  Yes
  VirusTotal:     8/89 vendors flag as malicious
  DNS History:    Resolved to 185.220.101.42, 91.215.85.17
  Confidence:     High
  TLP:            AMBER

Step 4: Score and Prioritize IOCs

Assign confidence and risk scores to each indicator:

Score Confidence Level Criteria
90-100 Confirmed Malicious Multiple TI sources confirm, observed in active attack
70-89 Highly Suspicious Single TI source confirms, behavioral analysis supports
50-69 Suspicious Limited TI data, contextually suspicious
30-49 Unconfirmed No TI matches, but anomalous in environment
0-29 Likely Benign False positive indicators or legitimate infrastructure

Step 5: Distribute IOCs for Detection and Blocking

Push IOCs to defensive systems for immediate protection:

  • Firewall/IPS: Block C2 IPs and domains
  • DNS: Sinkhole malicious domains
  • EDR: Add file hashes to blocklist, create custom IOC watchlists
  • Email Gateway: Block sender domains, attachment hashes, malicious URLs
  • SIEM: Create correlation searches for IOC matches
  • Web Proxy: Block URLs and domains in web filtering policy

Step 6: Share IOCs with Partners

Package IOCs in STIX 2.1 format for sharing:

{
  "type": "indicator",
  "spec_version": "2.1",
  "id": "indicator--a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "created": "2025-11-15T18:00:00Z",
  "modified": "2025-11-15T18:00:00Z",
  "name": "Qakbot C2 Server IP",
  "indicator_types": ["malicious-activity"],
  "pattern": "[ipv4-addr:value = '185.220.101.42']",
  "pattern_type": "stix",
  "valid_from": "2025-11-15T14:23:00Z",
  "confidence": 95,
  "labels": ["c2", "qakbot"],
  "object_marking_refs": ["marking-definition--f88d31f6-486f-44da-b317-01333bde0b82"]
}

Submit to MISP, ISAC portals, and TAXII servers per sharing agreements.

Key Concepts

Term Definition
IOC (Indicator of Compromise) Technical artifact observed during a security incident that indicates adversary presence (hash, IP, domain, etc.)
TLP (Traffic Light Protocol) Standard for classifying the sharing restrictions of threat intelligence: WHITE, GREEN, AMBER, AMBER+STRICT, RED
STIX (Structured Threat Information Expression) Standard language for representing cyber threat intelligence in a structured, machine-readable format
TAXII (Trusted Automated Exchange of Intelligence Information) Transport protocol for sharing STIX-formatted threat intelligence between organizations
Confidence Score Numerical rating (0-100) indicating the analyst's certainty that an indicator is truly malicious
IOC Lifecycle Process of creating, validating, distributing, and eventually retiring indicators as they lose relevance
Defanging Practice of modifying malicious URLs and domains in reports to prevent accidental clicks (e.g., evil[.]com)

Tools & Systems

  • MISP: Open-source threat intelligence sharing platform for managing, storing, and distributing IOCs
  • VirusTotal: Multi-engine malware scanning and threat intelligence platform for IOC enrichment
  • OpenCTI: Open-source cyber threat intelligence platform supporting STIX 2.1 natively
  • Yeti: Open-source platform for organizing observables, indicators, and TTPs
  • CyberChef: GCHQ's data transformation tool useful for decoding, defanging, and formatting IOCs

Common Scenarios

Scenario: Post-Incident IOC Package for ISAC Sharing

Context: After responding to a Qakbot infection that led to Cobalt Strike deployment, the IR team must package all IOCs for sharing with the Financial Services ISAC (FS-ISAC).

Approach:

  1. Compile all network, host, and email indicators from the investigation
  2. Enrich each IOC with VirusTotal and MISP correlation data
  3. Assign confidence scores based on direct observation vs. secondary correlation
  4. Mark all IOCs with TLP:AMBER for partner sharing
  5. Export as STIX 2.1 bundle and submit to FS-ISAC TAXII feed
  6. Create a human-readable IOC summary report for email distribution

Pitfalls:

  • Including internal IP addresses or hostnames in shared IOC packages (information leakage)
  • Sharing IOCs at TLP:WHITE that should be restricted to TLP:AMBER
  • Not defanging URLs and domains in human-readable reports
  • Sharing IP addresses of legitimate CDNs or cloud providers as malicious IOCs

Output Format

INDICATOR OF COMPROMISE REPORT
================================
Incident:     INC-2025-1547
Date:         2025-11-15
TLP:          AMBER
Sharing:      FS-ISAC, internal SOC
 
NETWORK INDICATORS
Type     | Value                    | Confidence | Context
---------|--------------------------|------------|--------
IPv4     | 185.220.101[.]42         | 95         | Qakbot C2 server
IPv4     | 91.215.85[.]17           | 90         | Cobalt Strike C2
Domain   | update.evil[.]com        | 95         | Staging domain
URL      | hxxps://185.220[.]101.42/gate.php | 95  | C2 check-in
JA3      | a0e9f5d64349fb13191bc7...| 80         | Qakbot TLS fingerprint
 
HOST INDICATORS
Type     | Value                    | Confidence | Context
---------|--------------------------|------------|--------
SHA-256  | a1b2c3d4e5f6...         | 100        | Qakbot dropper
SHA-256  | b2c3d4e5f6a7...         | 100        | Cobalt Strike beacon
FilePath | C:\Users\*\AppData\Local\Temp\update.exe | 85 | Dropper location
RegKey   | HKCU\...\Run\svcupdate  | 90         | Persistence
Mutex    | Global\MTX_0x1234ABCD   | 95         | Qakbot instance lock
Task     | WindowsUpdate           | 90         | Scheduled task persistence
 
EMAIL INDICATORS
Type     | Value                    | Confidence | Context
---------|--------------------------|------------|--------
Sender   | billing@spoofed[.]com   | 95         | Phishing sender
Subject  | "Invoice-Nov2025"       | 70         | Phishing subject line
Hash     | c3d4e5f6a7b8...         | 100        | Malicious .docm attachment
 
TOTAL: 14 indicators | HIGH confidence avg: 91
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.2 KB

API Reference: IOC Collection Agent

Overview

Extracts indicators of compromise from text/files using regex patterns, enriches them via VirusTotal and MalwareBazaar APIs, assigns confidence scores, and exports as a STIX 2.1 bundle.

Dependencies

Package Version Purpose
requests >=2.28 API enrichment calls
stix2 >=3.0 STIX 2.1 bundle creation

CLI Usage

python agent.py --input-file incident_report.txt --vt-key <key> --output iocs.json
python agent.py --input-text "C2 at 185.220.101.42 hash a1b2c3..." --output iocs.json

Key Functions

extract_iocs_from_text(text)

Extracts IPv4 addresses, domains, SHA-256/MD5 hashes, and URLs using regex patterns.

extract_iocs_from_file(file_path)

Reads a file and delegates to extract_iocs_from_text.

enrich_ip_virustotal(ip_address, api_key)

Queries VirusTotal v3 API for IP reputation (malicious count, ASN, country).

enrich_hash_malwarebazaar(file_hash)

Queries MalwareBazaar for file hash metadata (family, type, tags).

enrich_domain_abuseipdb(domain, api_key)

Checks domain reputation via AbuseIPDB API (abuse confidence, report count).

score_ioc(ioc_type, enrichment_data)

Assigns confidence score (0-100) based on enrichment results and detection counts.

export_stix_bundle(iocs_with_scores, output_path)

Creates STIX 2.1 Indicator objects and exports as a Bundle JSON file.

External APIs Used

API Endpoint Auth Purpose
VirusTotal v3 /api/v3/ip_addresses/{ip} API key header IP reputation
MalwareBazaar https://mb-api.abuse.ch/api/v1/ None Hash lookup
AbuseIPDB /api/v2/check API key header Domain/IP reputation

IOC Types Supported

Type Regex Pattern STIX Pattern
IPv4 \b(?:\d{1,3}\.){3}\d{1,3}\b [ipv4-addr:value = '...']
Domain \b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b [domain-name:value = '...']
SHA-256 \b[a-fA-F0-9]{64}\b [file:hashes.'SHA-256' = '...']
MD5 \b[a-fA-F0-9]{32}\b [file:hashes.MD5 = '...']
URL https?://[^\s"'<>]+ [url:value = '...']

Scripts 1

agent.py7.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""IOC Collection Agent - Extracts, enriches, and exports indicators of compromise."""

import json
import re
import logging
import argparse
from datetime import datetime

import requests
from stix2 import Indicator, Bundle

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

IPV4_PATTERN = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
DOMAIN_PATTERN = re.compile(r"\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b")
SHA256_PATTERN = re.compile(r"\b[a-fA-F0-9]{64}\b")
MD5_PATTERN = re.compile(r"\b[a-fA-F0-9]{32}\b")
URL_PATTERN = re.compile(r"https?://[^\s\"'<>]+")


def extract_iocs_from_text(text):
    """Extract IOCs (IPs, domains, hashes, URLs) from unstructured text."""
    iocs = {
        "ipv4": list(set(IPV4_PATTERN.findall(text))),
        "domain": list(set(DOMAIN_PATTERN.findall(text))),
        "sha256": list(set(SHA256_PATTERN.findall(text))),
        "md5": list(set(MD5_PATTERN.findall(text))),
        "url": list(set(URL_PATTERN.findall(text))),
    }
    total = sum(len(v) for v in iocs.values())
    logger.info("Extracted %d IOCs from text input", total)
    return iocs


def extract_iocs_from_file(file_path):
    """Extract IOCs from a file (text report, log file, etc.)."""
    with open(file_path, "r", errors="ignore") as f:
        content = f.read()
    return extract_iocs_from_text(content)


def enrich_ip_virustotal(ip_address, api_key):
    """Enrich an IP address using VirusTotal API."""
    url = f"https://www.virustotal.com/api/v3/ip_addresses/{ip_address}"
    headers = {"x-apikey": api_key}
    resp = requests.get(url, headers=headers, timeout=30)
    if resp.status_code == 200:
        data = resp.json()["data"]["attributes"]
        result = {
            "ip": ip_address,
            "malicious": data.get("last_analysis_stats", {}).get("malicious", 0),
            "country": data.get("country", "unknown"),
            "asn": data.get("asn", 0),
            "as_owner": data.get("as_owner", "unknown"),
        }
        logger.info("VT enrichment for %s: %d malicious detections", ip_address, result["malicious"])
        return result
    return {"ip": ip_address, "error": resp.status_code}


def enrich_hash_malwarebazaar(file_hash):
    """Enrich a file hash using MalwareBazaar API."""
    url = "https://mb-api.abuse.ch/api/v1/"
    resp = requests.post(url, data={"query": "get_info", "hash": file_hash}, timeout=30)
    result = resp.json()
    if result.get("query_status") == "ok":
        sample = result["data"][0]
        return {
            "hash": file_hash,
            "family": sample.get("signature", "unknown"),
            "file_type": sample.get("file_type", "unknown"),
            "tags": sample.get("tags", []),
            "first_seen": sample.get("first_seen"),
        }
    return {"hash": file_hash, "status": "not_found"}


def enrich_domain_abuseipdb(domain, api_key):
    """Check domain reputation via AbuseIPDB."""
    url = "https://api.abuseipdb.com/api/v2/check"
    headers = {"Key": api_key, "Accept": "application/json"}
    resp = requests.get(url, headers=headers, params={"ipAddress": domain}, timeout=30)
    if resp.status_code == 200:
        data = resp.json()["data"]
        return {
            "domain": domain,
            "abuse_confidence": data.get("abuseConfidenceScore", 0),
            "total_reports": data.get("totalReports", 0),
            "country": data.get("countryCode", "unknown"),
        }
    return {"domain": domain, "error": resp.status_code}


def score_ioc(ioc_type, enrichment_data):
    """Assign confidence score (0-100) based on enrichment results."""
    if ioc_type == "ipv4":
        malicious_count = enrichment_data.get("malicious", 0)
        if malicious_count >= 10:
            return 95
        elif malicious_count >= 5:
            return 80
        elif malicious_count >= 1:
            return 60
        return 30
    elif ioc_type in ("sha256", "md5"):
        if enrichment_data.get("family") and enrichment_data["family"] != "unknown":
            return 95
        return 50
    return 50


def export_stix_bundle(iocs_with_scores, output_path):
    """Export IOCs as a STIX 2.1 bundle."""
    pattern_map = {
        "ipv4": lambda v: f"[ipv4-addr:value = '{v}']",
        "domain": lambda v: f"[domain-name:value = '{v}']",
        "url": lambda v: f"[url:value = '{v}']",
        "sha256": lambda v: f"[file:hashes.'SHA-256' = '{v}']",
        "md5": lambda v: f"[file:hashes.MD5 = '{v}']",
    }
    stix_indicators = []
    for ioc in iocs_with_scores:
        ioc_type = ioc["type"]
        pattern_fn = pattern_map.get(ioc_type)
        if pattern_fn:
            indicator = Indicator(
                name=f"{ioc_type}: {ioc['value']}",
                pattern=pattern_fn(ioc["value"]),
                pattern_type="stix",
                valid_from=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
                confidence=ioc.get("confidence", 50),
                labels=[ioc_type],
            )
            stix_indicators.append(indicator)

    bundle = Bundle(objects=stix_indicators)
    with open(output_path, "w") as f:
        f.write(bundle.serialize(pretty=True))
    logger.info("STIX bundle exported: %d indicators to %s", len(stix_indicators), output_path)


def generate_ioc_report(iocs_with_scores):
    """Print a formatted IOC report."""
    lines = [
        "INDICATOR OF COMPROMISE REPORT",
        "=" * 40,
        f"Date: {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}",
        f"Total IOCs: {len(iocs_with_scores)}",
        "",
    ]
    for ioc_type in ["ipv4", "domain", "sha256", "md5", "url"]:
        typed = [i for i in iocs_with_scores if i["type"] == ioc_type]
        if typed:
            lines.append(f"\n{ioc_type.upper()} ({len(typed)}):")
            for ioc in typed[:10]:
                lines.append(f"  {ioc['value'][:60]:60s} Confidence: {ioc.get('confidence', 'N/A')}")
    print("\n".join(lines))


def main():
    parser = argparse.ArgumentParser(description="IOC Collection Agent")
    parser.add_argument("--input-file", help="File to extract IOCs from")
    parser.add_argument("--input-text", help="Text string to extract IOCs from")
    parser.add_argument("--vt-key", help="VirusTotal API key for enrichment")
    parser.add_argument("--output", default="ioc_bundle.json", help="STIX bundle output")
    args = parser.parse_args()

    raw_iocs = {}
    if args.input_file:
        raw_iocs = extract_iocs_from_file(args.input_file)
    elif args.input_text:
        raw_iocs = extract_iocs_from_text(args.input_text)

    iocs_with_scores = []
    for ioc_type, values in raw_iocs.items():
        for value in values:
            entry = {"type": ioc_type, "value": value, "confidence": 50}
            if ioc_type == "ipv4" and args.vt_key:
                enrichment = enrich_ip_virustotal(value, args.vt_key)
                entry["confidence"] = score_ioc("ipv4", enrichment)
                entry["enrichment"] = enrichment
            elif ioc_type in ("sha256", "md5"):
                enrichment = enrich_hash_malwarebazaar(value)
                entry["confidence"] = score_ioc(ioc_type, enrichment)
                entry["enrichment"] = enrichment
            iocs_with_scores.append(entry)

    generate_ioc_report(iocs_with_scores)
    export_stix_bundle(iocs_with_scores, args.output)


if __name__ == "__main__":
    main()
Keep exploring