分析 DNS 日誌以偵測資料外洩
使用時機
請在以下情況使用此技能:
- SOC 團隊懷疑攻擊者透過 DNS 隧道繞過防火牆/代理伺服器控制進行資料外洩
- 威脅情資顯示駭侵者使用基於 DNS 的 C2 通道(例如 Cobalt Strike DNS Beacon)
- UEBA 偵測到特定主機產生異常大量的 DNS 查詢
- 惡意程式分析發現 DNS-over-HTTPS (DoH) 或 DNS 隧道能力
請勿將此技能用於標準的 DNS 故障排除或可用性監控 — 此技能聚焦於安全相關的 DNS 濫用偵測。
必要條件
- 已啟用 DNS 查詢日誌記錄(Windows DNS Server、Bind、Infoblox 或 Cisco Umbrella)
- DNS 日誌已導入 SIEM(Splunk 中的
Stream:DNS、dnssourcetype,或 Zeek DNS 日誌) - 用於歷史網域解析分析的被動 DNS 資料
- 正常 DNS 行為的基線(查詢量、網域分佈、TXT 記錄頻率)
- 具備
math與collections函式庫的 Python 環境,以進行熵值計算
工作流程
步驟 1:透過子網域長度分析偵測 DNS 隧道
DNS 隧道會將資料編碼在子網域標籤中,導致查詢長度異常:
index=dns sourcetype="stream:dns" query_type IN ("A", "AAAA", "TXT", "CNAME", "MX")
| eval domain_parts = split(query, ".")
| eval subdomain = mvindex(domain_parts, 0, mvcount(domain_parts)-3)
| eval subdomain_str = mvjoin(subdomain, ".")
| eval subdomain_len = len(subdomain_str)
| eval tld = mvindex(domain_parts, -1)
| eval registered_domain = mvindex(domain_parts, -2).".".tld
| where subdomain_len > 50
| stats count AS queries, dc(query) AS unique_queries,
avg(subdomain_len) AS avg_subdomain_len,
max(subdomain_len) AS max_subdomain_len,
values(src_ip) AS sources
by registered_domain
| where queries > 20
| sort - avg_subdomain_len
| table registered_domain, queries, unique_queries, avg_subdomain_len, max_subdomain_len, sources
步驟 2:偵測高熵值網域查詢(DGA 偵測)
網域生成演算法會產生看似隨機的網域:
index=dns sourcetype="stream:dns"
| eval domain_parts = split(query, ".")
| eval sld = mvindex(domain_parts, -2)
| eval sld_len = len(sld)
| eval char_count = sld_len
| eval vowels = len(replace(sld, "[^aeiou]", ""))
| eval consonants = len(replace(sld, "[^bcdfghjklmnpqrstvwxyz]", ""))
| eval digits = len(replace(sld, "[^0-9]", ""))
| eval vowel_ratio = if(char_count > 0, vowels / char_count, 0)
| eval digit_ratio = if(char_count > 0, digits / char_count, 0)
| where sld_len > 12 AND (vowel_ratio < 0.2 OR digit_ratio > 0.3)
| stats count AS queries, dc(query) AS unique_domains, values(src_ip) AS sources
by query
| where unique_domains > 10
| sort - queries
基於 Python 的 DNS 查詢香農熵值計算:
import math
from collections import Counter
def shannon_entropy(text):
"""計算字串的香農熵"""
if not text:
return 0
counter = Counter(text.lower())
length = len(text)
entropy = -sum(
(count / length) * math.log2(count / length)
for count in counter.values()
)
return round(entropy, 4)
# 範例測試
normal_domain = "google" # 低熵值
dga_domain = "x8kj2m9p4qw7n" # 高熵值
tunnel_subdomain = "aGVsbG8gd29ybGQ.evil.com" # Base64 編碼資料
print(f"正常網域: {shannon_entropy(normal_domain)}") # ~2.25
print(f"DGA網域: {shannon_entropy(dga_domain)}") # ~3.70
print(f"隧道網域: {shannon_entropy(tunnel_subdomain)}") # ~3.50
# 閾值:子網域熵值 > 3.5 = 可能為隧道/DGA
Splunk 熵值評分實作:
index=dns sourcetype="stream:dns"
| eval domain_parts = split(query, ".")
| eval check_string = mvindex(domain_parts, 0)
| eval check_len = len(check_string)
| where check_len > 8
| eval chars = split(check_string, "")
| stats count AS total_chars, dc(chars) AS unique_chars by query, src_ip, check_string, check_len
| eval entropy_estimate = log(unique_chars, 2) * (unique_chars / check_len)
| where entropy_estimate > 3.5
| stats count AS high_entropy_queries, dc(query) AS unique_queries by src_ip
| where high_entropy_queries > 50
| sort - high_entropy_queries
步驟 3:偵測異常 DNS 查詢量
識別產生異常 DNS 流量的主機:
index=dns sourcetype="stream:dns" earliest=-24h
| bin _time span=1h
| stats count AS queries, dc(query) AS unique_domains by src_ip, _time
| eventstats avg(queries) AS avg_queries, stdev(queries) AS stdev_queries by src_ip
| eval z_score = (queries - avg_queries) / stdev_queries
| where z_score > 3 OR queries > 5000
| sort - z_score
| table _time, src_ip, queries, unique_domains, avg_queries, z_score
偵測 TXT 記錄濫用(常見的隧道方法):
index=dns sourcetype="stream:dns" query_type="TXT"
| stats count AS txt_queries, dc(query) AS unique_txt_domains,
values(query) AS domains by src_ip
| where txt_queries > 100
| eval suspicion = case(
txt_queries > 1000, "CRITICAL — 可能為 DNS 隧道",
txt_queries > 500, "HIGH — 可能為 DNS 隧道",
txt_queries > 100, "MEDIUM — TXT 量異常"
)
| sort - txt_queries
| table src_ip, txt_queries, unique_txt_domains, suspicion
步驟 4:偵測已知的 DNS 隧道工具
搜尋常見 DNS 隧道工具的特徵:
index=dns sourcetype="stream:dns"
| eval query_lower = lower(query)
| where (
match(query_lower, "\.dnscat\.") OR
match(query_lower, "\.dns2tcp\.") OR
match(query_lower, "\.iodine\.") OR
match(query_lower, "\.dnscapy\.") OR
match(query_lower, "\.cobalt.*\.beacon") OR
query_type="NULL" OR
(query_type="TXT" AND len(query) > 100)
)
| stats count by src_ip, query, query_type
| sort - count
偵測繞過本地 DNS 的 DNS over HTTPS (DoH):
index=proxy OR index=firewall
dest IN ("1.1.1.1", "1.0.0.1", "8.8.8.8", "8.8.4.4",
"9.9.9.9", "149.112.112.112", "208.67.222.222")
dest_port=443
| stats sum(bytes_out) AS total_bytes, count AS connections by src_ip, dest
| where connections > 100 OR total_bytes > 10485760
| eval alert = "可能的 DoH 繞過 — 透過 HTTPS 向公共解析器發送 DNS 查詢"
| sort - total_bytes
步驟 5:將 DNS 發現與端點資料進行關聯
交叉比對可疑 DNS 與處理程序資料:
index=dns src_ip="192.168.1.105" query="*.evil-tunnel.com" earliest=-24h
| stats count AS dns_queries, earliest(_time) AS first_query, latest(_time) AS last_query
by src_ip, query
| join src_ip [
search index=sysmon EventCode=3 DestinationPort=53 Computer="WORKSTATION-042"
| stats count AS connections, values(Image) AS processes by SourceIp
| rename SourceIp AS src_ip
]
| table src_ip, query, dns_queries, first_query, last_query, processes
步驟 6:計算資料外洩量估計
估計編碼在 DNS 查詢中的資料量:
index=dns src_ip="192.168.1.105" query="*.evil-tunnel.com" earliest=-24h
| eval domain_parts = split(query, ".")
| eval encoded_data = mvindex(domain_parts, 0)
| eval encoded_bytes = len(encoded_data)
| eval decoded_bytes = encoded_bytes * 0.75 -- Base64 解碼因子
| stats sum(decoded_bytes) AS total_bytes_estimated, count AS total_queries,
earliest(_time) AS first_seen, latest(_time) AS last_seen
| eval estimated_kb = round(total_bytes_estimated / 1024, 1)
| eval estimated_mb = round(total_bytes_estimated / 1048576, 2)
| eval duration_hours = round((last_seen - first_seen) / 3600, 1)
| eval rate_kbps = round(estimated_kb / (duration_hours * 3600) * 8, 2)
| table total_queries, estimated_mb, duration_hours, rate_kbps, first_seen, last_seen
關鍵概念
| 術語 | 定義 |
|---|---|
| DNS 隧道 | 將資料編碼於 DNS 查詢/回應中以竊取資料或透過 DNS 建立 C2 通道的技術 |
| DGA | 網域生成演算法 — 惡意軟體為 C2 通訊彈性而產生偽隨機網域名稱的技術 |
| 香農熵 | 測量字串隨機性的數學方法 — 網域名稱中熵值高(>3.5) 表示 DGA 或隧道 |
| TXT 記錄濫用 | 將設計用來存放文字資料的 DNS TXT 記錄用作資料隧道的高頻寬通道 |
| DNS over HTTPS (DoH) | 透過 HTTPS(連接埠 443)加密的 DNS 查詢,繞過傳統 DNS 監控 |
| 被動 DNS | 顯示某網域隨時間解析到哪些 IP 的歷史 DNS 解析記錄 |
工具與系統
- Splunk Stream:網路流量捕獲附加元件,提供解析後的 DNS 查詢資料供 SIEM 分析
- Zeek (Bro):網路安全監控器,產生詳細的 DNS 交易日誌供分析
- Cisco Umbrella (OpenDNS):雲端 DNS 安全平台,可封鎖惡意網域並記錄查詢資料
- Infoblox DNS Firewall:DNS 層級安全,提供基於 RPZ 的封鎖與詳細的查詢記錄
- Farsight DNSDB:提供歷史網域解析查詢與基礎架構映射的被動 DNS 資料庫
常見情境
- Cobalt Strike DNS Beacon:偵測針對 C2 網域定期發送的、包含編碼負載的 TXT 查詢
- 資料外洩:大量獨特的子網域查詢,將竊取資料編碼為 Base64/十六進位格式
- DGA 惡意軟體:偵測對演算法生成網域(高熵值、無網頁內容)的 DNS 查詢
- DNS-over-HTTPS 繞過:員工使用 DoH 繞過公司 DNS 過濾與監控
- 緩慢滴漏式外洩:低量 DNS 隧道保持低於閾值警報(需要基線比對)
輸出格式
DNS 外洩分析 — WORKSTATION-042
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
時間範圍: 2024-03-14 至 2024-03-15
來源: 192.168.1.105 (WORKSTATION-042, 財務部)
發現:
[CRITICAL] 偵測到對 evil-tunnel[.]com 的 DNS 隧道
查詢量: 18 小時內 12,847 次查詢
平均子網域長度: 63 個字元 (正常: <20)
平均熵值: 3.82 (閾值: 3.5)
查詢類型: TXT (89%), A (11%)
估計資料量: ~4.7 MB 透過 DNS 外洩
速率: 0.58 kbps (緩慢滴漏模式)
[HIGH] 解析了類似 DGA 的網域
唯一 DGA 網域: 已解析 247 個網域
模式: 15 字元亂數字母數字.xyz TLD
熵值範圍: 3.6 - 4.1
程序歸因:
程序: svchost_update.exe (偽裝 — 非合法 svchost)
PID: 4892
父程序: explorer.exe
雜湊值: SHA256: a1b2c3d4... (VT: 34/72 惡意 — Cobalt Strike Beacon)
應變措施:
[已完成] 已透過 EDR 隔離主機
[已完成] 網域 evil-tunnel[.]com 已加入 DNS 黑洞
[已完成] 已建立事件單 IR-2024-0448