analyzing-dns-logs-for-exfiltration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Analyzing DNS Logs for Exfiltration

分析DNS日志以检测数据泄露

When to Use

适用场景

Use this skill when:
  • SOC teams suspect data exfiltration through DNS tunneling to bypass firewall/proxy controls
  • Threat intelligence indicates adversaries using DNS-based C2 channels (e.g., Cobalt Strike DNS beacon)
  • UEBA detects anomalous DNS query volumes from specific hosts
  • Malware analysis reveals DNS-over-HTTPS (DoH) or DNS tunneling capabilities
Do not use for standard DNS troubleshooting or availability monitoring — this skill focuses on security-relevant DNS abuse detection.
以下场景适用本技能:
  • SOC团队怀疑攻击者通过DNS隧道绕过防火墙/代理控制进行数据泄露
  • 威胁情报显示攻击者使用基于DNS的C2通道(如Cobalt Strike DNS beacon)
  • UEBA检测到特定主机的DNS查询量存在异常
  • 恶意软件分析发现存在DNS-over-HTTPS (DoH)或DNS隧道功能
请勿用于标准DNS故障排查或可用性监控——本技能专注于与安全相关的DNS滥用检测。

Prerequisites

前置条件

  • DNS query logging enabled (Windows DNS Server, Bind, Infoblox, or Cisco Umbrella)
  • DNS logs ingested into SIEM (Splunk with
    Stream:DNS
    ,
    dns
    sourcetype, or Zeek DNS logs)
  • Passive DNS data for historical domain resolution analysis
  • Baseline of normal DNS behavior (query volume, domain distribution, TXT record frequency)
  • Python with
    math
    and
    collections
    libraries for entropy calculation
  • 已启用DNS查询日志(Windows DNS Server、Bind、Infoblox或Cisco Umbrella)
  • DNS日志已导入SIEM(带有
    Stream:DNS
    dns
    源类型的Splunk,或Zeek DNS日志)
  • 拥有用于历史域名解析分析的被动DNS数据
  • 已建立正常DNS行为基准(查询量、域名分布、TXT记录频率)
  • 安装了
    math
    collections
    库的Python环境,用于熵值计算

Workflow

工作流程

Step 1: Detect DNS Tunneling via Subdomain Length Analysis

步骤1:通过子域名长度检测DNS隧道

DNS tunneling encodes data in subdomain labels, creating unusually long queries:
spl
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
DNS隧道会将数据编码在子域名标签中,产生异常长的查询:
spl
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

Step 2: Detect High-Entropy Domain Queries (DGA Detection)

步骤2:检测高熵域名查询(DGA检测)

Domain Generation Algorithms produce random-looking domains:
spl
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-based Shannon Entropy Calculation for DNS queries:
python
import math
from collections import Counter

def shannon_entropy(text):
    """Calculate Shannon entropy of a string"""
    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)
域名生成算法(DGA)会生成看似随机的域名:
spl
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查询香农熵计算:
python
import math
from collections import Counter

def shannon_entropy(text):
    """Calculate Shannon entropy of a string"""
    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)

Test with examples

Test with examples

normal_domain = "google" # Low entropy dga_domain = "x8kj2m9p4qw7n" # High entropy tunnel_subdomain = "aGVsbG8gd29ybGQ.evil.com" # Base64 encoded data
print(f"Normal: {shannon_entropy(normal_domain)}") # ~2.25 print(f"DGA: {shannon_entropy(dga_domain)}") # ~3.70 print(f"Tunnel: {shannon_entropy(tunnel_subdomain)}") # ~3.50
normal_domain = "google" # Low entropy dga_domain = "x8kj2m9p4qw7n" # High entropy tunnel_subdomain = "aGVsbG8gd29ybGQ.evil.com" # Base64 encoded data
print(f"Normal: {shannon_entropy(normal_domain)}") # ~2.25 print(f"DGA: {shannon_entropy(dga_domain)}") # ~3.70 print(f"Tunnel: {shannon_entropy(tunnel_subdomain)}") # ~3.50

Threshold: entropy > 3.5 for subdomain = likely tunneling/DGA

Threshold: entropy > 3.5 for subdomain = likely tunneling/DGA


**Splunk implementation of entropy scoring:**

```spl
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

**Splunk中的熵值评分实现:**

```spl
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

Step 3: Detect Anomalous DNS Query Volume

步骤3:检测DNS查询量异常

Identify hosts generating abnormal DNS traffic:
spl
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
Detect TXT record abuse (common tunneling method):
spl
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 — Likely DNS tunneling",
    txt_queries > 500, "HIGH — Possible DNS tunneling",
    txt_queries > 100, "MEDIUM — Unusual TXT volume"
  )
| sort - txt_queries
| table src_ip, txt_queries, unique_txt_domains, suspicion
识别生成异常DNS流量的主机:
spl
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记录滥用(常见的隧道技术):
spl
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 — Likely DNS tunneling",
    txt_queries > 500, "HIGH — Possible DNS tunneling",
    txt_queries > 100, "MEDIUM — Unusual TXT volume"
  )
| sort - txt_queries
| table src_ip, txt_queries, unique_txt_domains, suspicion

Step 4: Detect Known DNS Tunneling Tools

步骤4:检测已知DNS隧道工具

Search for signatures of common DNS tunneling tools:
spl
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
Detect DNS over HTTPS (DoH) bypassing local DNS:
spl
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 = "Possible DoH bypass — DNS queries sent over HTTPS to public resolver"
| sort - total_bytes
搜索常见DNS隧道工具的特征:
spl
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):
spl
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 = "Possible DoH bypass — DNS queries sent over HTTPS to public resolver"
| sort - total_bytes

