DNS恶意流量检测实战:从特征工程到模型训练的完整链路

发布时间:2026/9/15 20:39:27
DNS恶意流量检测实战:从特征工程到模型训练的完整链路 如果你在安全行业待得够久一定会遇到这么一种情况凌晨两点蓝队值守告警平台安安静静但你随手翻了翻DNS query日志发现某个内网主机每隔几秒就在查询一个前缀长达四十多字符的域名而且这个域名你压根没见过。你说它是恶意吧现有规则一条没命中你说它是误报吧哪有正常业务会这么查域名。这种让人睡不踏实的“灰色流量”正是DNS入侵检测要解决的核心问题。摆脱规则库的滞后性从流量的数学特征里找出攻击者的痕迹是我参加DataCon 2020 DNS恶意流量检测方向比赛时一直在做的事。这篇文章不聊虚的直接把当时从数据清洗、特征工程、模型训练到误报追踪的完整链路拆开讲希望能给正在做流量侧安全能力建设的同学一些能直接落地的参考。1. 为什么攻击者死磕DNS从一条“长寿”的查询说起1.1 一个真实场景那条夹在正常请求里的异常查询很多蓝队人员对DNS的第一印象是“协议太老了没什么可看的”。但实际上DNS是所有网络访问的“前置条件”——任何上网行为几乎都要经过一次域名解析。这种高覆盖率的特性决定了它必然会被攻击者盯上。攻击者拿到内网权限之后想要偷偷回传数据、下发指令通常有两条路一条是直连外部IP很容易被防火墙和流量审计发现另一条就是伪装成DNS查询把数据藏在域名字段里发出去再通过DNS响应把指令带回来。我印象特别深的一次排查是某台办公终端持续向一个所谓“安全更新域名”发起查询平均每秒一次子域名部分是一长串高熵字符。当时第一反应是判定它为恶意隧道但去翻威胁情报库这个域名并没有被任何厂商标记。后来把特征数据提取出来才发现它的查询规律和DNS隧道工具生成的流量高度一致固定查询频率、域名长度异常、子域名使用随机字符。规则库没收录这个域名但它从特征层面已经暴露了。1.2 隧道与C2DNS协议为什么这么“好用”DNS之所以成为入侵检测绕不开的战场根本原因有三点全段放行绝大多数网络允许内部主机访问外部DNS服务器即使出方向防火墙策略严格53端口的UDP流量通常也会被放行。数据隐蔽DNS是明文协议但日志量巨大每天百万、千万级的查询让安全团队很难逐条审查恶意消息混在里面就像一滴水融进大海。结构灵活域名本身天然支持多级标签攻击者可以把数据切分成小块塞进子域名里编码方式从Base32、Base64到Hex都可以检测难度被成倍放大。DNS隧道不是唯一形态还有DNS劫持、DNS放大攻击、域名伪造等场景。DataCon 2020这个赛题之所以聚焦DNS深层逻辑是在成熟的攻防对抗里DNS是绕过传统边界防护最稳定的一条信令通道。如果不做深层次的入侵检测只靠静态域名黑名单面对新注册的随机域名基本等于裸奔。2. 拿到赛题数据之后先别急着建模把样本的“体质”摸清楚2.1 原始日志有多脏一份典型DNS query日志的真实面目比赛提供的原始数据并不是干净的标签集合而是接近真实环境中的DNS日志。典型的记录包含时间戳、查询域名、查询类型、源IP、目的IP、响应码、解析结果等字段。我拿到数据后做的第一件事不是跑模型而是把数据全量加载进DataFrame做了一次字段体检。字段名含义典型脏数据问题timestamp查询时间时间戳格式不统一存在重复毫秒错位domain查询域名存在大写混用、末尾多一个点、Unicode域名qtype查询类型A记录为主但混有AAAA、TXT、CNAME等src_ip客户端IP存在多个出口NAT后的IP混淆难以定位具体主机response_code响应码NXDOMAIN占比极高不能直接当作异常特征resolve_result解析结果部分记录为空部分记录是多个IP用分号拼接光是把domain字段规整成小写、去掉末尾点就花了不少功夫。这些细节如果你不处理后期做特征统计时同一个域名会被拆成好几个实体导致计数特征失真。2.2 标签的真相恶意样本并没有你想的那么多赛题提供了一些标注但恶意样本的占比普遍偏低一般只有百分之几甚至千分级。这意味着两个非常麻烦的问题类别不均衡用原始数据直接训练二分类器模型只要学会把一切判为“正常”准确率也非常高但这显然毫无意义。标签噪声标注来自人工抽检或情报平台本身就有误标。有些被标记为恶意的域名实际是被黑产批量搭建的菠菜站点有些未标记的域名可能就是当时还没被发现的C2。我当时的做法是先不去动标签只做一轮“碰撞检查”。把标注恶意域名逐个用规则解析一遍看它们是否具备高熵子域名、长域名、异常TTL等明显特征。结果发现至少十个标注样本的特征和普通恶意样本完全不同更像是广告域名。这类样本如果直接拿去训练会严重污染模型的“恶意语义”。2.3 领域知识先行的样本切分按天切分比随机切分更诚实还有一个经验DNS流量本质上是一种时间序列数据攻击者的行为模式可能会在一天的不同时段发生变化。如果做随机切分训练集里出现了测试集某个恶意域名的部分历史流量测试分数会被虚高乐观。正确做法是按时间切分比如前80%时间的样本作为训练集后20%作为测试集。这样可以检验模型对新出现的攻击模式是否有泛化能力而不是靠“背题”拿高分。3. 特征工程把DNS流量翻译成模型看得懂的数字3.1 字符级特征域名本身就有“体味”同一个域名出现在日志里正常的业务域名长度较短、语义明确恶意域名通常会刻意拉长字符串以增加数据载荷容量。字符级特征是用模型识别域名的第一层抓手我实际提取过且效果比较稳的特征包括这些域名总长度正常域名大多在15到40个字符之间隧道域名动不动就超过50个字符。但要注意不能用固定阈值一刀切因为有些合法业务域名确实很长。子域名标签数量与最大标签长度恶意隧道偏好使用多层子域名单看域名总长度区分度不如“子域名数量最大标签长度”的组合。字符熵计算域名整体和每个标签的信息熵。正常域名里字母组合受语义约束熵值通常在3到4之间随机生成的隧道子域名熵值可达4.5以上。元音字母占比、数字占比、连续辅音占比这些是语言统计的小技巧。正常词汇有较多元音字符拼接相对自然while(1)风格的随机字符串往往元音占比极低。数字与字母混排频度连续切换字母和数字的次数越多越像机器随机生成。当时我写了一个Python方法把这些特征算出来验证集上的提升非常明显。核心代码如下熵的计算用到了信息论基础import math from collections import Counter def shannon_entropy(text): if not text: return 0.0 prob [count / len(text) for count in Counter(text).values()] return -sum(p * math.log2(p) for p in prob) def domain_char_features(domain): labels domain.rstrip(.).split(.) total_len len(domain) max_label_len max(len(x) for x in labels) label_count len(labels) digit_ratio sum(c.isdigit() for c in domain) / total_len vowel_ratio sum(c in aeiou for c in domain.lower()) / total_len entropy shannon_entropy(domain) return { domain_length: total_len, label_count: label_count, max_label_len: max_label_len, digit_ratio: digit_ratio, vowel_ratio: vowel_ratio, entropy: entropy }别小看这组特征对NXDOMAIN洪泛攻击、DNS隧道这类场景光字符熵这一项就能过滤掉大半常见误报。3.2 行为级特征单条query看不出异常一组query才有规律字符特征解决的是“域名长得好不好看”但真正的检测能力还要看行为。试想一下一个正常用户一天可能只访问某个域名两三次而一台被控主机为了和C2保持联系会以固定间隔反复解析同一个域名。这种行为模式单条日志看不出来但放到时间窗口里就变得格外扎眼。我实际采用的行为特征包括时间窗口内同一源IP对同一域名的查询次数统计5分钟、30分钟、1小时三个粒度恶意隧道往往呈现高频、周期性。访问域名种类数/查询次数的比值如果一台主机短时间查询大量不同域名可能是DNS探测如果主机重复查询极少数域名可能是隧道保持连接。同一域名被多少不同源IP访问正常域名通常会被很多主机访问恶意域名往往只被少数受控主机访问。这个特征对检测新出现的隧道域名很有效。响应结果特征NXDOMAIN的响应占比、响应IP是否为空、响应IP是否属于特定段。恶意域名经常挂马或轮换IP导致解析结果不稳定。TTL特征统计查询域名的TTL均值与方差。隧道域名为了快速更新记录TTL往往设置得很小例如60秒或300秒正常业务域名的TTL一般以小时或天为单位。3.3 特征组合与筛选不是特征越多越好特征提取完我大概汇总出了60多个维度。这时候最容易犯的错是“一股脑全扔给模型”结果训练时间长、过拟合严重AUC看着很高但上线就拉胯。我的做法是做一轮互信息筛选把与标签相关性弱、方差接近0的特征去掉。比如“响应IP是否属于内网段”这种特征在测试集上分布极其稀疏直接淘汰。另外把字符特征和行为特征组合成交叉特征也很好用。例如“高频查询次数×高熵子域名”这个二维组合基本就是DNS隧道的画像。光靠单维度阈值要么放过真实恶意样本要么误杀CDN节点和多级CNAME的合法业务。4. 模型选型与训练细节为什么我押注树模型而不是深度学习4.1 三类模型的横向对比在DataCon 2020的DNS场景里我对比过三类主流方案纯规则引擎、树模型集成、深度神经网络。三类方案各有利弊方案优点缺点适用场景规则引擎解释性强、实时性好、部署简单依赖先验知识未知攻击变体容易漏对已有恶意家族做快速封堵GBDT树模型能处理表格特征、可解释性较好、调参成本低需要人工特征工程对序列关系不敏感大多数流量检测场景的首选基线深度学习能自动发现特征处理长序列需要大量样本可解释性差部署成本高超大流量规模且标注充足的情况综合权衡后我最终选择了基于LightGBM的树模型方案。核心原因是DNS入侵检测的绝大多数有效特征在结构上是离散和低维的树模型对这类特征非常友好不需要像深度学习那样做大量数据增强和归一化。加上当时赛题的标注样本数量并不充裕深模型很容易过拟合到少量恶意样本上泛化能力反而不如调参得当的GBDT。4.2 样本不均衡不要一上来就欠采样恶意样本占比低是这类题目的标配。很多人第一反应是随机欠采样把正常样本砍到和恶意样本一样多。我实际试过这样做的结果是模型对“正常样本”的语义理解严重不足上线后误报率高得让人崩溃。更好的做法是先用全量数据训练一个初始模型拿到特征重要性排序和每个样本的得分然后针对分数偏高的“难样本”做加权。LightGBM原生支持is_unbalanceTrue参数它会根据类别占比自动调整正样本权重。同时我用scale_pos_weight结合负样本数量/正样本数量设定权重比手动欠采样温和得多。在验证策略上我做了分层五折交叉验证但加了一个约束同一个域名在同一个折里不能同时出现在训练集和测试集。如果不加这个约束模型等于在“背域名”成绩虚高实际业务环境里却完全不可用。4.3 时间序列的验证陷阱用后20%时间窗口判断真实泛化能力即使做了域名级别的去重时间穿越问题依然存在。攻击者的C2基础设施可能连续几天使用同一批域名模型在训练集里见过某恶意域名的规律测试集里恰好又是它的延续这种“知识”本质上是时间穿越。我在最后提交前专门按时间顺序把数据重新划分前80%时间训练后20%测试。实验结果非常真实AUC比随机划分掉了大概6到8个百分点。这个回落在意料之中但也提醒我模型对“已知攻击的变体”有泛化能力但对“从未见过的攻击手法”仍然依赖字符特征来兜底。4.4 训练结果观察特征重要性里的意外发现训练完成后我习惯性地查看了特征重要性排名第一的不是熵不是域名长度而是“同一源IP在5分钟内查询同一域名的次数”。这让我反思了很久单条域名的字符特征只是外貌行为模式才是恶意流量的灵魂。攻击者想要稳定维持C2就必须高频查询而正常用户几乎不会在短时间内重复解析同一个域名。这种行为差异天然比字符层面的伪装更有区分度。5. 误报排查一次高置信度误判里的完整追踪链路5.1 误报出现得分0.93的“恶意域名”其实是合法动态DNS比赛临近结束时我发现模型对某个样本给出了0.93的高分但人工复核却判定它为正常业务。这条流量数据是一个内网设备在不断解析某个动态DNS服务商分发下来的子域名子域名是设备ID随机字符串的组合。从字符特征看长度超过40、熵值高、全小写字母混数字几乎就是教科书级别的“隧道域名”从行为特征看每当IP变化时设备会立即重新解析导致查询频率极高。两项核心特征全部命中恶意画像模型判罪判得理直气壮。5.2 逐层拆解从特征回溯到原始报文要定位误报根因不能只盯着模型输出得逐层回溯。我的排查链路是这样的第一步把预测得分最高的1000个样本全部导出来检查它们的共同特征模式。结果发现大量样本集中在某一个域名提供商下的第二级域名。第二步把对应样本的原始query日志调出来发现这些查询记录的时间间隔并不均匀而是集中在这个设备的内网IP变化后的几秒内——很像动态IP环境下设备为了保持连通而主动更新DNS。第三步把域名前缀用信息熵解码工具还原发现前缀虽然高熵但它符合一种特定编码规则解析出来的内容是一串设备序列号。这个规则和隧道场景的“分块传输回传数据”完全不同一个在表达“我是谁”一个在传递“我的数据”。5.3 修复动作与阈值策略搞清楚根因后我做了两个调整加入动态DNS服务商白名单对已知的合法动态DNS域做白名单放行。但白名单不是一刀切而是结合“前缀是否可解码为设备标识”做二次校验既保留对陌生隧道的检测力又压制这方面的误报。调整行为特征阈值针对动态DNS场景查询频率特征不再只看次数还要看两次查询之间的时间间隔分布。如果查询峰值集中在IP变化前后则判定为正常更新行为如果查询间隔均匀且持续整夜则判定为可疑心跳。这次误报排查最大的收获是模型输出只是概率不是真相。上线一个检测模型配套的必须是一套可解释、可回溯的分析工具。没有这个能力你根本不敢把模型结果直接推到封禁策略上。6. 从比赛到实战把模型变成一项能持续运转的安全能力6.1 实时检测链路怎么搭从DNS日志采集到告警推送比赛里的模型跑完就结束了但实战里要做的事还很多。我当时搭的是一条准实时的检测链路DNS日志通过采集组件从内网DNS服务器旁路方式拉取写入Kafka消息队列流处理引擎按窗口做聚合统计把字符特征和行为特征算出来后送进训练好的模型打分。评分超过阈值的域名和源IP会被丢进一个“待确认队列”由安全运营人员每日复核。这条链路里最容易踩的坑是“全局聚合窗口”导致的计算延迟。DNS流量每秒成千上万条如果每一条都要做一次5分钟窗口的统计计算压力会非常大。实际做法是使用滑动窗口的预聚合结果比如每30秒更新一次窗口内的计数特征而不是每条都从头扫描全量窗口。6.2 持续维护比训练模型更费功夫模型上线三个月后我发现误报率悄悄上升了一个百分点。查下去才知道某个云服务商调整了域名策略大量合法业务都开始使用高熵子域名做CDN路由。这类流量的字符特征突然变得“恶意化”但行为特征还是正常业务的模式。这个案例让我意识到DNS入侵检测是一个持续对抗的过程。你需要定期用最新的真实流量做一轮“伪标签校准”把模型得分在0.8以上的样本拉出来人工确认哪些是误报、哪些是漏网之鱼。每次校准不只是调阈值还要把新的特征模式沉淀到训练集里定期重训模型。另外域名黑名单和白名单要建立生命周期机制动态DNS域名、安全情报域名、内部业务域名都会更新至少每月审查一次。6.3 如果再打一次DataCon我会在哪些地方做得不同复盘整个比赛过程有三点我确信下次可以做得更好。第一我会在特征工程阶段就引入更多网络层的上下文信息比如DNS响应IP是否在威胁情报黑名单里以及响应IP与已知恶意IP的相似度。在缺少上下文纯拼域名特征的情况下检测能力很快就触及天花板。第二我会把CNAME链的解析关系也纳入检测。很多恶意域名会通过CNAME指向一个合法CDN域名来狐假虎威只检测查询的原始域名很容易被绕过必须追踪完整的CNAME链。第三我会在训练阶段同时做多模型集成把决策树、逻辑回归、一个简单的字符级CNN同时训练用Blending方式做最终融合。虽然树模型主力的效果已经不错但不同算法看到的数据侧重点不一样集成之后对个别困难样本的纠错能力会明显更强。回到开头那个凌晨两点的场景如果你手头有一套基于DNS入侵检测思路的辅助工具处理方式会完全不一样你不会盯着一条孤立的query猜来猜去而是能立刻看到这个域名的字符熵、这个源IP的查询频率、历史窗口里是否有类似行为。这就是特征化检测相对静态规则的真正价值——它不依赖见过这个域名而是依赖理解恶意流量背后的行为本质。希望这篇文章里从数据清洗到误报追踪的每一步能给你在构建自己的DNS检测能力时节省一些绕路的成本。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询