Go 主机安全面试:Linux DNS 隧道与异常域名外联检测
主机被入侵后,攻击者不一定直接连高危端口。DNS 因为常被放行,容易被用来做命令控制、数据外传、DGA 域名探测或低频心跳。面试官通常会追问:Go Agent 在主机侧怎么发现 DNS 隧道?只看 53 端口够不够?怎么把域名、进程、用户和网络连接串成可解释告警?
岗位场景
Linux 主机
-> 采集进程、DNS 查询、UDP/TCP 连接、resolver 配置和网络命名空间
-> 标准化 query、qtype、rcode、进程、用户、容器和目标 DNS 服务器
-> 识别长子域、高熵标签、高频 NXDOMAIN、异常 DNS 服务器和可疑进程
-> 关联 Web RCE、落地文件、反弹 Shell、账号异常和后续外联这类题考的是 Linux 网络栈、DNS 协议基础、进程归因、规则降噪、攻击链还原和 Go Agent 低开销采集能力。
高频面试题
1. 为什么 DNS 隧道适合做主机安全面试题?
简洁答案:DNS 是基础网络协议,很多环境默认允许出站查询;攻击者可以把数据编码进子域名,通过 DNS 请求绕过部分网络访问控制。
关键知识点:
- DNS 请求常走 UDP/53,也可能走 TCP/53,不能只看 UDP。
- 隧道流量常见特征是长子域、高熵字符串、固定根域、高频查询和异常 qtype。
- 正常业务也会有 CDN、监控、服务发现和安全产品查询,必须结合进程和基线判断。
Go 落地思路:
- 事件模型保留
query、qtype、rcode、dns.server、process.name、pid、uid、container.id。 - 不把“访问 53 端口”直接等价为告警,而是作为网络证据之一。
- 对域名特征、查询频率、进程身份和历史基线做组合评分。
2. 主机侧可以从哪些位置采集 DNS 证据?
简洁答案:可以从进程网络事件、eBPF socket 事件、本地解析器日志、抓包、审计日志和配置文件变化里采集,优先选择对业务影响小且能做进程归因的来源。
常见来源:
| 证据来源 | 可拿到的字段 | 局限 |
|---|---|---|
| eBPF socket/sendto | 进程、目标地址、端口、payload 摘要 | 需要内核能力和降级方案 |
| conntrack/netlink | 连接五元组、状态 | 不一定有 DNS query 内容 |
| 本地 DNS 日志 | query、rcode、qtype | 可能缺少真实进程 |
| pcap 抓包 | 完整 DNS 包 | 成本高,权限敏感 |
/etc/resolv.conf | DNS 服务器配置 | 只能发现配置变化 |
Go 落地思路:
- 能采 payload 时解析 DNS query;不能采 payload 时至少记录进程到 53 端口的连接。
- 对采集能力上报
capability,例如can_trace_dns_payload、can_map_socket_process。 - 容器场景要保留网络命名空间和容器 ID,否则容易把不同业务的 DNS 行为混在一起。
3. DNS 隧道的域名特征通常怎么看?
简洁答案:重点看单个 label 长度、整体域名长度、字符分布、熵值、数字比例、重复根域和查询频率。
关键知识点:
- 正常域名通常有可读单词、业务前缀或 CDN 结构。
- 隧道会把数据编码成
base32、base64变体或十六进制字符串。 - DGA 域名也可能高熵,但更偏大量根域轮询;DNS 隧道更常固定根域下生成大量长子域。
Go 落地思路:
- 把域名拆成 label,分别计算最长 label、平均长度、数字比例和字符熵。
- 对固定根域做滑动窗口统计,例如 5 分钟内子域数量、NXDOMAIN 数量、不同 label 数量。
- 不用复杂模型也能先做可解释规则,后续再用样本数据调阈值。
func labelLooksEncoded(s string) bool {
if len(s) < 24 {
return false
}
var alphaNum int
for _, r := range s {
if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' {
alphaNum++
}
}
return alphaNum == len(s)
}4. 为什么必须做进程归因?
简洁答案:同样是 DNS 查询,浏览器、系统服务、安全产品和 Web 子进程的风险完全不同;没有进程归因就很难降噪和复盘。
关键知识点:
systemd-resolved、dnsmasq、容器 runtime 可能代理大量 DNS 请求。- 真正的可疑进程可能是
bash、python、perl、php、java、未知 ELF 或临时目录文件。 - Web RCE 后的 DNS 隧道常表现为 Web 父进程拉起脚本,再由脚本发起异常 DNS 查询。
Go 落地思路:
- 采集 socket 创建时记录
pid,再补充/proc/<pid>/cmdline、cwd、exe、uid。 - 如果 DNS 请求经过本地 resolver,要尽量保留调用方进程和代理进程两层信息。
- 规则输出写清楚“哪个进程查询了哪个根域、频率是多少、为什么异常”。
5. DNS 隧道和正常 CDN/服务发现怎么区分?
简洁答案:正常 CDN 和服务发现通常有稳定业务域、已知进程、规律 qtype 和可解释部署背景;DNS 隧道更容易出现异常进程、高熵子域、高失败率和攻击链邻近事件。
降噪思路:
- 建立主机、业务、进程、根域四层基线。
- 对已知安全产品、监控组件、服务发现组件配置白名单,但保留审计。
- 结合相邻事件:Web RCE、临时目录执行、异常登录、反弹 Shell、文件落地。
Go 落地思路:
- 规则不要写成“长域名即告警”,而是写成组合条件。
- 维护小而明确的内置可信根域,客户环境差异交给策略配置。
- 告警分级:单一异常域名做低危,高熵高频且关联可疑进程做高危。
可疑进程 + 高熵长子域 + 固定根域高频查询
+ NXDOMAIN 比例异常
+ 时间窗口内存在 Web RCE 或临时目录执行
=> DNS 隧道或异常域名外联6. DNS 查询失败率为什么有检测价值?
简洁答案:DGA、探测和错误配置的隧道工具常会产生大量 NXDOMAIN;正常业务也会失败,但通常集中在固定组件和可解释域名上。
关键知识点:
NXDOMAIN表示域名不存在,短时间大量出现需要关注。SERVFAIL可能来自 DNS 服务器异常,不一定是攻击。- 查询失败率必须按进程、根域和主机维度统计,不能只看全机总量。
Go 落地思路:
- 用滑动窗口维护
total、nxdomain、unique_subdomain。 - 同一个根域下大量随机子域失败,比全机失败数更有解释力。
- 窗口状态要可控,例如只保留近 5 到 10 分钟,避免 Agent 内存被高基数域名拖垮。
7. 如何设计一条低误报的 DNS 隧道检测规则?
简洁答案:用“进程风险 + 域名形态 + 查询行为 + 攻击链上下文”组合,而不是靠单个特征。
规则字段:
| 维度 | 示例字段 | 作用 |
|---|---|---|
| 进程 | process.name、argv、cwd、uid | 判断调用方是否异常 |
| 域名 | query、root_domain、longest_label、entropy | 判断是否像编码数据 |
| 行为 | qps、unique_subdomain、rcode_ratio | 判断是否高频或失败 |
| 上下文 | RCE、落地文件、登录、外联 | 还原攻击链 |
Go 落地思路:
- 先用结构体表达标准事件,再让规则只依赖标准字段。
- 对高基数字段做限流和采样,避免单机恶意流量打爆 Agent。
- 输出命中证据列表,方便客户判断是攻击、业务 SDK 还是安全工具。
8. 线上误报排查时应该问客户要哪些证据?
简洁答案:要拿到命中时间窗口内的进程树、DNS 查询样本、根域统计、主机角色、业务发布记录和相邻安全事件。
排查清单:
- 命中进程的
exe、cmdline、cwd、文件 hash、签名或包来源。 - 同根域查询样本,包含成功、失败、qtype、DNS 服务器。
- 主机角色:Web、数据库、CI、Kubernetes 节点、办公终端。
- 相邻事件:异常登录、Web RCE、下载执行、反弹 Shell、敏感文件修改。
- 是否存在监控、安全扫描、服务发现、日志 SDK 或灰度发布。
Go 落地思路:
- Agent 本地保留短时间证据缓存,告警时附带前后窗口摘要。
- 服务端支持按
host_id + root_domain + process聚合查询。 - 对确认为业务流量的样本生成基线规则,而不是简单关闭检测。
Go 实现设计要点
采集层
-> eBPF/socket/netlink/resolver log 适配
标准化层
-> DNSQueryEvent: query、qtype、rcode、server、pid、uid、container
富化层
-> 进程画像、根域提取、label 特征、历史基线
检测层
-> 滑动窗口统计、组合规则、限流采样
告警层
-> 证据列表、攻击链关联、降噪建议设计时优先保证三件事:
- 字段稳定:规则不要依赖采集源私有字段。
- 成本可控:高频 DNS 事件必须有窗口、上限和丢弃策略。
- 证据可解释:告警里展示样本域名、进程、频率和关联事件。
学习要点
- DNS 基础:query、qtype、rcode、UDP/TCP 53、递归解析。
- Linux 网络采集:socket、netlink、eBPF、网络命名空间。
- 进程归因:PID、PPID、
/proc、用户、工作目录、可执行路径。 - 异常检测:高熵 label、固定根域高频、NXDOMAIN 比例、DGA 与隧道差异。
- 告警降噪:业务基线、可信进程、白名单、攻击链上下文。
- Go 工程化:事件结构、滑动窗口、限流、内存上限、能力降级。
小练习
- 设计一个
DNSQueryEvent结构,要求能表达域名、qtype、rcode、进程、用户、容器和 DNS 服务器。 - 写一个函数统计 5 分钟内同一根域的不同子域数量,并限制最多保留 1000 个子域样本。
- 给出一条规则:
php-fpm子进程在/tmp执行后,短时间内对同一根域发起大量高熵子域查询。 - 解释为什么
systemd-resolved发出的 DNS 请求不能直接归因到业务进程。 - 设计一个误报排查流程,区分 DNS 隧道、CDN SDK、服务发现和安全扫描。
- 思考如何在不抓完整 payload 的情况下,仍然保留足够证据支撑告警解释。
复盘题
- DNS 隧道检测为什么不能只看 53 端口?
- 高熵长子域一定是攻击吗?哪些业务场景会产生类似特征?
- 主机侧 DNS 检测和网络侧 DNS 检测各有什么优势?
- Go Agent 如何避免被高频 DNS 事件拖垮?
- 一条高质量 DNS 隧道告警应该包含哪些证据?
