DNS 域传送防御是权威 DNS 服务器最容易被忽略的一条基线:一条 dig AXFR 命令就可能把整套内网域名结构、测试环境主机名、办公网段命名规则全部导出。本文给出 BIND 的完整加固配置,从关闭匿名传送、TSIG 授权主从同步到 RRL 限速,全部命令可直接复制执行。
适用场景
- 自建权威 DNS 服务器:企业内部域名、内网解析、子域委派由 BIND 托管,服务器上有真实的内网主机命名;
- 主从架构的 DNS 集群:主从同步依赖 AXFR/IXFR,一旦
allow-transfer写成any,同步通道就变成了公开的数据导出接口; - 混合云多出口架构:同一域名存在内网解析与公网解析两套视图(view),视图配置不当会导致内部记录随公网查询泄露;
- 需要满足等保与合规要求:DNS 设备的安全基线明确要求限制区域传送与递归查询。
需要区分清楚:域传送泄露属于信息泄露,本身不直接导致入侵,但它把后续攻击的侦察成本降到接近零,因此经常被列为渗透测试的第一批发现项。
前置条件
- 服务器运行 BIND 9.11 以上(推荐 9.16/9.18 稳定分支),配置目录通常为
/etc/bind或/etc/named; - 具备 root 或 bind 用户权限,可修改
named.conf与区域文件; - 主从架构下需明确从服务器 IP 列表,用于生成授权规则;
- 客户端侧安装
dig(bind9-dnsutils / bind-utils)用于验证; - 可选的
rndc管理通道,用于改配置后重载而不重启进程。
原理说明
AXFR 是什么,为什么会泄露
AXFR(Asynchronous Full Zone Transfer)是区域文件的完整传输协议,设计目的是让从服务器全量拉取主服务器的区域数据。协议本身没有身份校验机制,唯一的访问控制就是服务端配置的 allow-transfer 列表。当该值被写成 any,或者被遗漏而回落到默认行为时,任意可达的客户端都能发起一次全量导出。
# 一次典型的信息泄露
$ dig AXFR example.com @ns1.example.com
example.com. 3600 IN SOA ns1.example.com. admin.example.com. ...
example.com. 3600 IN NS ns1.example.com.
www.example.com. 60 IN A 203.0.113.10
vpn.example.com. 60 IN A 10.0.8.11 ← 内网地址直接暴露
gitlab.internal.example.com. 60 IN A 10.0.20.5 ← 资产清单被完整导出
zabbix.example.com. 60 IN A 10.0.20.9
staging.example.com. 60 IN A 10.0.31.7
导出结果的价值在于:攻击者无需任何扫描就能得到主机命名规范、业务系统清单、内外网网段划分,进而定向寻找 GitLab、Jenkins、Zabbix 这类高价值管理系统的暴露面。
三个必须同时处理的默认行为
- allow-transfer:未显式配置时的继承行为在不同版本间存在差异,稳妥做法是显式声明为
none,再逐个授权从服务器; - allow-recursion:权威服务器不应提供递归服务,否则会沦为 DNS 放大攻击的反射器;
- version.bind:CHAOS 类查询返回的 BIND 版本号可直接对上 CVE 列表,属于免费指纹信息。
NSEC 与区域遍历
部署 DNSSEC 时若使用 NSEC 记录,区域中所有存在的域名会被链式串联,攻击者可通过「遍历不存在域名」逐条走出整个区域,效果与 AXFR 类似。使用 NSEC3 并配合随机 salt 可显著提高遍历成本,但对小规模区域仍可穷举,因此不能把 NSEC3 当作 AXFR 加固的替代方案。
操作步骤
步骤一:现状自查
# 1) 从外部主机尝试全量传送(把 ns1 换为你的权威服务器)
dig AXFR example.com @203.0.113.53 +time=3
# 只要返回了任何 "IN A" 记录,即存在泄露
# 2) 探测 BIND 版本号
dig CH TXT version.bind @203.0.113.53 +short
# 3) 测试是否提供开放递归(预期 REFUSED)
dig +recurse openresolver.example.com @203.0.113.53 A +time=3
# 4) 检查本机配置中所有 allow 指令
grep -RnE '^\s*(allow-transfer|allow-query|allow-recursion|recursion|version)\b' \
/etc/bind/ /etc/named.conf 2>/dev/null
第 1 步如果返回了记录,说明处于高危暴露状态;第 3 步如果返回了真实解析结果(而非 REFUSED),说明服务器在被用作放大攻击跳板。
步骤二:加固全局配置
cp /etc/bind/named.conf.options /etc/bind/named.conf.options.bak-$(date +%F)
cat > /etc/bind/named.conf.options <<'EOF'
options {
directory "/var/cache/bind";
// ---- 监听范围:只监听必要网卡 ----
listen-on { 127.0.0.1; 10.0.0.12; };
listen-on-v6 { none; };
// ---- 权威服务器不提供递归,消除放大攻击面 ----
recursion no;
allow-recursion { none; };
// ---- 查询与传送:默认全拒,按需授权 ----
allow-query { any; }; // 对外解析需保留
allow-transfer { none; }; // 关键:默认禁止 AXFR
allow-update { none; }; // 禁止动态更新
allow-notify { 10.0.0.13; }; // 只接受从服务器的通知
// ---- 信息隐藏 ----
version "not disclosed";
hostname "not disclosed";
server-id "not disclosed";
minimal-responses yes; // 减少附加段的信息量
// ---- 限制单次传送与查询速率 ----
transfers-in 10;
transfers-out 5;
transfers-per-ns 2;
max-transfer-time-out 30; // 单位分钟
recursive-clients 0;
// ---- 关闭不需要的特性 ----
dnssec-validation no; // 权威服务器不需要验证上游
empty-zones-enable yes;
fetch-glue no;
querylog no;
// ---- RRL:限制同源同名的重复应答,抑制放大滥用 ----
rate-limit {
responses-per-second 15;
errors-per-second 5;
nxdomains-per-second 5;
window 5;
slip 2;
ipv4-prefix-length 24;
ipv6-prefix-length 56;
};
};
EOF
named-checkconf -z /etc/bind/named.conf.options && echo "CONF OK"
几个关键项的作用需要说明:
- allow-transfer { none; } 是本次加固的核心,把默认的「谁都能拉」改成「默认拒绝」;
- recursion no 让服务器只应答自己权威的区域,其他查询一律拒绝,从根上消除被当成反射器的可能;
- minimal-responses yes 使响应中不再附带额外的 NS、A 记录,减少单次应答可提取的信息;
- rate-limit 即 RRL,对同源同名的请求做令牌桶限制,是抵御 DNS 放大与随机子域洪水最有效的一层。
步骤三:用 TSIG 为合法主从同步开白名单
仅靠 IP 白名单存在被伪造的风险(尤其是同网段内的主机)。标准做法是叠加 TSIG 密钥认证,实现「IP + 密钥」双因子授权。
# 1) 生成一个高强度的 base64 密钥(32 字节即可)
tsig-keygen -a hmac-sha256 xfer-key.example.com
# 输出形如:key "xfer-key.example.com" { algorithm hmac-sha256; secret "cE8x...U3g="; };
# 2) 写入 named.conf.local,把密钥绑定到从服务器
cat >> /etc/bind/named.conf.local <<'EOF'
key "xfer-key.example.com" {
algorithm hmac-sha256;
secret "cE8xS2hVbTZ3cFF5RjRkTDl3UG5Bc0RmR2hKbU5kU3g=";
};
server 10.0.0.13 { keys { xfer-key.example.com; }; };
zone "example.com" {
type master;
file "/etc/bind/db.example.com";
allow-transfer { key "xfer-key.example.com"; }; // 只允许持钥方传送
also-notify { 10.0.0.13; };
notify yes;
};
EOF
# 3) 从服务器侧对称写入同样的 key 段与 server 段,zone 类型改为 slave
named-checkconf && rndc reload && echo "RELOADED"
密钥文件权限必须收紧,避免密钥本身成为泄露点:
chown root:bind /etc/bind/named.conf.local
chmod 640 /etc/bind/named.conf.local
grep -q 'include "/etc/bind/named.conf.local"' /etc/bind/named.conf || \
echo 'include "/etc/bind/named.conf.local";' >> /etc/bind/named.conf
步骤四:分离内外网视图
存在内外网双解析需求时,必须用 view 做严格隔离,避免内部记录出现在公网视图中。
acl "internal" { 10.0.0.0/8; 172.16.0.0/12; 127.0.0.1; };
view "internal-view" {
match-clients { internal; };
recursion yes;
allow-recursion { internal; };
allow-transfer { none; };
zone "example.com" {
type master;
file "/etc/bind/db.example.com.internal";
};
};
view "external-view" {
match-clients { any; };
recursion no; // 公网视图绝不开递归
allow-transfer { none; };
zone "example.com" {
type master;
file "/etc/bind/db.example.com.external"; // 只含公网记录
};
};
关键点:两个视图的区域文件必须是完全独立的两份,不能用同一份文件;公网视图的区域文件中不得出现任何 10. / 172.16. / 192.168. 开头的 A 记录。
步骤五:区域文件与主机层面的补充加固
# 1) 编译区域文件,确认无语法错误与遗留记录
named-checkzone example.com /etc/bind/db.example.com.external
# 2) 剔除区域文件中的内网地址残留
grep -nE '\b(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)' \
/etc/bind/db.example.com.external && echo "!! 发现内网记录,需清理"
# 3) 通过 systemd 收敛进程能力
mkdir -p /etc/systemd/system/named.service.d
cat > /etc/systemd/system/named.service.d/hardening.conf <<'EOF'
[Service]
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/cache/bind /var/log/named
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
MemoryDenyWriteExecute=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID
EOF
systemctl daemon-reload && systemctl restart named && systemctl is-active named
# 4) 内网 DNS 服务器:53 端口只放行内网网段,并对 UDP 查询限速
nft add table inet dnsguard
nft add chain inet dnsguard input '{ type filter hook input priority -5; policy accept; }'
nft add rule inet dnsguard input udp dport 53 ip saddr 10.0.0.0/8 limit rate 30/second accept
nft add rule inet dnsguard input tcp dport 53 ip saddr 10.0.0.0/8 ct state new accept
nft add rule inet dnsguard input udp dport 53 drop
nft add rule inet dnsguard input tcp dport 53 drop
nft list ruleset | grep -A5 dnsguard
公网权威服务器不能收敛源地址(否则外部无法解析),此时网络层加固的落点是:只放行 53 端口,明确丢弃其他所有服务端口,并把 RRL 作为应对反射滥用的主要手段。
配置验证
第一步,从外部确认 AXFR 已被拒绝:
dig AXFR example.com @203.0.113.53 +time=3
# 预期输出:Transfer failed. 或 communications error / REFUSED
# 只要不再逐行打印记录即为通过
第二步,确认 IXFR 增量传送同样被拒绝(这一项容易被遗漏):
dig IXFR=2026090101 example.com @203.0.113.53 +time=3
# 预期:REFUSED 或失败,不能返回区域内容
第三步,确认合法从服务器仍能同步(在主服务器上查看日志):
rndc status | grep -i 'transfers'
tail -50 /var/log/named/query.log /var/log/syslog 2>/dev/null | grep -Ei 'xfr|transfer'
# 预期看到形如:client 10.0.0.13#xxxx: transfer of 'example.com/IN': AXFR started
# 以及 transfer completed,且结果标记为 signed/verified
第四步,用带密钥的方式手工验证 TSIG 通道(应成功):
dig AXFR -y hmac-sha256:xfer-key.example.com:cE8xS2hVbTZ3cFF5RjRkTDl3UG5Bc0RmR2hKbU5kU3g= \
example.com @10.0.0.12 +time=5 | head -20
# 预期:正常返回区域记录,说明白名单未把合法通道一起封掉
第五步,确认版本号与递归均已关闭:
dig CH TXT version.bind @203.0.113.53 +short
# 预期:not disclosed
dig +recurse example.com @203.0.113.53 A +time=3
# 预期:REFUSED(而非返回解析结果)
dig +norecurse www.example.com @203.0.113.53 A
# 预期:正常返回,说明权威解析未受影响
第六步,验证 RRL 限速与公网视图洁净度:
# RRL:短时间发起大量随机子域查询,观察日志
for i in $(seq 1 60); do
dig +short "$(uuidgen | tr A-Z a-z).example.com" @203.0.113.53 >/dev/null
done
grep -i 'rate limit' /var/log/syslog | tail -5
# 预期:出现 rate-limit 丢弃记录,大量查询收到截断应答
# 公网视图不得含内网地址
dig AXFR -y hmac-sha256:xfer-key.example.com:cE8xS2hVbTZ3cFF5RjRkTDl3UG5Bc0RmR2hKbU5kU3g= \
example.com @10.0.0.12 \
| grep -E '\b(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)' \
|| echo "OK: 无内网记录泄露"
常见问题
FAQ 1:把 allow-transfer 设为 none 后,从服务器同步失败了,如何排查?
按以下顺序定位,覆盖绝大多数失败场景:
- 授权粒度写错:
allow-transfer { none; }写在options中会被 zone 段覆盖。若 zone 段写了allow-transfer { key "xfer-key.example.com"; };,则只有持钥方可通过,从服务器必须同时配置相同的 key 与 server 段; - 算法不一致:主从两端的
algorithm必须相同,且 BIND 版本对hmac-sha256的支持需 9.10 以上。使用hmac-md5会在新版本中被拒绝; - secret 复制出错:base64 串区分大小写,从截图或聊天工具复制时容易丢失尾部等号。用
named-checkconf可提前发现语法错误; - 也需放行 notify:主服务器上的
allow-notify未包含从服务器 IP 时,从服务器收不到变更通知但手工rndc retransfer仍可成功,现象具有迷惑性; - SOA 序列号未递增:区域文件修改后未提升 serial,从服务器认为无更新,日志中不会出现传送记录。这是最容易被误判为「传送被拒」的情况。
FAQ 2:为什么已经禁止 AXFR,外部还是能列出子域名?
AXFR 只是域名枚举的其中一条路径,以下渠道同样会泄露子域信息,需要分别收敛:
- NSEC 区域遍历:部署了 DNSSEC 且使用 NSEC 时,可通过不断查询伪造的不存在域名反推真实域名链。改用 NSEC3 并开启
nsec3param(含随机 salt 与迭代次数)可显著提升遍历成本; - 证书透明度日志(CT):任何签发过公网证书的子域名都会出现在 CT 日志中,这是公开数据,无法通过 DNS 配置消除,应通过资产测绘工具主动比对自身认知范围;
- 通配符与错误应答差异:部分实现会对「存在但无记录」与「完全不存在」返回不同的 RCODE 或应答长度,形成侧信道。开启
minimal-responses并统一 NXDOMAIN 行为可缓解; - 内网视图误匹配:view 的
match-clients使用了过宽网段(例如把0.0.0.0/0划进 internal),导致公网查询命中内网视图。用dig从公网主机查询一条仅存在于内网视图的记录即可验证。
FAQ 3:云托管 DNS 没有 allow-transfer 配置项,怎么处理?
云 DNS 平台通常已默认关闭区域传送,但混合架构下风险仍在:一是确认未开启「允许外部查询区域列表」类选项;二是若平台支持自定义 NS,需检查是否仍保留对自建 BIND 的委派,形成旁路;三是定期用本文步骤一的自查命令扫描全部权威服务器 IP——历史遗留的 BIND 实例往往才是泄露的真正来源,建议把实例清理列入变更管理流程。
总结
- 默认全拒:
allow-transfer { none; }必须显式写入,再通过 TSIG 密钥逐个放行从服务器,形成「IP + 密钥」双因子授权; - 关闭递归:权威服务器
recursion no并配合allow-recursion { none; },从根上消除被用作 DNS 放大反射器的可能; - 信息隐身:
version、hostname、server-id全部改为not disclosed,minimal-responses yes减少应答附带信息; - 视图隔离:内外网使用独立区域文件,公网视图中不得出现任何私有网段 A 记录,必要时用自动化 grep 做发布前校验;
- 限速兜底:开启 RRL(
rate-limit)与transfers-out上限,即使授权配置出现疏漏,速率限制也能把泄露量控制在可接受范围; - 正反向验证:既验证外部 AXFR 被拒绝,也验证合法主从同步未被破坏,同时用
dig CH TXT version.bind与递归测试交叉确认加固项全部生效。
整套加固在单台 BIND 上通常 20 分钟内可完成,主从架构需要两端对称配置。由于该项属于纯配置类加固、无业务侵入性,建议纳入 DNS 服务器的标准镜像与配置基线,新实例上线即具备防护能力。