Skip to content

DNS 拒绝服务与缓存投毒防御 ​

防御目标与对抗矩阵 ​

针对 DNS 基础设施自身的 DoS(对应攻击技术见 07-dns-dos.md)同时威胁权威服务器与递归解析器。防御必须覆盖边缘高熵随机子域清洗、否定缓存主动合成、递归并发配额隔离及加密校验加固。

攻击向量受害节点核心防御机制关键配置 / 标准
Water Torture(水刑随机子域)权威 DNS + 递归解析器边缘 dnsdist 动态限速高熵/NXDOMAIN 子域 + 限制每域并发拉取fetches-per-zone + dnsdist 动态封禁 + Anycast
NXDOMAIN Flood权威 DNS启用 RFC 8198(基于 NSEC/NSEC3 在递归层合成否定缓存)+ 合理负缓存 SOA TTLsynth-from-dnssec yes + SOA minimum >= 300s
Phantom Domain 攻击递归解析器 outgoing 池对无响应权威域名实施退避惩罚(Infra Cache RTT 降级)、严格限制每域/每服并发与超时BIND fetches-per-server、Unbound infra-cache-min-rtt
DNS Cache Poisoning (CVE-2008-1447)递归缓存与全站可达性启用源端口随机化 + 0x20 大小写随机化 + DNSSEC 强验证 + DoT/DoH 上游Unbound use-caps-for-id: yes、dnssec-validation auto
DNSSEC 自身带宽放大权威 DNS 出口带宽关闭/最小化 ANY 响应(RFC 8482)、采用 ECDSA P-256 签名缩减 RRSIG 体积minimal-any yes + ECDSA 密钥(比 RSA-2048 小约 4x)

防御工作流 ​

阶段一:权威 DNS 加固(Anycast + RRL + ECDSA + 最小化 ANY) ​

  1. 采用 Anycast 分布与多权威供应商隔离:避免单点权威 IP 被直接打穿,确保关键域名具备独立的次级权威托管。
  2. 收缩 DNSSEC 响应体积:将 DNSSEC 签名算法从 RSA-2048 迁移至 ECDSA P-256(Algorithm 13),单条 RRSIG 从 ~256 字节降至 ~64 字节,显著降低自身带宽消耗,同时按 RFC 8482 对 ANY 查询仅返回 HINFO 占位记录。
bind
// /etc/bind/named.conf.options — 权威与递归防 DoS 关键指令
options {
    // 1. 抑制 DNSSEC 与 ANY 带宽放大
    minimal-any yes;
    minimal-responses yes;
    max-udp-size 1232;

    // 2. 启用 RFC 8198 否定响应合成,大幅削减 NXDOMAIN 穿透至权威
    synth-from-dnssec yes;

    // 3. 限制对单一域或单一失联权威(Phantom Domain)的并发探测
    fetches-per-zone 40 drop;
    fetches-per-server 100 drop;
    max-recursion-depth 7;
    max-recursion-queries 64;

    // 4. RRL 对高频重复/NXDOMAIN 响应限流
    rate-limit {
        responses-per-second 5;
        nxdomains-per-second 5;
        errors-per-second 5;
        window 5;
        slip 2;
    };
};

阶段二:递归解析器抗 Phantom Domain 与防缓存投毒(Unbound) ​

Unbound 通过基础设施缓存(Infra Cache)自动追踪上游权威服务器的丢包率与延迟;配合 0x20 位随机化与 serve-expired 策略,可在上游权威遭攻击或存在幽灵域名时维持本地可用性:

yaml
# /etc/unbound/unbound.conf.d/dos-hardening.conf
server:
  # 1. 防缓存投毒:随机化源端口、TXID 及查询名大小写 0x20 bit
  outgoing-port-avoid: 0-1024
  use-caps-for-id: yes
  harden-glue: yes
  harden-dnssec-stripped: yes
  harden-below-nxdomain: yes
  aggressive-nsec: yes        # RFC 8198 本地合成 NXDOMAIN,拦截 Water Torture

  # 2. 抗 Phantom Domain:限制单查询最大重试与上游退避
  infra-host-ttl: 900
  infra-cache-min-rtt: 50
  num-queries-per-thread: 4096
  jostle-timeout: 200         # 队列满时优先淘汰超时慢查询(幽灵域),保护正常查询
  ratelimit: 1000
  ratelimit-below-domain: 100 # 按父域限速,直接遏制随机子域穿透

  # 3. 优雅降级:权威短时不可达时返回过期缓存同时异步预取
  prefetch: yes
  serve-expired: yes
  serve-expired-ttl: 86400

阶段三:边缘 dnsdist 动态拦截 Water Torture ​

在权威或递归前置部署 dnsdist,基于父域名维度的 NXDOMAIN 与随机子域 QPS 统计自动触发丢弃或强制转 TCP(TC=1):

lua
-- /etc/dnsdist/dnsdist.conf
local dbr = dynBlockRulesGroup()
-- 单源 IP 10 秒内 NXDOMAIN 超过 50/s 直接封禁 60 秒
dbr:setRCodeRate(DNSRCode.NXDOMAIN, 50, 10, "Excessive NXDOMAIN (Water Torture)", 60)
-- 单源 IP 总 QPS 超过 200/s 截断返回 TC=1 迫使切 TCP
dbr:setQueryRate(200, 10, "High QPS TC fallback", 60, DNSAction.Truncate)
function maintenance()
  dbr:apply()