Step 5: Correlate DNS Findings with Endpoint Data

步骤5:关联DNS检测结果与终端数据

Cross-reference suspicious DNS with process data:
spl
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
将可疑DNS数据与进程数据交叉关联:
spl
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

Step 6: Calculate Data Exfiltration Volume Estimate

步骤6:估算数据泄露量

Estimate data volume encoded in DNS queries:
spl
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 decoding factor
| 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查询中编码的数据量:
spl
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 decoding factor
| 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

Key Concepts

核心概念

TermDefinition
DNS TunnelingTechnique encoding data within DNS queries/responses to exfiltrate data or establish C2 channels through DNS
DGADomain Generation Algorithm — malware technique generating pseudo-random domain names for C2 resilience
Shannon EntropyMathematical measure of randomness in a string — high entropy (>3.5) in domain names indicates DGA or tunneling
TXT Record AbuseUsing DNS TXT records (designed for text data) as a high-bandwidth channel for data tunneling
DNS over HTTPS (DoH)DNS queries encrypted over HTTPS (port 443), bypassing traditional DNS monitoring
Passive DNSHistorical record of DNS resolutions showing which IPs a domain resolved to over time
术语定义
DNS Tunneling一种将数据编码在DNS查询/响应中,通过DNS实现数据泄露或建立C2通道的技术
DGA域名生成算法——恶意软件用于生成伪随机域名以保持C2通信弹性的技术
Shannon Entropy衡量字符串随机性的数学指标——域名中高熵值(>3.5)表明可能是DGA生成或隧道通信
TXT Record Abuse将DNS TXT记录(设计用于文本数据)用作高带宽的数据隧道通道
DNS over HTTPS (DoH)通过HTTPS(443端口)加密的DNS查询,绕过传统DNS监控
Passive DNS历史DNS解析记录,显示域名在不同时间解析到的IP地址

Tools & Systems

工具与系统

  • Splunk Stream: Network traffic capture add-on providing parsed DNS query data for SIEM analysis
  • Zeek (Bro): Network security monitor generating detailed DNS transaction logs for analysis
  • Cisco Umbrella (OpenDNS): Cloud DNS security platform blocking malicious domains and logging query data
  • Infoblox DNS Firewall: DNS-layer security providing RPZ-based blocking and detailed query logging
  • Farsight DNSDB: Passive DNS database for historical domain resolution lookups and infrastructure mapping
  • Splunk Stream:网络流量捕获插件,为SIEM分析提供解析后的DNS查询数据
  • Zeek (Bro):网络安全监控工具,生成详细的DNS事务日志用于分析
  • Cisco Umbrella (OpenDNS):云DNS安全平台,阻止恶意域名并记录查询数据
  • Infoblox DNS Firewall:DNS层安全工具,提供基于RPZ的拦截和详细查询日志
  • Farsight DNSDB:被动DNS数据库,用于历史域名解析查询和基础设施映射

Common Scenarios

常见场景

  • Cobalt Strike DNS Beacon: Detect periodic TXT queries with encoded payloads to C2 domain
  • Data Exfiltration: Large volumes of unique subdomain queries encoding stolen data in Base64/hex
  • DGA Malware: Detect DNS queries to algorithmically generated domains (high entropy, no web content)
  • DNS-over-HTTPS Bypass: Employee using DoH to bypass corporate DNS filtering and monitoring
  • Slow Drip Exfiltration: Low-volume DNS tunneling staying below threshold alerts (requires baseline comparison)
  • Cobalt Strike DNS Beacon:检测定期向C2域名发送带有编码载荷的TXT查询
  • 数据泄露:大量唯一子域名查询,以Base64/十六进制编码窃取的数据
  • DGA恶意软件:检测解析算法生成域名的DNS查询(高熵值,无网页内容)
  • DNS-over-HTTPS绕过:员工使用DoH绕过企业DNS过滤和监控
  • 慢速数据泄露:低流量DNS隧道,保持在阈值警报以下(需要基准对比)

Output Format

输出格式

DNS EXFILTRATION ANALYSIS — WORKSTATION-042
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Period:       2024-03-14 to 2024-03-15
Source:       192.168.1.105 (WORKSTATION-042, Finance Dept)

Findings:
  [CRITICAL] DNS tunneling detected to evil-tunnel[.]com
    Query Volume:       12,847 queries in 18 hours
    Avg Subdomain Len:  63 characters (normal: <20)
    Avg Entropy:        3.82 (threshold: 3.5)
    Query Types:        TXT (89%), A (11%)
    Estimated Data:     ~4.7 MB exfiltrated via DNS
    Rate:               0.58 kbps (slow drip pattern)

  [HIGH] DGA-like domains resolved
    Unique DGA Domains: 247 domains resolved
    Pattern:            15-char random alphanumeric.xyz TLD
    Entropy Range:      3.6 - 4.1

Process Attribution:
  Process:   svchost_update.exe (masquerading — not legitimate svchost)
  PID:       4892
  Parent:    explorer.exe
  Hash:      SHA256: a1b2c3d4... (VT: 34/72 malicious — Cobalt Strike beacon)

Containment:
  [DONE] Host isolated via EDR
  [DONE] Domain evil-tunnel[.]com added to DNS sinkhole
  [DONE] Incident IR-2024-0448 created
DNS EXFILTRATION ANALYSIS — 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顶级域名
    熵值范围:      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 sinkhole
  [已完成] 创建事件IR-2024-0448