建置 IOC 去活化與分享管道 是做什麼的?
概述
IOC 去活化(defanging)可修改潛在的惡意指標(URL、IP 位址、網域、電子郵件地址),以防止意外點擊或執行,同時保留可讀性,便於分析與分享。此技能涵蓋建置自動化管線,從多個來源擷取原始 IOC、進行正規化與去重、套用去活化以安全地供人類閱讀、轉換為 STIX 2.1 格式以利機器讀取,並透過 TAXII 伺服器、MISP 實例與電子郵件報告進行分發。
使用時機
- 當需要在您的環境中部署或設定 IOC 去活化與分享管線功能時
- 當建立符合合規要求的安全控管時
- 當為此領域建置或改善安全架構時
- 當進行需要此實作的安全評估時
先決條件
- Python 3.9+ 並安裝
defang、ioc-fanger、stix2、requests、validators函式庫 - 用於自動化分享的 MISP 實例或 TAXII 伺服器
- 了解 IOC 類型:IPv4/IPv6、網域、URL、電子郵件地址、檔案雜湊
- 熟悉 STIX 2.1 Indicator 模式與 TLP 標記定義
- 可存取威脅情資來源以擷取 IOC
關鍵概念
IOC 去活化標準
去活化會取代活動中的協定與網域元件,以防止執行:http:// 變成 hxxp://,https:// 變成 hxxps://,網域/IP 中的點號變成 [.],電子郵件中的 @ 變成 [@]。這對於在報告、電子郵件、Slack 頻道以及貼文網站中分享 IOC 至關重要,因為自動連結可能觸發與惡意基礎設施的網路連線。
IOC 正規化
不同來源的原始 IOC 格式不一致。正規化包括轉換為小寫、移除尾部斜線與空白、從 URL 中提取網域、解析 URL 編碼、驗證格式正確性,以及跨來源去重。
STIX 2.1 Indicator 模式
STIX 模式以標準化格式表達 IOC:[ipv4-addr:value = '203.0.113.1']、[domain-name:value = 'malicious.example.com']、[url:value = 'http://evil.com/payload']、[file:hashes.'SHA-256' = 'abc123...']。每個 Indicator 包含 valid_from、indicator_types、confidence 以及可選的 TLP 標記。
工作流程
步驟 1:建置 IOC 提取與正規化
import re
import hashlib
from urllib.parse import urlparse, unquote
from datetime import datetime
class IOCExtractor:
"""從文字中提取並正規化 IOC。"""
PATTERNS = {
"ipv4": r'\b(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\b',
"domain": r'\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b',
"url": r'https?://[^\s<>"{}|\\^`\[\]]+',
"email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
"md5": r'\b[a-fA-F0-9]{32}\b',
"sha1": r'\b[a-fA-F0-9]{40}\b',
"sha256": r'\b[a-fA-F0-9]{64}\b',
}
WHITELIST_DOMAINS = {
"google.com", "microsoft.com", "amazon.com", "github.com",
"cloudflare.com", "akamai.com", "example.com",
}
def extract_from_text(self, text):
"""從自由文字中提取所有 IOC 類型。"""
# 先對任何已去活化的指標進行復原
text = self._refang(text)
iocs = {"ipv4": set(), "domain": set(), "url": set(),
"email": set(), "md5": set(), "sha1": set(), "sha256": set()}
for ioc_type, pattern in self.PATTERNS.items():
matches = re.findall(pattern, text)
for match in matches:
normalized = self._normalize(match, ioc_type)
if normalized and not self._is_whitelisted(normalized, ioc_type):
iocs[ioc_type].add(normalized)
# 移除屬於 URL 一部分的網域
url_domains = set()
for url in iocs["url"]:
parsed = urlparse(url)
url_domains.add(parsed.netloc)
iocs["domain"] -= url_domains
total = sum(len(v) for v in iocs.values())
print(f"[+] 從文字中提取了 {total} 個唯一 IOC")
return {k: sorted(v) for k, v in iocs.items()}
def _refang(self, text):
"""將去活化的指標轉換回作用形式。"""
text = text.replace("hxxp://", "http://").replace("hxxps://", "https://")
text = text.replace("[.]", ".").replace("[@]", "@")
text = text.replace("[://]", "://").replace("(.)", ".")
return text
def _normalize(self, value, ioc_type):
"""正規化 IOC 值。"""
value = value.strip().lower()
if ioc_type == "url":
value = unquote(value).rstrip("/")
elif ioc_type == "domain":
value = value.rstrip(".")
return value
def _is_whitelisted(self, value, ioc_type):
"""檢查 IOC 是否在白名單中。"""
if ioc_type == "domain":
return value in self.WHITELIST_DOMAINS
if ioc_type == "url":
parsed = urlparse(value)
return parsed.netloc in self.WHITELIST_DOMAINS
return False
extractor = IOCExtractor()
sample_text = """
惡意軟體 C2:hxxps://evil-domain[.]com/beacon
從 192.168.1.100 下載惡意載入,並聯絡 10[.]0[.]0[.]1
SHA256:275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f
來自 attacker[@]phishing-domain[.]com 的釣魚郵件
"""
iocs = extractor.extract_from_text(sample_text)
步驟 2:去活化引擎
class IOCDefanger:
"""將 IOC 去活化,以便在報告與通訊中安全分享。"""
def defang_url(self, url):
return url.replace("http://", "hxxp://").replace("https://", "hxxps://").replace(".", "[.]")
def defang_domain(self, domain):
return domain.replace(".", "[.]")
def defang_ip(self, ip):
return ip.replace(".", "[.]")
def defang_email(self, email):
return email.replace("@", "[@]").replace(".", "[.]")
def defang_all(self, iocs):
"""將字典中的所有 IOC 去活化。"""
defanged = {}
for ioc_type, values in iocs.items():
if ioc_type == "url":
defanged[ioc_type] = [self.defang_url(v) for v in values]
elif ioc_type == "domain":
defanged[ioc_type] = [self.defang_domain(v) for v in values]
elif ioc_type == "ipv4":
defanged[ioc_type] = [self.defang_ip(v) for v in values]
elif ioc_type == "email":
defanged[ioc_type] = [self.defang_email(v) for v in values]
else:
defanged[ioc_type] = values # 雜湊不需要去活化
return defanged
def generate_sharing_report(self, iocs, defanged, report_name="IOC 報告"):
"""產生人類可讀的去活化 IOC 報告。"""
report = f"# {report_name}\n"
report += f"產生時間:{datetime.now().isoformat()}\n\n"
for ioc_type in ["url", "domain", "ipv4", "email", "sha256", "sha1", "md5"]:
values = defanged.get(ioc_type, [])
if values:
report += f"## {ioc_type.upper()}(共 {len(values)} 個)\n"
for v in values:
report += f"- `{v}`\n"
report += "\n"
return report
defanger = IOCDefanger()
defanged = defanger.defang_all(iocs)
report = defanger.generate_sharing_report(iocs, defanged, "惡意軟體活動 IOC")
print(report)
步驟 3:轉換為 STIX 2.1 格式
from stix2 import Indicator, Bundle, TLP_WHITE, TLP_GREEN, TLP_AMBER
from datetime import datetime
class STIXConverter:
"""將原始 IOC 轉換為 STIX 2.1 Indicator 物件。"""
TLP_MAP = {"white": TLP_WHITE, "green": TLP_GREEN, "amber": TLP_AMBER}
def iocs_to_stix(self, iocs, tlp="green", confidence=75):
"""將 IOC 字典轉換為 STIX 2.1 套件。"""
stix_objects = []
marking = self.TLP_MAP.get(tlp, TLP_GREEN)
for ip in iocs.get("ipv4", []):
stix_objects.append(Indicator(
name=f"惡意 IP:{ip}",
pattern=f"[ipv4-addr:value = '{ip}']",
pattern_type="stix",
valid_from=datetime.now(),
indicator_types=["malicious-activity"],
confidence=confidence,
object_marking_refs=[marking],
))
for domain in iocs.get("domain", []):
stix_objects.append(Indicator(
name=f"惡意網域:{domain}",
pattern=f"[domain-name:value = '{domain}']",
pattern_type="stix",
valid_from=datetime.now(),
indicator_types=["malicious-activity"],
confidence=confidence,
object_marking_refs=[marking],
))
for url in iocs.get("url", []):
escaped = url.replace("'", "\\'")
stix_objects.append(Indicator(
name=f"惡意 URL:{url[:60]}",
pattern=f"[url:value = '{escaped}']",
pattern_type="stix",
valid_from=datetime.now(),
indicator_types=["malicious-activity"],
confidence=confidence,
object_marking_refs=[marking],
))
for sha256 in iocs.get("sha256", []):
stix_objects.append(Indicator(
name=f"惡意檔案雜湊:{sha256[:16]}...",
pattern=f"[file:hashes.'SHA-256' = '{sha256}']",
pattern_type="stix",
valid_from=datetime.now(),
indicator_types=["malicious-activity"],
confidence=confidence,
object_marking_refs=[marking],
))
bundle = Bundle(objects=stix_objects)
print(f"[+] 已建立包含 {len(stix_objects)} 個 Indicator 的 STIX 套件")
return bundle
converter = STIXConverter()
stix_bundle = converter.iocs_to_stix(iocs, tlp="amber", confidence=80)
with open("iocs_stix_bundle.json", "w") as f:
f.write(stix_bundle.serialize(pretty=True))
步驟 4:透過 MISP 和 TAXII 分發
import requests
import json
class IOCDistributor:
"""透過多種管道分發 IOC。"""
def push_to_misp(self, iocs, misp_url, misp_key, event_info):
"""將 IOC 以新事件形式推送到 MISP。"""
headers = {
"Authorization": misp_key,
"Content-Type": "application/json",
"Accept": "application/json",
}
event = {
"Event": {
"info": event_info,
"distribution": "1", # 僅限此社群
"threat_level_id": "2", # 中
"analysis": "2", # 已完成
"Attribute": [],
}
}
type_mapping = {
"ipv4": "ip-dst",
"domain": "domain",
"url": "url",
"email": "email-src",
"md5": "md5",
"sha1": "sha1",
"sha256": "sha256",
}
for ioc_type, values in iocs.items():
misp_type = type_mapping.get(ioc_type)
if misp_type:
for value in values:
event["Event"]["Attribute"].append({
"type": misp_type,
"value": value,
"category": "Network activity" if ioc_type in ("ipv4", "domain", "url") else "Payload delivery",
"to_ids": True,
})
resp = requests.post(
f"{misp_url}/events",
headers=headers,
json=event,
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # 在實驗室環境中使用自簽憑證時,設定 SKIP_TLS_VERIFY=true 以跳過 TLS 驗證
)
if resp.status_code == 200:
event_id = resp.json().get("Event", {}).get("id", "")
print(f"[+] MISP 事件已建立:{event_id}")
return event_id
else:
print(f"[-] MISP 錯誤:{resp.status_code} - {resp.text[:200]}")
return None
def push_to_taxii(self, stix_bundle, taxii_url, collection_id, username, password):
"""將 STIX 套件推送到 TAXII 2.1 集合。"""
from taxii2client.v21 import Collection
collection = Collection(
f"{taxii_url}/collections/{collection_id}/",
user=username, password=password,
)
response = collection.add_objects(stix_bundle.serialize())
print(f"[+] TAXII:已發布套件,狀態:{response.status}")
return response
distributor = IOCDistributor()
distributor.push_to_misp(
iocs,
misp_url="https://misp.organization.com",
misp_key="YOUR_MISP_API_KEY",
event_info="惡意軟體活動 IOC - 2025",
)
驗證標準
- 從自由文字中正確提取 IOC,並支援復原(refanging)
- 去活化產生安全、不可點擊的指標
- STIX 2.1 套件包含有效的 Indicator 模式
- IOC 成功分發至 MISP 和 TAXII
- 去重功能防止重複的 Indicator
- 白名單功能防止已知良好網域產生誤報