end

弹性校验代码(可运行 Python 自检) ​

以下 Python 脚本实现双层校验:(1)基于子域标签香农熵(Shannon Entropy)与窗口内 NXDOMAIN 比例的水刑攻击/随机子域实时识别器;(2)BIND/Unbound 防御指令基线合规检查器。

python
#!/usr/bin/env python3
"""DNS DoS defense verifier: Water Torture entropy/NXDOMAIN detector & resolver config check."""

import math
from collections import Counter


def shannon_entropy(label: str) -> float:
    """Compute Shannon entropy of a DNS leftmost subdomain label."""
    if not label:
        return 0.0
    counts = Counter(label)
    total = len(label)
    return -sum((c / total) * math.log2(c / total) for c in counts.values())


def detect_water_torture_batch(queries: list[tuple[str, str]], entropy_threshold: float = 3.2,
                               nxdomain_ratio_threshold: float = 0.75) -> tuple[bool, dict]:
    """Detect random-subdomain Water Torture floods from (qname, rcode) telemetry."""
    if not queries:
        return False, {"count": 0}
    nx_count = sum(1 for _, rcode in queries if rcode == "NXDOMAIN")
    unique_qnames = len({q for q, _ in queries})
    entropies = [shannon_entropy(q.split(".", 1)[0]) for q, _ in queries]
    avg_entropy = sum(entropies) / len(entropies)
    nx_ratio = nx_count / len(queries)
    unique_ratio = unique_qnames / len(queries)

    flagged = (
        nx_ratio >= nxdomain_ratio_threshold
        and avg_entropy >= entropy_threshold
        and unique_ratio >= 0.8
    )
    return flagged, {
        "nx_ratio": round(nx_ratio, 3),
        "avg_entropy": round(avg_entropy, 3),
        "unique_ratio": round(unique_ratio, 3),
    }


def verify_unbound_hardening(conf_text: str) -> list[str]:
    """Verify required Unbound directives against Water Torture, Phantom Domain, and poisoning."""
    required = [
        "use-caps-for-id: yes",
        "aggressive-nsec: yes",
        "harden-dnssec-stripped: yes",
        "jostle-timeout:",
        "ratelimit-below-domain:",
        "serve-expired: yes",
    ]
    return [item for item in required if item not in conf_text]


def run_self_checks() -> None:
    # 1. 模拟正常查询样本(重复域名 + 低 NXDOMAIN)
    normal_batch = [("www.example.com", "NOERROR"), ("api.example.com", "NOERROR")] * 10
    flagged, _ = detect_water_torture_batch(normal_batch)
    assert not flagged, "Normal traffic triggered false positive"

    # 2. 模拟 Water Torture 高熵唯一随机子域 + NXDOMAIN 洪泛
    attack_batch = [
        (f"x9k2m{i:04x}q7z.example.com", "NXDOMAIN") for i in range(40)
    ]
    flagged, metrics = detect_water_torture_batch(attack_batch)
    assert flagged, f"Water Torture batch missed: {metrics}"

    # 3. 校验 Unbound 配置完整性
    good_conf = "\n".join([
        "use-caps-for-id: yes",
        "aggressive-nsec: yes",
        "harden-dnssec-stripped: yes",
        "jostle-timeout: 200",
        "ratelimit-below-domain: 100",
        "serve-expired: yes",
    ])
    assert verify_unbound_hardening(good_conf) == []
    assert "aggressive-nsec: yes" in verify_unbound_hardening("use-caps-for-id: yes")
    print("[PASS] All DNS DoS & cache poisoning defense checks passed.")


if __name__ == "__main__":
    run_self_checks()

验证与判定标准 ​

验证项达标判定标准可观测指标
Water Torture 抑制边缘 dnsdist 与 aggressive-nsec 阻断随机子域穿透至权威节点权威 NXDOMAIN 穿透 QPS 下降 > 90%
Phantom Domain 隔离静默丢弃的上游域名被 jostle-timeout 快速淘汰,不阻塞其他域名解析递归器正常域 P99 延迟维持 < 100ms
防缓存投毒开启抓包验证源端口随机、0x20 大小写混合及 DNSSEC 校验生效无效签名响应返回 SERVFAIL 不入缓存
DNSSEC 带宽优化已切 ECDSA P-256 且 ANY 查询返回精简响应单包响应大小 < 1232 字节,无放大瓶颈

MCP 工具映射 ​

防御阶段MCP 工具说明
DoH / 边缘 DNS 服务探测http_probe验证 DoH 网关与状态页面在负载下的可用性
蓝队技术路由kb_router以 track="blue" 搜索 dns / water_torture / cache_poisoning
攻防文档对照kb_read_file读取本文件及配对攻击文档 ctf-website/22-dos/07-dns-dos
分类清单查看kb_catalog列举 22-dos 下所有蓝队防御指南

Apache-2.0 · 仅供授权环境下的学习与防御性研究使用 · 免责声明