基于ipset与威胁情报的DNS主动防护实战

基于ipset与威胁情报的DNS主动防护实战 1. 项目概述为什么我们需要主动的DNS防护最近在排查一台线上服务器的异常流量时发现了一个老生常谈但又容易被忽视的问题大量的DNS异常解析请求。这些请求并非来自正常的业务应用而是试图向一些已知的恶意域名、矿池地址或者根本不存在的域名发起查询。在传统的安全观念里我们可能更关注防火墙的端口规则、Web应用的漏洞但DNS作为互联网的“电话簿”其安全性直接关系到整个内部网络的健康。一次成功的DNS投毒或劫持足以让所有安全防线形同虚设。这个项目“DNS防护实战用ipset自动拦截异常解析与群联AI云防护集成”核心就是构建一套主动、动态的本地DNS防护层。它不依赖于昂贵的商业硬件防火墙而是利用Linux系统自带的强大工具——ipset和iptables结合智能的威胁情报这里以“群联AI云防护”为例泛指各类威胁情报源实现自动化的恶意域名IP封禁。简单来说其工作流是监控本地DNS查询 - 比对威胁情报黑名单 - 将解析出的恶意IP地址动态加入ipset集合 - 通过iptables丢弃所有通往该集合内IP的流量。这套方案特别适合拥有自建DNS服务器、对网络自主可控性要求高的环境例如企业内网、数据中心、甚至高级家庭网络如使用OpenWRT的路由器。2. 核心工具与原理深度解析在动手之前我们必须吃透几个核心工具的原理这样才能在出现问题时心中有数调整起来也得心应手。2.1 DNS与异常解析的威胁DNS协议本身是明文的尽管DoH/DoT正在普及且缺乏强认证机制这使其天生脆弱。常见的威胁包括DNS劫持攻击者篡改DNS响应将正常域名指向恶意IP。DNS投毒缓存污染污染DNS服务器的缓存影响所有向其查询的用户。域名生成算法DGA恶意软件动态生成大量域名与其CC服务器通信试图绕过静态黑名单。隐蔽信道通过将信息编码在域名查询中DNS隧道来外泄数据。“异常解析”通常指解析频率异常高、解析从未见过的随机域名、解析已知的恶意或矿池域名。我们的防护思路是“后发制人”即使恶意查询发出了我们也绝不允许服务器与解析出来的恶意IP建立任何连接。2.2 ipset高效的海量IP管理引擎iptables本身可以基于IP地址做规则匹配但当需要封禁成千上万个IP时每条IP对应一条iptables规则会导致性能急剧下降因为iptables是线性匹配的。ipset就是为了解决这个问题而生的。你可以把它理解为一个超级高效的IP地址集合。它的核心价值在于存储与匹配分离ipset负责存储IP列表支持IP段、端口、MAC地址等多种元素iptables只需用一条规则匹配整个集合。数据结构优化内部使用哈希表存储查找效率是O(1)常数级与集合大小无关。动态更新可以随时向集合内添加或删除IP而无需刷新整个iptables规则链。例如封禁一个IP段在iptables里可能需要256条规则而用ipset只需要一条规则引用一个包含了该IP段的集合。2.3 iptables与ipset的联动iptables是Linux内核的包过滤框架。通过与ipset联动我们可以实现高性能的批量拦截。典型的联动规则如下# 创建一个名为“blocklist”的ipset集合存储IPv4地址 ipset create blocklist hash:ip family inet hashsize 1024 maxelem 65536 # 在iptables的INPUT和FORWARD链根据你的网络拓扑选择末尾加入丢弃规则 iptables -I INPUT -m set --match-set blocklist src -j DROP iptables -I FORWARD -m set --match-set blocklist src -j DROP iptables -I OUTPUT -m set --match-set blocklist dst -j DROP关键参数解释hash:ip指定集合类型为存储IP地址的哈希表。family inet表示IPv4。如果是IPv6则用inet6。hashsize 1024初始哈希表大小。应根据预估的IP数量调整减少哈希冲突。maxelem 65536集合最大元素数量。-m set --match-set blocklist dstiptables的扩展匹配模块检查数据包的目标地址dst是否在blocklist集合中。src则是匹配源地址。注意规则顺序至关重要。-I是插入到链的头部-A是追加到尾部。通常拦截规则应放在链的靠前位置但要在允许建立连接的相关规则如-m state --state ESTABLISHED,RELATED -j ACCEPT之后以免影响正常回包。2.4 威胁情报源“群联AI云防护”与替代方案标题中的“群联AI云防护”是一个示例性的威胁情报源概念。在实践中我们需要一个能持续提供恶意域名或IP列表的渠道。你可以选择商业威胁情报平台如 VirusTotal Intelligence, AlienVault OTX, 微步在线等它们通常提供API接口。开源威胁情报 feeds这是更常见和经济的方案。例如恶意域名列表如urlhaus.abuse.ch的域名列表、malsilo.gitlab.io的DGA域名列表。恶意IP列表如blocklist.de的各类攻击IP列表、www.spamhaus.org的Drop或EDROP列表。加密货币矿池域名/IP列表可以从一些安全博客或GitHub项目如notracking/hosts-blocklists获取。自建分析系统如果有能力可以分析自己的DNS日志利用机器学习AI模型识别异常模式生成自定义黑名单。这就是“AI云防护”的本地化体现。本实战将主要以开源威胁情报Feed为例演示如何自动化获取并处理这些数据。3. 系统架构与自动化部署实战整个系统的架构可以分为四个模块数据采集模块、数据处理模块、规则更新模块和日志审计模块。下面我们一步步实现。3.1 环境准备与依赖安装假设我们在一台运行systemd的Linux服务器如CentOS 8或Ubuntu 20.04上部署这台服务器通常也是内网的DNS转发器或防火墙。# 1. 安装核心工具 # 对于CentOS/RHEL/AlmaLinux sudo dnf install ipset iptables-services curl wget bind-utils -y sudo systemctl enable --now iptables # 对于Ubuntu/Debian sudo apt update sudo apt install ipset iptables curl wget dnsutils -y # Ubuntu默认使用ufw我们需要直接使用iptables确保ufw未启用或已正确配置。 # 2. 创建工作目录和脚本存放处 sudo mkdir -p /opt/dns-guardian/{scripts,logs,lists,backups} sudo chmod -R 755 /opt/dns-guardian3.2 核心脚本编写自动获取与更新黑名单我们将编写一个Bash脚本负责从多个威胁情报源拉取黑名单去重合并然后更新到ipset集合中。创建脚本/opt/dns-guardian/scripts/update_blocklist.sh#!/bin/bash # update_blocklist.sh - 自动更新DNS防护IP黑名单 # 作者你的名字 # 日志记录 LOG_FILE/opt/dns-guardian/logs/update_$(date %Y%m%d).log exec $LOG_FILE 21 echo echo 开始执行黑名单更新 $(date) echo # 1. 定义威胁情报源列表 (URL数组) THREAT_FEEDS( https://blocklist.de/ips.txt # 综合攻击IP https://www.spamhaus.org/drop/drop.txt # Spamhaus DROP列表 https://www.spamhaus.org/drop/edrop.txt # Spamhaus EDROP列表 https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/coinbl_ips.list # 加密货币矿池IP # 可以添加更多源例如来自 abuse.ch 或自建列表 ) # 2. 临时文件 TMP_DIR/tmp/dns-guardian-$$ RAW_LIST${TMP_DIR}/raw_ips.txt MERGED_LIST${TMP_DIR}/merged_ips.txt FINAL_LIST/opt/dns-guardian/lists/blocklist.txt # 创建临时目录 mkdir -p $TMP_DIR # 3. 下载并合并列表 echo [步骤1] 正在从 ${#THREAT_FEEDS[]} 个威胁情报源下载数据... for feed_url in ${THREAT_FEEDS[]}; do echo 处理源: $feed_url # 使用curl设置超时和重试忽略证书错误仅用于公开列表 curl -s --max-time 30 --retry 2 --retry-delay 3 --insecure $feed_url 2/dev/null | \ grep -Eo ([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})? $RAW_LIST || echo 警告: 下载 $feed_url 失败 done # 4. 数据清洗与去重 echo [步骤2] 清洗与去重IP地址... # 去除空行、注释行只保留有效的IP/CIDR格式然后排序去重 grep -v ^# $RAW_LIST | grep -v ^$ | sort -u -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n $MERGED_LIST IP_COUNT$(wc -l $MERGED_LIST) echo 获取到 $IP_COUNT 个不重复的IP/CIDR地址。 # 5. 与旧列表对比计算增量可选减少ipset操作次数 if [[ -f $FINAL_LIST ]]; then echo [步骤3] 计算增量更新... # 找出新增的IP ADDED_IPS${TMP_DIR}/added_ips.txt comm -13 (sort $FINAL_LIST) (sort $MERGED_LIST) $ADDED_IPS # 找出已删除的IP在当前列表中没有的 REMOVED_IPS${TMP_DIR}/removed_ips.txt comm -23 (sort $FINAL_LIST) (sort $MERGED_LIST) $REMOVED_IPS ADD_COUNT$(wc -l $ADDED_IPS) DEL_COUNT$(wc -l $REMOVED_IPS) echo 新增IP: $ADD_COUNT 个 移除IP: $DEL_COUNT 个。 # 6. 更新ipset集合 (增量更新模式) IPSET_NAMEdns_blocklist # 检查集合是否存在不存在则创建 if ! ipset list -n | grep -q ^${IPSET_NAME}$; then echo 集合 $IPSET_NAME 不存在正在创建... ipset create $IPSET_NAME hash:net family inet hashsize 2048 maxelem 100000 fi # 添加新增的IP if [[ $ADD_COUNT -gt 0 ]]; then echo 正在添加 $ADD_COUNT 个IP到ipset集合... while read -r cidr; do # 使用 -exist 选项避免重复添加时报错 ipset add $IPSET_NAME $cidr -exist 2/dev/null done $ADDED_IPS fi # 删除已移除的IP if [[ $DEL_COUNT -gt 0 ]]; then echo 正在从ipset集合中删除 $DEL_COUNT 个IP... while read -r cidr; do ipset del $IPSET_NAME $cidr 2/dev/null || true # 忽略不存在的条目 done $REMOVED_IPS fi else echo [步骤3] 未找到旧列表执行全量更新... # 首次运行或列表丢失执行全量替换 IPSET_NAMEdns_blocklist # 如果集合已存在销毁并重建更干净 if ipset list -n | grep -q ^${IPSET_NAME}$; then echo 销毁旧集合 $IPSET_NAME ... ipset destroy $IPSET_NAME fi echo 创建新集合 $IPSET_NAME ... ipset create $IPSET_NAME hash:net family inet hashsize 2048 maxelem 100000 echo 批量添加IP到集合中... # 使用 ipset restore 批量添加效率远高于循环 ipset save ${TMP_DIR}/ipset_backup.txt 2/dev/null || true # 备份当前所有集合 echo create ${IPSET_NAME} hash:net family inet hashsize 2048 maxelem 100000 ${TMP_DIR}/ipset_restore.txt awk {print add ${IPSET_NAME} $0} $MERGED_LIST ${TMP_DIR}/ipset_restore.txt ipset restore -! ${TMP_DIR}/ipset_restore.txt echo 全量更新完成。 fi # 7. 保存最终列表和ipset集合状态 cp $MERGED_LIST $FINAL_LIST ipset save /opt/dns-guardian/backups/ipset_state_$(date %Y%m%d_%H%M%S).txt # 8. 清理临时文件 rm -rf $TMP_DIR echo [步骤4] 更新完成。当前集合 $IPSET_NAME 信息 ipset list $IPSET_NAME | head -20 # 只显示前20行摘要 echo 完整列表已保存至: $FINAL_LIST echo echo 更新脚本执行完毕 $(date) echo 给脚本添加执行权限sudo chmod x /opt/dns-guardian/scripts/update_blocklist.sh3.3 配置iptables规则链有了ipset集合我们需要配置iptables来使用它。建议创建一个独立的规则链便于管理。# 创建一个名为“DNS-GUARDIAN”的自定义链 sudo iptables -N DNS-GUARDIAN # 在该链中添加规则匹配目标IP在黑名单中则丢弃包 sudo iptables -A DNS-GUARDIAN -m set --match-set dns_blocklist dst -j DROP # 也可以记录被拦截的日志谨慎开启日志量可能很大 # sudo iptables -A DNS-GUARDIAN -m set --match-set dns_blocklist dst -j LOG --log-prefix [DNS-BLOCK] # 将自定义链挂接到INPUT和FORWARD链的合适位置 # 假设我们想保护本机INPUT和通过本机转发的流量FORWARD # 规则位置很重要通常放在允许已建立连接规则之后其他应用规则之前。 # 查看现有INPUT链规则编号 sudo iptables -L INPUT --line-numbers # 假设我们想插在第5条规则之后根据实际情况调整 sudo iptables -I INPUT 6 -j DNS-GUARDIAN sudo iptables -I FORWARD 1 -j DNS-GUARDIAN # 对于本机主动向外发出的恶意请求OUTPUT也需要拦截 sudo iptables -I OUTPUT 1 -m set --match-set dns_blocklist dst -j DROP # 或者将OUTPUT也引入DNS-GUARDIAN链保持规则统一 # sudo iptables -I OUTPUT 1 -j DNS-GUARDIAN为了让规则持久化需要保存iptables和ipset规则。不同发行版方法不同CentOS/RHEL/AlmaLinux:sudo iptables-save /etc/sysconfig/iptables sudo ipset save /etc/sysconfig/ipsetUbuntu/Debian (使用 iptables-persistent):sudo apt install iptables-persistent -y # 安装过程中会询问是否保存当前规则选是。 # 或者手动保存 sudo netfilter-persistent save # ipset持久化需要额外步骤可以写入启动脚本或使用ipset-persistent包。一个简单的ipset持久化方法是在/etc/rc.local如果可用或创建一个systemd服务在启动时加载备份文件。3.4 设置定时任务与日志轮转我们希望黑名单能定期自动更新比如每小时一次。# 编辑crontab sudo crontab -e添加以下行# 每小时的第5分钟执行一次更新脚本并将输出追加到专用日志 5 * * * * /opt/dns-guardian/scripts/update_blocklist.sh /opt/dns-guardian/logs/cron.log 21 # 每天凌晨3点清理30天前的日志 0 3 * * * find /opt/dns-guardian/logs/ -name update_*.log -mtime 30 -delete配置logrotate来管理脚本生成的日志防止磁盘被撑满。创建/etc/logrotate.d/dns-guardian/opt/dns-guardian/logs/*.log { daily missingok rotate 30 compress delaycompress notifempty create 644 root root sharedscripts postrotate # 如果需要可以在这里发送信号或重启服务本例中不需要 endscript }4. 高级集成对接“AI云防护”与DNS查询监控基础的黑名单更新是“静态”的。要实现标题中的“自动拦截异常解析”我们需要更主动的监控——分析实时的DNS查询日志发现异常后动态更新黑名单。4.1 监控DNS查询日志以BIND为例如果使用BIND作为DNS服务器其查询日志默认在/var/log/named/queries.log路径可能因配置而异。我们需要解析这个日志提取出查询的域名。首先确保BIND开启了查询日志。在/etc/named.conf的options或指定视图view中添加logging { channel query_log { file /var/log/named/queries.log versions 5 size 50m; severity dynamic; print-time yes; print-category yes; }; category queries { query_log; }; };重启BIND服务。然后编写一个监控脚本/opt/dns-guardian/scripts/monitor_dns_log.sh使用tail -F实时跟踪日志并使用dig或nslookup解析域名得到IP再判断是否加入黑名单。这里提供一个简化版的思路#!/bin/bash # monitor_dns_log.sh - 监控DNS日志并动态拦截 LOG_FILE/var/log/named/queries.log KNOWN_GOOD_DOMAINS_FILE/opt/dns-guardian/lists/known_good_domains.txt # 白名单域名列表 SUSPICIOUS_DOMAINS_FILE/opt/dns-guardian/lists/suspicious_patterns.txt # 可疑域名模式正则 # 加载白名单和可疑模式 mapfile -t GOOD_DOMAINS $KNOWN_GOOD_DOMAINS_FILE 2/dev/null || true mapfile -t SUSPICIOUS_PATTERNS $SUSPICIOUS_DOMAINS_FILE 2/dev/null || true # 简单的异常检测函数 check_domain() { local domain$1 local is_suspicious0 # 检查是否在白名单中 for good in ${GOOD_DOMAINS[]}; do if [[ $domain *$good* ]]; then return 1 # 白名单忽略 fi done # 检查是否匹配可疑模式例如DGA特征长随机字符串 for pattern in ${SUSPICIOUS_PATTERNS[]}; do if [[ $domain ~ $pattern ]]; then is_suspicious1 break fi done # 这里可以集成更复杂的逻辑例如 # 1. 调用外部API如VirusTotal检查域名信誉。 # 2. 检查域名是否在本地缓存的黑名单域名列表中。 # 3. 统计查询频率异常高频查询可能是DGA或扫描。 if [[ $is_suspicious -eq 1 ]]; then echo $(date): 检测到可疑域名查询: $domain /opt/dns-guardian/logs/monitor.log # 解析域名获取IP ips$(dig short A $domain 2/dev/null | grep -E ^[0-9]\.[0-9]\.[0-9]\.[0-9]$) for ip in $ips; do # 将IP加入ipset黑名单 ipset add dns_blocklist $ip -exist 2/dev/null echo 已拦截IP: $ip (来自域名: $domain) /opt/dns-guardian/logs/monitor.log done fi } # 主循环跟踪日志文件 tail -n0 -F $LOG_FILE | while read -r line; do # 解析BIND查询日志格式提取客户端IP和查询域名 # 示例日志行: client 192.168.1.100#12345: query: www.example.com IN A (192.168.1.1) if [[ $line ~ query:\ ([^[:space:]])\ IN\ A ]]; then domain${BASH_REMATCH[1]} # 去掉末尾的点 domain${domain%.} # 异步调用检查函数避免阻塞日志读取 ( check_domain $domain ) fi done这个脚本非常基础真正的“AI云防护”集成点就在check_domain函数里。你可以将其替换为调用一个本地运行的、轻量级机器学习模型例如用Pythonscikit-learn训练的异常检测模型的接口或者调用商业威胁情报云的API。4.2 模拟“群联AI云防护”集成假设我们有一个本地的Python服务它提供了一个HTTP APIhttp://localhost:5000/check_domain接收域名参数返回一个JSON包含is_malicious是否恶意和ips关联的恶意IP列表字段。我们可以修改上面的check_domain函数中的核心部分# 替换掉原来的可疑模式检查部分 api_response$(curl -s -G --data-urlencode domain$domain http://localhost:5000/check_domain) if [[ $? -eq 0 ]]; then is_malicious$(echo $api_response | jq -r .is_malicious 2/dev/null) malicious_ips$(echo $api_response | jq -r .ips[] 2/dev/null) if [[ $is_malicious true ]]; then for ip in $malicious_ips; do ipset add dns_blocklist $ip -exist 2/dev/null echo [AI] 已拦截IP: $ip (来自恶意域名: $domain) /opt/dns-guardian/logs/monitor.log done fi fi这便实现了与本地AI分析引擎的联动。这个AI引擎可以定期从云端同步最新的威胁模型和特征库实现“云防护”的效果。5. 实战调试、问题排查与性能优化部署完成后稳定运行和问题排查是关键。5.1 基础功能验证测试ipset集合sudo ipset list dns_blocklist | head -5 # 查看集合前几项 sudo ipset test dns_blocklist 8.8.8.8 # 测试一个IP是否在集合中8.8.8.8是谷歌DNS不应在测试iptables规则sudo iptables -L DNS-GUARDIAN -vn --line-numbers # 查看自定义链的流量计数 sudo iptables -L INPUT -vn | grep DNS-GUARDIAN # 查看INPUT链中跳转到该链的规则匹配情况观察pkts和bytes计数如果规则生效匹配到的数据包会被丢弃计数会增加。模拟触发拦截手动向黑名单集合添加一个测试IP如1.2.3.4。sudo ipset add dns_blocklist 1.2.3.4从服务器上尝试ping或curl这个IP。ping -c 2 1.2.3.4 curl --connect-timeout 3 http://1.2.3.4应该会看到ping不通或curl超时。同时检查iptables计数器是否增加。sudo iptables -L DNS-GUARDIAN -vn5.2 常见问题与解决方案问题现象可能原因排查步骤与解决方案脚本执行失败无法下载列表1. 网络不通。2. 源URL失效或被墙。3.curl证书问题。1.curl -v URL查看详细错误。2. 更换备用威胁情报源。3. 对于公开HTTP源可添加--insecure参数仅限测试。ipset命令报错ipset v7.5: The set with the given name already exists集合已存在脚本试图重复创建。在脚本中使用ipset create ... -exist或先检查集合是否存在。推荐使用我们脚本中的“检查-创建/销毁-重建”逻辑。iptables规则不生效流量未被拦截1. 规则顺序错误被前面的规则放行了。2. 规则链未正确挂接。3. 网络拓扑中流量不经过本机如直连网关。1. 用iptables -L -n -v --line-numbers仔细检查规则顺序确保拦截规则在允许规则之后、拒绝规则之前。2. 确认INPUT/FORWARD/OUTPUT链中是否有-j DNS-GUARDIAN的跳转规则。3. 确认服务器是否是流量的必经之路网关、透明桥、DNS服务器。更新脚本运行后网络出现短暂中断全量更新时销毁旧集合、创建新集合的瞬间匹配规则会暂时失效。采用增量更新模式脚本中已实现。只添加新IP删除过期IP避免集合重建。这是生产环境的最佳实践。系统重启后ipset规则丢失未配置ipset持久化。CentOS/RHELipset save /etc/sysconfig/ipset并确保ipset.service启用。Ubuntu/Debian安装ipset-persistent包或创建systemd服务在启动时ipset restore /path/to/backup。DNS监控脚本占用CPU过高tail -F循环处理频繁的日志或dig解析调用太多。1. 增加检测间隔例如每10条日志处理一次或使用inotifywait替代tail -F。2. 对域名进行本地缓存短时间内相同的域名不重复解析。3. 将核心检测逻辑如调用AI API移到独立的、可控制频率的后台进程。误拦截了正常业务IP威胁情报源存在误报。1. 建立白名单机制。在加入ipset前检查IP是否在本地白名单文件/opt/dns-guardian/lists/whitelist.txt中。2. 在脚本的ipset add前添加判断grep -q ^$ip$ whitelist.txt continue。3. 定期审计拦截日志将误报IP加入白名单。5.3 性能优化与高级技巧ipset参数调优hashsize应根据集合大小设置。初始值可以设为略大于当前元素数量。如果元素增长内核会自动扩容但会有一次性的rehash开销。监控ipset list输出的Header信息。maxelem设置一个足够大的值避免未来需要扩容集合。使用hash:net存储网段比存储大量单个IP的hash:ip更节省内存。我们的脚本已经处理了CIDR格式。减少iptables规则遍历确保-m set --match-set规则尽可能早地匹配并DROP减少后续无用规则的匹配。对于非常繁忙的网关可以考虑使用iptables的raw表进行更早的拦截在连接跟踪之前但配置更复杂。日志策略默认不要开启-j LOG规则除非在调试期。海量拦截日志会迅速写满磁盘并消耗I/O。可以配置条件日志例如只记录来自特定内网IP的拦截或者使用速率限制-m limit --limit 5/min -j LOG。IPv6支持创建集合时指定family inet6。使用ip6tables配置IPv6的过滤规则。从威胁情报源获取IPv6黑名单。许多源同时提供IPv4和IPv6列表。高可用考虑将最终的blocklist.txt和ipset备份文件同步到备用节点。在备用节点上编写恢复脚本定期从主节点拉取备份并恢复ipset规则。使用配置管理工具Ansible, SaltStack批量部署和更新规则。这套基于ipset的DNS防护体系将外部威胁情报与本地网络控制深度结合实现了一种轻量级、高性能的主动防御。它尤其适合作为纵深防御中的一层弥补了传统边界防火墙和杀毒软件在应对新型、隐蔽的DNS层威胁时的不足。在实际运营中关键在于威胁情报源的准确性、更新频率以及白名单的精细化管理需要在安全性和业务可用性之间找到最佳平衡点。