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
    10
    export {
    # 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_domainignore_qtypesignore_querysignore_subdomainsignore_domainsignore_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
    25
    event 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# Event handler for DNS messages
event dns_message(c: connection, is_orig: bool, msg: dns_msg, len: count) &priority=-10 {
# Store the payload size in the DNS::Info record
c$dns$payload_len = len;

# Observe the payload size for SumStats calculations
SumStats::observe("dns_payload_len", SumStats::Key($str=c$dns$qtype_name), SumStats::Observation($num=len));

if (c$dns$qtype_name in data) {
local avg = data[c$dns$qtype_name]$prev_avg;
local sd = data[c$dns$qtype_name]$prev_sd;

local max_avg_threshold = avg * SuspiciousDNSTraffic::multiplier_threshold;
local min_avg_threshold = avg / SuspiciousDNSTraffic::multiplier_threshold;
local max_sd_threshold = avg + sd * SuspiciousDNSTraffic::deviation_threshold;
local min_sd_threshold = avg - sd * SuspiciousDNSTraffic::deviation_threshold;

# Compare the current payload size with the calculated average and standard deviation
if ( avg != 0.0 ) {
local alert_reason: set[string] = set();
if ( len > max_avg_threshold ) {
c$dns$is_oversize_payload = T;
c$dns$avg_threshold = max_avg_threshold;
add alert_reason["payload_len > avg_threshold"];
} else if ( len < min_avg_threshold ) {
c$dns$is_oversize_payload = T;
c$dns$avg_threshold = min_avg_threshold;
add alert_reason["payload_len < avg_threshold"];
}

if ( len > max_sd_threshold ) {
c$dns$is_oversize_payload = T;
c$dns$sd_threshold = max_sd_threshold;
add alert_reason["payload_len > sd_threshold"];
} else if ( len < min_sd_threshold ) {
c$dns$is_oversize_payload = T;
c$dns$sd_threshold = min_sd_threshold;
add alert_reason["payload_len < sd_threshold"];
}

if ( |alert_reason| > 0 ) {
c$dns$alert_reason = alert_reason;
}
}

# c$dns$payload_prev_avg = avg;
# c$dns$payload_prev_sd = sd;
}
}

# Initialize the event handler for analyzing DNS requests
event zeek_init() {
# Define a reducer to calculate the standard deviation of DNS payload sizes
local r_std_dev = SumStats::Reducer($stream="dns_payload_len", $apply=set(SumStats::STD_DEV));

# Create a SumStats object to collect data every baseline_interval (e.g. 5 minutes)
SumStats::create([
$name = "dns_payload_len.std_dev",
$epoch = baseline_interval,
$reducers = set(r_std_dev),
$epoch_result(ts: time, key: SumStats::Key, result: SumStats::Result) = {
local current_avg = double_to_count(result["dns_payload_len"]$average);
local current_std_dev = double_to_count(result["dns_payload_len"]$std_dev);

# Initialize the table with the given data
data[key$str] = [$prev_avg = current_avg, $prev_sd = current_std_dev];

# Update the data table with the new values
Config::set_value("SuspiciousDNSTraffic::data", data);
}
]);
}

未完待续视频素材下载, 未完待续AE模板下载_光厂(VJ师网)

2. 请求域名长度异常
3. 非常见域名监控
4. Fast Flux
5. 非常见请求类型
6. 域名信誉度