How to use Zeek Detect Suspicious DNS traffic
写在前面:
我嘞个去!小不在意都已经到了2024的下半年了,近期花了一些时间研究了DNS异常流量的检测模型。老样子依旧是放在Zeek上来实现,Zeek作为网络流量的“瑞士军刀”可玩性还是很大的。由于现在写Blog时间太少了,这里将会通过分期的形式进行更新。
本期主题是:如何通过 Zeek Summary Statistics 实现动态基线,检测 DNS Payload 请求长度上的异常。
检测维度
1. 请求包长度异常
通常最简单粗暴的方式就是通过手动指定一个『长度』作为告警阈值。但是难点就是『长度』设置为多少才算合适?长度设置较小,告警量多,误报率就会增加。长度设置较大,告警量少,但不一定会有告警。所以最好的方式就是能够动态计算一段时间内的『长度』作为阈值,以这个为基线去计算标准差和平均值再进行比对。
定义一个动态基线时间窗口
1
2# Global variable for baseline interval
const baseline_interval: interval = 300sec &redef; # Baseline interval for SumStats collection由于DNS在不同的请求类型下Payload长度也会有明显的差异,所以为了避免这类“干扰项”我们将按照DNS请求类型进行聚合来计算动态基线
1
2
3
4
5
6
7
8
9
10export {
# Define a record type to store the average and standard deviation
type Stats: record {
prev_avg: count; # Previous average payload size
prev_sd: count; # Previous standard deviation of payload size
};
# Define a table to store the data
option data: table[string] of Stats;
}考虑到实际环境中DNS的请求数量会非常的多,我们也需要考虑代码在上线后对集群负载带来的影响。所以,这里将会通过
local_domain
、ignore_qtypes
、ignore_querys
、ignore_subdomains
、ignore_domains
、ignore_tlds
等多个条件进行控制。- local_domian:代表实际环境中的内部域名,这些域名通常用于企业内网或者内部业务系统之间的通讯和API调用。通常这些域名不需要被列入到动态基线的计算范围内,因为它们在多数情况下都是在内部网络环境中使用,而非公开互联网访问。这有助于减少不必要的噪音和误报,提高基线计算的准确性。
- ignore_qtypes:代表你需要忽略的DNS请求类型。
- ignore_querys:代表你需要忽略的完整DNS请求。如:
canon88.github.io
就是一个完整的域名请求 - ignore_subdomains:代表你需要忽略的子域名。例如:在
canon88.github.io
中,canon88
就是子域名 - ignore_domains:代表你需要忽略的主域名或二级域名。例如:在
canon88.github.io
中,github.io
是主域 - ignore_tlds:代表你需要忽略的顶级域名(TLD)。例如:在
canon88.github.io
中,io
就是顶级域名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25event dns_message(c: connection, is_orig: bool, msg: dns_msg, len: count) &priority=-10 {
# Return early if the domain is local
if (c$dns$is_local_domain)
return;
# Filter out queries of types that should be ignored
if (c$dns$qtype_name in SuspiciousDNSTraffic::ignore_qtypes)
return;
# Ignore trusted queries
if (c$dns$query in SuspiciousDNSTraffic::ignore_querys)
return;
# Ignore trusted subdomains
if ( (c$dns?$subdomain) && (c$dns$subdomain in SuspiciousDNSTraffic::ignore_subdomains) )
return;
# Ignore trusted domains
if ( (c$dns?$domain) && (c$dns$domain in SuspiciousDNSTraffic::ignore_domains) )
return;
# Ignore trusted top-level domains
if ( (c$dns?$tld) && ( (c$dns$tld in SuspiciousDNSTraffic::ignore_tlds) || (SuspiciousDNSTraffic::ignore_tlds_regex in c$dns$tld) ) )
return;
}下面开始进入“正题”,通过Zeek Summary Statistics 进行 DNS Payload 动态基线的计算。
计算基线数据:使用
SumStats
模块每隔baseline_interval
(300秒) 计算一次 DNS 负载大小的平均值和标准差,并将这些数据存储在data
表中。比对当前负载大小:在处理每个 DNS 消息时,当前负载大小(
len
)会与数据表中的先前平均值(prev_avg
)和标准差(prev_sd
)进行比对。比对阈值计算:
最大平均值阈值:
avg * multiplier_threshold
最小平均值阈值:
avg / multiplier_threshold
最大标准差阈值:
avg + sd * deviation_threshold
最小标准差阈值:
avg - sd * deviation_threshold
确定可疑负载:
如果当前负载大小超过最大平均值阈值或小于最小平均值阈值,则标记为可疑。
如果当前负载大小超过最大标准差阈值或小于最小标准差阈值,则标记为可疑。
1 | # Event handler for DNS messages |