npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
MITRE ATLAS
When to Use
Use this skill when:
- A phishing email or alert generates IOCs (URLs, IP addresses, file hashes) requiring rapid triage
- Automated feeds deliver bulk IOCs that need confidence scoring before ingestion into blocking controls
- An incident investigation requires contextual enrichment of observed network artifacts
Do not use this skill in isolation for high-stakes blocking decisions — always combine automated enrichment with analyst judgment, especially for shared infrastructure (CDNs, cloud providers).
Prerequisites
- VirusTotal API key (free or Enterprise) for multi-AV and sandbox lookup
- AbuseIPDB API key for IP reputation checks
- MISP instance or TIP for cross-referencing against known campaigns
- Python with
requestsandvt-pylibraries, or SOAR platform with pre-built connectors
Workflow
Step 1: Normalize and Classify IOC Types
Before enriching, classify each IOC:
- IPv4/IPv6 address: Check if RFC 1918 private (skip external enrichment), validate format
- Domain/FQDN: Defang for safe handling (
evil[.]com), extract registered domain via tldextract - URL: Extract domain + path separately; check for redirectors
- File hash: Identify hash type (MD5/SHA-1/SHA-256); prefer SHA-256 for uniqueness
- Email address: Split into domain (check MX/DMARC) and local part for pattern analysis
Defang IOCs in documentation (replace . with [.] and :// with [://]) to prevent accidental clicks.
Step 2: Multi-Source Enrichment
VirusTotal (file hash, URL, IP, domain):
import vt
client = vt.Client("YOUR_VT_API_KEY")
# File hash lookup
file_obj = client.get_object(f"/files/{sha256_hash}")
detections = file_obj.last_analysis_stats
print(f"Malicious: {detections['malicious']}/{sum(detections.values())}")
# Domain analysis
domain_obj = client.get_object(f"/domains/{domain}")
print(domain_obj.last_analysis_stats)
print(domain_obj.reputation)
client.close()AbuseIPDB (IP addresses):
import requests
response = requests.get(
"https://api.abuseipdb.com/api/v2/check",
headers={"Key": "YOUR_KEY", "Accept": "application/json"},
params={"ipAddress": "1.2.3.4", "maxAgeInDays": 90}
)
data = response.json()["data"]
print(f"Confidence: {data['abuseConfidenceScore']}%, Reports: {data['totalReports']}")MalwareBazaar (file hashes):
response = requests.post(
"https://mb-api.abuse.ch/api/v1/",
data={"query": "get_info", "hash": sha256_hash}
)
result = response.json()
if result["query_status"] == "ok":
print(result["data"][0]["tags"], result["data"][0]["signature"])Step 3: Contextualize with Campaign Attribution
Query MISP for existing events matching the IOC:
from pymisp import PyMISP
misp = PyMISP("https://misp.example.com", "API_KEY")
results = misp.search(value="evil-domain.com", type_attribute="domain")
for event in results:
print(event["Event"]["info"], event["Event"]["threat_level_id"])Check Shodan for IP context (hosting provider, open ports, banners) to identify if the IP belongs to bulletproof hosting or a legitimate cloud provider (false positive risk).
Step 4: Assign Confidence Score and Disposition
Apply a tiered decision framework:
- Block (High Confidence ≥ 70%): ≥15 AV detections on VT, AbuseIPDB score ≥70, matches known malware family or campaign
- Monitor/Alert (Medium 40–69%): 5–14 AV detections, moderate AbuseIPDB score, no campaign attribution
- Whitelist/Investigate (Low <40%): ≤4 AV detections, no abuse reports, legitimate service (Google, Cloudflare CDN IPs)
- False Positive: Legitimate business service incorrectly flagged; document and exclude from future alerts
Step 5: Document and Distribute
Record findings in TIP/MISP with:
- All enrichment data collected (timestamps, source, score)
- Disposition decision and rationale
- Blocking actions taken (firewall, proxy, DNS sinkhole)
- Related incident ticket number
Export to STIX indicator object with confidence field set appropriately.
Key Concepts
| Term | Definition |
|---|---|
| IOC | Indicator of Compromise — observable network or host artifact indicating potential compromise |
| Enrichment | Process of adding contextual data to a raw IOC from multiple intelligence sources |
| Defanging | Modifying IOCs (replacing . with [.]) to prevent accidental activation in documentation |
| False Positive Rate | Percentage of benign artifacts incorrectly flagged as malicious; critical for tuning block thresholds |
| Sinkhole | DNS server redirecting malicious domain lookups to a benign IP for detection without blocking traffic entirely |
| TTL | Time-to-live for an IOC in blocking controls; IP indicators should expire after 30 days, domains after 90 days |
Tools & Systems
- VirusTotal: Multi-engine malware scanner and threat intelligence platform with 70+ AV engines, sandbox reports, and community comments
- AbuseIPDB: Community-maintained IP reputation database with 90-day abuse report history
- MalwareBazaar (abuse.ch): Free malware hash repository with YARA rule associations and malware family tagging
- URLScan.io: Free URL analysis service that captures screenshots, DOM, and network requests for phishing URL triage
- Shodan: Internet-wide scan data providing hosting provider, open ports, and banner information for IP enrichment
Common Pitfalls
- Blocking shared infrastructure: CDN IPs (Cloudflare 104.21.x.x, AWS CloudFront) may legitimately host malicious content but blocking the IP disrupts thousands of legitimate sites.
- VT score obsession: Low VT detection count does not mean benign — zero-day malware and custom APT tools often score 0 initially. Check sandbox behavior, MISP, and passive DNS.
- Missing defanging: Pasting live IOCs in emails or Confluence docs can trigger automated URL scanners or phishing tools.
- No expiration policy: IOCs without TTLs accumulate in blocklists indefinitely, generating false positives as infrastructure is repurposed by legitimate users.
- Over-relying on single source: VirusTotal aggregates AV opinions — all may be wrong or lag behind emerging malware. Use 3+ independent sources for high-stakes decisions.
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md2.9 KB
API Reference: IOC Enrichment Tools
VirusTotal API v3
File Hash Lookup
curl -H "x-apikey: $VT_KEY" \
"https://www.virustotal.com/api/v3/files/<sha256>"Domain Lookup
curl -H "x-apikey: $VT_KEY" \
"https://www.virustotal.com/api/v3/domains/<domain>"IP Lookup
curl -H "x-apikey: $VT_KEY" \
"https://www.virustotal.com/api/v3/ip_addresses/<ip>"Key Response Fields
| Field | Description |
|---|---|
last_analysis_stats.malicious |
Number of AV engines detecting as malicious |
last_analysis_stats.undetected |
AV engines finding clean |
reputation |
Community reputation score |
popular_threat_classification |
Threat label consensus |
Python (vt-py)
import vt
client = vt.Client("API_KEY")
file_obj = client.get_object(f"/files/{sha256}")
stats = file_obj.last_analysis_stats
client.close()AbuseIPDB API v2
Check IP
curl -G "https://api.abuseipdb.com/api/v2/check" \
-H "Key: $ABUSE_KEY" -H "Accept: application/json" \
-d "ipAddress=1.2.3.4" -d "maxAgeInDays=90"Response Fields
| Field | Description |
|---|---|
abuseConfidenceScore |
0-100 abuse confidence |
totalReports |
Report count in timeframe |
countryCode |
Source country |
isp |
Internet service provider |
isTor |
Tor exit node flag |
MalwareBazaar API (abuse.ch)
Hash Lookup
curl -X POST "https://mb-api.abuse.ch/api/v1/" \
-d "query=get_info" -d "hash=<sha256>"Response Fields
| Field | Description |
|---|---|
signature |
Malware family name |
tags |
Associated tags |
file_type |
File type identification |
first_seen |
First submission date |
reporter |
Submitting analyst |
URLScan.io API
Submit URL for Scan
curl -X POST "https://urlscan.io/api/v1/scan/" \
-H "API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"url": "http://suspicious.com", "visibility": "private"}'Retrieve Results
curl "https://urlscan.io/api/v1/result/<uuid>/"Shodan API
IP Lookup
curl "https://api.shodan.io/shodan/host/<ip>?key=$SHODAN_KEY"Response Fields
| Field | Description |
|---|---|
ports |
Open ports list |
os |
Operating system |
org |
Organization |
asn |
Autonomous system number |
hostnames |
Associated hostnames |
IOC Confidence Scoring Framework
| Score | Disposition | Criteria |
|---|---|---|
| >= 70 | BLOCK | 15+ VT detections, AbuseIPDB >= 70%, or MalwareBazaar match |
| 40-69 | MONITOR | 5-14 VT detections, moderate abuse score |
| < 40 | INVESTIGATE | Low detection, no campaign attribution |
Defanging Convention
| Original | Defanged |
|---|---|
http:// |
hxxp:// |
https:// |
hxxps:// |
.com |
[.]com |
evil.com |
evil[.]com |
Scripts 1
agent.py8.8 KB
#!/usr/bin/env python3
"""IOC analysis and enrichment agent using VirusTotal, AbuseIPDB, and MalwareBazaar APIs."""
import re
import os
import json
import datetime
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
def classify_ioc(value):
"""Classify an IOC by type: ipv4, domain, url, sha256, sha1, md5, email."""
value = value.strip()
if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value):
return "ipv4"
if re.match(r"^[a-fA-F0-9]{64}$", value):
return "sha256"
if re.match(r"^[a-fA-F0-9]{40}$", value):
return "sha1"
if re.match(r"^[a-fA-F0-9]{32}$", value):
return "md5"
if re.match(r"^https?://", value):
return "url"
if re.match(r"^[^@]+@[^@]+\.[^@]+$", value):
return "email"
if re.match(r"^[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", value):
return "domain"
return "unknown"
def defang_ioc(value):
"""Defang an IOC for safe documentation."""
value = value.replace("http://", "hxxp://")
value = value.replace("https://", "hxxps://")
value = re.sub(r"\.(?=\w)", "[.]", value)
return value
def refang_ioc(value):
"""Refang a defanged IOC for querying APIs."""
value = value.replace("hxxp://", "http://")
value = value.replace("hxxps://", "https://")
value = value.replace("[.]", ".")
value = value.replace("[://]", "://")
return value
def is_private_ip(ip):
"""Check if an IP is RFC 1918 private."""
octets = [int(o) for o in ip.split(".")]
if octets[0] == 10:
return True
if octets[0] == 172 and 16 <= octets[1] <= 31:
return True
if octets[0] == 192 and octets[1] == 168:
return True
if octets[0] == 127:
return True
return False
def query_virustotal_hash(sha256, api_key):
"""Query VirusTotal for a file hash."""
url = f"https://www.virustotal.com/api/v3/files/{sha256}"
resp = requests.get(url, headers={"x-apikey": api_key}, timeout=30)
if resp.status_code == 200:
data = resp.json().get("data", {}).get("attributes", {})
stats = data.get("last_analysis_stats", {})
return {
"sha256": sha256,
"malicious": stats.get("malicious", 0),
"total": sum(stats.values()),
"type_description": data.get("type_description", ""),
"popular_threat_name": data.get("popular_threat_classification", {}).get(
"suggested_threat_label", ""),
"tags": data.get("tags", []),
}
return None
def query_virustotal_domain(domain, api_key):
"""Query VirusTotal for domain reputation."""
url = f"https://www.virustotal.com/api/v3/domains/{domain}"
resp = requests.get(url, headers={"x-apikey": api_key}, timeout=30)
if resp.status_code == 200:
data = resp.json().get("data", {}).get("attributes", {})
stats = data.get("last_analysis_stats", {})
return {
"domain": domain,
"malicious": stats.get("malicious", 0),
"suspicious": stats.get("suspicious", 0),
"reputation": data.get("reputation", 0),
"registrar": data.get("registrar", ""),
"creation_date": data.get("creation_date", ""),
}
return None
def query_abuseipdb(ip, api_key, max_age_days=90):
"""Query AbuseIPDB for IP reputation."""
url = "https://api.abuseipdb.com/api/v2/check"
resp = requests.get(url, headers={"Key": api_key, "Accept": "application/json"},
params={"ipAddress": ip, "maxAgeInDays": max_age_days}, timeout=30)
if resp.status_code == 200:
data = resp.json().get("data", {})
return {
"ip": ip,
"abuse_confidence": data.get("abuseConfidenceScore", 0),
"total_reports": data.get("totalReports", 0),
"country": data.get("countryCode", ""),
"isp": data.get("isp", ""),
"domain": data.get("domain", ""),
"is_tor": data.get("isTor", False),
}
return None
def query_malwarebazaar(sha256):
"""Query MalwareBazaar for file hash information."""
url = "https://mb-api.abuse.ch/api/v1/"
resp = requests.post(url, data={"query": "get_info", "hash": sha256}, timeout=30)
if resp.status_code == 200:
result = resp.json()
if result.get("query_status") == "ok" and result.get("data"):
entry = result["data"][0]
return {
"sha256": sha256,
"signature": entry.get("signature", ""),
"tags": entry.get("tags", []),
"file_type": entry.get("file_type", ""),
"reporter": entry.get("reporter", ""),
"first_seen": entry.get("first_seen", ""),
}
return None
def score_ioc(vt_result=None, abuse_result=None, mb_result=None):
"""Assign a confidence score and disposition to an IOC."""
score = 0
reasons = []
if vt_result:
malicious = vt_result.get("malicious", 0)
if malicious >= 15:
score += 40
reasons.append(f"VT: {malicious} detections (high)")
elif malicious >= 5:
score += 20
reasons.append(f"VT: {malicious} detections (moderate)")
elif malicious > 0:
score += 5
reasons.append(f"VT: {malicious} detections (low)")
if abuse_result:
abuse_score = abuse_result.get("abuse_confidence", 0)
if abuse_score >= 70:
score += 30
reasons.append(f"AbuseIPDB: {abuse_score}% confidence")
elif abuse_score >= 30:
score += 15
reasons.append(f"AbuseIPDB: {abuse_score}% confidence")
if mb_result:
score += 30
reasons.append(f"MalwareBazaar: {mb_result.get('signature', 'known malware')}")
if score >= 70:
disposition = "BLOCK"
elif score >= 40:
disposition = "MONITOR"
else:
disposition = "INVESTIGATE"
return {"score": score, "disposition": disposition, "reasons": reasons}
def enrich_ioc(value, vt_key=None, abuse_key=None):
"""Enrich a single IOC with multi-source intelligence."""
ioc_type = classify_ioc(value)
result = {
"ioc": value,
"type": ioc_type,
"defanged": defang_ioc(value),
"enrichment": {},
"timestamp": datetime.datetime.utcnow().isoformat() + "Z",
}
if not HAS_REQUESTS:
result["error"] = "requests library not installed"
return result
if ioc_type == "ipv4" and is_private_ip(value):
result["note"] = "RFC 1918 private IP - skipping external enrichment"
return result
if ioc_type in ("sha256", "sha1", "md5") and vt_key:
result["enrichment"]["virustotal"] = query_virustotal_hash(value, vt_key)
result["enrichment"]["malwarebazaar"] = query_malwarebazaar(value)
elif ioc_type == "ipv4":
if abuse_key:
result["enrichment"]["abuseipdb"] = query_abuseipdb(value, abuse_key)
if vt_key:
result["enrichment"]["virustotal"] = query_virustotal_domain(value, vt_key)
elif ioc_type == "domain" and vt_key:
result["enrichment"]["virustotal"] = query_virustotal_domain(value, vt_key)
scoring = score_ioc(
result["enrichment"].get("virustotal"),
result["enrichment"].get("abuseipdb"),
result["enrichment"].get("malwarebazaar"),
)
result["score"] = scoring["score"]
result["disposition"] = scoring["disposition"]
result["reasons"] = scoring["reasons"]
return result
if __name__ == "__main__":
print("=" * 60)
print("IOC Analysis & Enrichment Agent")
print("VirusTotal, AbuseIPDB, MalwareBazaar integration")
print("=" * 60)
demo_iocs = [
"185.220.101.42",
"evil-domain.com",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"http://malicious-site.com/payload.exe",
"192.168.1.100",
]
print("\n--- IOC Classification & Defanging ---")
for ioc in demo_iocs:
ioc_type = classify_ioc(ioc)
defanged = defang_ioc(ioc)
private = " (private)" if ioc_type == "ipv4" and is_private_ip(ioc) else ""
print(f" {ioc_type:8s} | {defanged}{private}")
vt_key = os.environ.get("VT_API_KEY")
abuse_key = os.environ.get("ABUSEIPDB_API_KEY")
if vt_key or abuse_key:
print("\n--- Enrichment (live API queries) ---")
for ioc in demo_iocs:
result = enrich_ioc(ioc, vt_key, abuse_key)
print(f"\n {result['ioc']} ({result['type']})")
print(f" Disposition: {result.get('disposition', 'N/A')} "
f"(score: {result.get('score', 0)})")
for reason in result.get("reasons", []):
print(f" - {reason}")
else:
print("\n[*] Set VT_API_KEY and/or ABUSEIPDB_API_KEY environment variables for live enrichment.")