forked from jbaggs/anomalous-dns
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathan-dns-domain.zeek
124 lines (111 loc) · 4.05 KB
/
an-dns-domain.zeek
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
##! AnomalousDNS: domain submodule.
##!
##! This submodule tracks unique DNS queries to domains. It is intended to help
##! identify low throughput exfiltration and DNS command and control traffic.
##! It is important to note that it is a domain centered metric. Attributing
##! the traffic to a specific endpoint requires further analysis.
##!
##! Care should be taken in tuning this submodule.
##! Various content delivery networks, and possibly your own network,
##! will set it off until properly tuned. A well crafted whitelist is key.
##!
##! Author: Jeremy Baggs
module AnomalousDNS;
export {
redef enum Notice::Type += {
Domain_Query_Limit,
};
## Threshold for unique queries to a domain per query period
const domain_query_limit = 8 &redef;
## Domain query threshold for recursive resolvers
const recursive_domain_query_limit = 12 &redef;
## Time until queries expire from tracking.
const query_period = 60min;
}
# Whitelist from domain-whitelist.zeek replaces the pattern below
# when set to load in __load__.zeek
const domain_whitelist: pattern = /\.(in-addr\.arpa|ip6\.arpa)$/ &redef;
# Additional whitelisting for recursive resolvers.
# Whitelist from recursive-whitelist.zeek replaces the pattern below
# when set to load in __load__.zeek
#
# The default pattern below is for exempting queries of the form: "_.foo.baz",
# for nameservers that are implementing QNAME Minimisation.
# See: https://tools.ietf.org/html/rfc7816.html#section-3
const recursive_whitelist: pattern = /^(_\..*)$/ &redef;
# Data structures for tracking unique queries to domains
global domain_query: table[string] of set[string] &read_expire=query_period+1min;
global domain_query_hosts: table[string] of set[addr] &read_expire=query_period+1min;
global recursive_domain_query: table[string] of set[string] &read_expire=query_period+1min;
global recursive_domain_query_hosts: table[string] of set[addr] &read_expire=query_period+1min;
function notify(c: connection, domain: string, queries: count, hosts: set[addr])
{
local hostlist = "hosts:";
for (h in hosts)
hostlist = cat(hostlist," ",h);
NOTICE([$note=Domain_Query_Limit,
$conn=c,
$msg=fmt("Unique queries (%sq, < %s) to domain: %s exceeded threshold.",
queries,cat(query_period),domain),
$sub=hostlist,
$identifier= cat(domain,c$id$orig_h),
$suppress_for=30min
]);
}
function track_query(c: connection, query: string)
{
local domain = DomainTLD::effective_domain(query);
if (c$id$orig_h in recursive_resolvers )
{
if (domain ! in recursive_domain_query)
{
recursive_domain_query[domain]=set(query) &write_expire=query_period;
recursive_domain_query_hosts[domain]=set(c$id$orig_h) &write_expire=query_period;
}
else
{
add recursive_domain_query[domain][query];
add recursive_domain_query_hosts[domain][c$id$orig_h];
}
if (|recursive_domain_query[domain]| > recursive_domain_query_limit)
{
event AnomalousDNS::domain_query_exceeded(c,domain);
if (dquery_notice)
notify(c, domain, |recursive_domain_query[domain]|, recursive_domain_query_hosts[domain]);
}
}
else
{
if (domain ! in domain_query)
{
domain_query[domain]=set(query) &write_expire=query_period;
domain_query_hosts[domain]=set(c$id$orig_h) &write_expire=query_period;
}
else
{
add domain_query[domain][query];
add domain_query_hosts[domain][c$id$orig_h];
}
if (|domain_query[domain]| > domain_query_limit)
{
event AnomalousDNS::domain_query_exceeded(c,domain);
if (dquery_notice)
notify(c, domain, |domain_query[domain]|, domain_query_hosts[domain]);
}
}
}
event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count)
{
if (c$id$orig_h in recursive_resolvers )
{
if ( qtype ! in server_ignore_qtypes && recursive_whitelist ! in query && domain_whitelist ! in query)
track_query(c, query);
}
else if (c$id$orig_h in local_dns_servers)
{
if ( qtype ! in server_ignore_qtypes && domain_whitelist ! in query)
track_query(c, query);
}
else if (c$id$orig_h ! in domain_untracked && domain_whitelist ! in query)
track_query(c, query);
}