结论先给:这类信道靠"流量合法"绕过了网络层,所以别在 NetworkPolicy / 出口代理上继续加码——它们在内容、频次、方向上都拦不住时序编码。有效的两条主线是:①检测=把 DNS 日志的"间隔序列"当信号做统计,抓"双峰/低熵/固定长度"这个编码指纹,而不是抓单点异常;②收敛=核心是噪声化信道(让编码方无法稳定控制查询间隔)+压低 Secret 的明文半衰期(让"值得外泄的东西"活不过传输窗口)。CPU quota 打乱 sleep 精度效果最弱、代价最尴尬,放最后。下面按你要的"链路"给。
一、检测:抓指纹,别抓单点
关键认知:业务里 time.Sleep(50ms)+http.Get 产生的是单峰间隔(集中在 ~50ms、带自然抖动);而 bit 编码是"50ms/0ms 两种间隔交替",是双峰。所以判定核心是双峰性 + 固定长度分组,不是"间隔方差大"(那会误报一堆业务)。
对每个 (client_ip, qname) 分组,取滑动窗口(建议 5min)内的相邻查询间隔序列 Δt,算这几个量:
- 双峰分离度(bimodality coefficient):
BC = (g²/s²) + 1,g 为偏度、s 为峰度。经验判据:BC > 0.55视为显著双峰(单峰业务通常 <0.55)。这是把"双峰 vs 单峰"量化、且对抖动鲁棒的最实用指标,比裸 CV 好控误报。 - 序列长度落点:编码常按 64/128/256 bit 成块传。窗口内查询条数若稳定落在
{64,128,256,512}的邻域(±5%),是强特征。 - 低熵:把 Δt 按阈值 T(取双峰谷底,可用 Otsu 分割自动找)量化成 0/1,算序列香农熵。真随机/业务熵高(接近 1 bit/sym),结构化编码熵偏低且带前导同步位。
- 自相关:Δt 的自相关函数在"码元周期"处出现峰值=等间隔编码的指纹,可辅助排除心跳类(心跳间隔通常 >1s,用"间隔量级 <500ms + 双峰"组合即可排除)。
判定规则(组合,别单指标):BC>0.55 且 序列长度落入固定块集合 且 同 (client,qname) 窗口内条数 > N → 命中 dns_timing_anomaly。三个条件同时满足,误报率能压得很低——因为"双峰 + 固定长度 + 高频"三者同时出现在业务侧的概率极低。
阈值怎么定(别拍脑袋):先拿你现有 DNS 日志离线跑 1~2 周,对所有 (client,qname) 算 BC / 长度分布,取 99 分位作为基线阈值再上线。这样阈值来自你自己业务的真实分布,而不是通用文献值。
工具:没有开箱即用的"DNS 时序侧信道"专用工具(DNS C2/tunneling 检测如基于 Shannon entropy 的 dnstlux、DNSAP 检测的是内容熵,思路可迁移到时序,但要自己接)。落地路径:
- CoreDNS 开日志(client IP、qname、query time)→ Kafka/文件;
- 流处理(Flink/Spark Streaming)按
(client_ip, qname)分组算上面四个量,超阈值打 metric; - 更贴近"发起侧时序"的进阶方案:eBPF 挂在 DNS 客户端 socket 层(
sendto/connect到:53)打时间戳,按(pid, dst)算相邻间隔——能直接关联到具体 Pod/进程,定位"谁在编码"比服务端日志快得多。
二、收敛:按性价比排序
- 噪声化信道(最直接、性价比最高):编码方的命门是"能稳定控制 Δt"。在 CoreDNS 侧对可疑
(client,qname)做令牌桶限速 + 随机应答延迟(jitter 20~100ms),把"自由控制间隔"变成"被系统约束的间隔"。一旦码元边界被随机延迟冲掉,解码端还原失败。代价:对本来就低频的合法业务几乎无感,对编码方是致命的。这是"抬高信道噪声"而非"封禁",不破坏合法流量。 - 压低信道容量:per-namespace / per-ServiceAccount 的 QPS 上限 + 强制最小查询间隔。攻击方本质是"用时间换带宽"(64bit 要 2~3min),把速率压到极低并引入不可控抖动,能传完的概率骤降。
- 收掉信道载体:用 CoreDNS allowlist/rewrite,把
internal-dns.svc这类"可被当信道"的泛解析域名收敛——如果某些内部域名本不该被业务高频解析,直接 deny,信道就没有载体了。注意:单纯"强制走集群 DNS、禁直连宿主机 resolver"效果有限,因为红队走的本来就是集群内 DNS,禁直连宿主机 resolver 并不消除这条信道。 - 治本:压低 Secret 明文半衰期:把长生命周期 token 换成短期 bound SA token / KMS 短期凭据,用 csi-secrets-store / 外部 KMS 按需注入、短 TTL,让"值得外泄的东西"半衰期缩到分钟级。配合上面的信道噪声化,外泄窗口内传完的概率进一步骤降。这不能 100% 阻止"已经拿到就外泄",但把攻击的 ROI 打下去。
- CPU quota 打乱 sleep 精度(放最后):效果最弱。50ms 级 sleep 在 Linux 上本就有 ±ms 抖动,但攻击方可自适应(把码元周期拉到 200ms 级吸收抖动);而加 CPU quota 会连累正常业务一起抖。属于"抬高成本"而非"阻断"。
三、落地链路(从异常→判定→告警→收敛)
- 采集:CoreDNS 日志(client IP / qname / query time)落 Kafka;
- 流处理:按
(client_ip, qname)分组,滑窗算 BC / 长度 / 熵 / 自相关; - 判定:组合规则命中 → 打 metric
dns_timing_anomaly{client,qname}; - 告警:先 WARN 进 oncall,连续 N 个窗口命中再 CRIT 并自动触发限速+jitter;
- 收敛:CoreDNS 联动对该
(client,qname)加令牌桶+随机延迟,同时安全团队查该 Pod 是否被植入; - 复盘:把该
(client,qname)入黑/白名单,更新 CoreDNS allowlist。
四、误报控制要点(你最担心这条)
- 双峰 vs 单峰是分水岭:业务 sleep 是单峰,编码是双峰。用 BC 区分,别用裸方差。
- 心跳类(固定间隔 ping)会命中"等间隔",但它 qname 固定且间隔 >1s,用"间隔量级 <500ms + 双峰"组合排除。
- 上线前先跑离线基线,用你自己业务 99 分位定阈值,而不是套文献值。
边界说明
你引用的 id=1450(镜像密钥烘层)、id=565(RBAC 最小权限)解决的是"防读"——即在拿到 token 之前拦住。而这条问题的前提是"红队已经拿到 token",属于防外泄,两者是攻击链的不同段:先做最小权限(565)降低"能拿到"的概率,再做本文的信道噪声化 + 短 TTL(降低"拿到了能传走"的概率),两段合起来才闭环。