From 170276807b7aae7519f4ce9f9f63d62d18c89dc3 Mon Sep 17 00:00:00 2001 From: Evan Typanski Date: Tue, 13 Aug 2024 15:27:25 -0400 Subject: [PATCH] Add DNS TKEY event --- NEWS | 3 + scripts/base/init-bare.zeek | 15 +++++ src/analyzer/protocol/dns/DNS.cc | 61 ++++++++++++++++++ src/analyzer/protocol/dns/DNS.h | 11 ++++ src/analyzer/protocol/dns/events.bif | 17 +++++ .../scripts.base.protocols.dns.tkey/dns.log | 11 ++++ .../scripts.base.protocols.dns.tkey/output | 21 ++++++ testing/btest/Traces/dns/tkey.pcap | Bin 0 -> 4618 bytes .../scripts/base/protocols/dns/tkey.zeek | 22 +++++++ 9 files changed, 161 insertions(+) create mode 100644 testing/btest/Baseline/scripts.base.protocols.dns.tkey/dns.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dns.tkey/output create mode 100644 testing/btest/Traces/dns/tkey.pcap create mode 100644 testing/btest/scripts/base/protocols/dns/tkey.zeek diff --git a/NEWS b/NEWS index b7ee0395f0..2e61bae237 100644 --- a/NEWS +++ b/NEWS @@ -32,6 +32,9 @@ New Functionality * The MySQL analyzer now generates a ``mysql_user_change()`` event when the user changes mid-session via the ``COM_USER_CHANGE`` command. +* The DNS analyzer was extended to support TKEY RRs (RFC 2390). A corresponding + ``dns_TKEY`` event was added. + Changed Functionality --------------------- diff --git a/scripts/base/init-bare.zeek b/scripts/base/init-bare.zeek index 9ac3870e8b..1da63d14b1 100644 --- a/scripts/base/init-bare.zeek +++ b/scripts/base/init-bare.zeek @@ -4083,6 +4083,21 @@ type dns_edns_cookie: record { server_cookie: string &default=""; ##< Cookie from the server (0 bytes if missing, or 8 to 32 bytes). }; +## A DNS TKEY record. +## +## .. zeek:see:: dns_TKEY +type dns_tkey: record { + query: string; ##< Query. + qtype: count; ##< Query type. + alg_name: string; ##< Algorithm name. + inception: time; ##< Requested or provided start of validity interval for keying material. + expiration: time; ##< Requested or provided end of validity interval for keying material. + mode: count; ##< Key agreement or purpose of the message. + rr_error: count; ##< Error code. + key_data: string; ##< Key exchange data field. + is_query: count; ##< The RR is a query/Response. +}; + ## An additional DNS TSIG record. ## ## .. zeek:see:: dns_TSIG_addl diff --git a/src/analyzer/protocol/dns/DNS.cc b/src/analyzer/protocol/dns/DNS.cc index 304893f121..54a374cfc3 100644 --- a/src/analyzer/protocol/dns/DNS.cc +++ b/src/analyzer/protocol/dns/DNS.cc @@ -256,6 +256,8 @@ bool DNS_Interpreter::ParseAnswer(detail::DNS_MsgInfo* msg, const u_char*& data, case detail::TYPE_EDNS: status = ParseRR_EDNS(msg, data, len, rdlength, msg_start); break; + case detail::TYPE_TKEY: status = ParseRR_TKEY(msg, data, len, rdlength, msg_start); break; + case detail::TYPE_TSIG: status = ParseRR_TSIG(msg, data, len, rdlength, msg_start); break; case detail::TYPE_RRSIG: status = ParseRR_RRSIG(msg, data, len, rdlength, msg_start); break; @@ -824,6 +826,48 @@ bool DNS_Interpreter::ParseRR_TSIG(detail::DNS_MsgInfo* msg, const u_char*& data return true; } +bool DNS_Interpreter::ParseRR_TKEY(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, + const u_char* msg_start) { + if ( ! dns_TKEY || msg->skip_event ) { + data += rdlength; + len -= rdlength; + return true; + } + + if ( len < 16 ) + return false; + + const u_char* data_start = data; + u_char alg_name[513]; + int alg_name_len = sizeof(alg_name) - 1; + + u_char* alg_name_end = ExtractName(data, len, alg_name, alg_name_len, msg_start); + + if ( ! alg_name_end ) + return false; + + uint32_t inception = ExtractLong(data, len); + uint32_t expiration = ExtractLong(data, len); + uint16_t mode = ExtractShort(data, len); + uint16_t error = ExtractShort(data, len); + String* key_data; + ExtractOctets(data, len, dns_TKEY ? &key_data : nullptr); + ExtractOctets(data, len, nullptr); // Other data + + if ( dns_TKEY ) { + detail::TKEY_DATA tkey; + tkey.alg_name = new String(alg_name, int(alg_name_end - alg_name), true); + tkey.inception = inception; + tkey.expiration = expiration; + tkey.mode = mode; + tkey.error = error; + tkey.key = key_data; + analyzer->EnqueueConnEvent(dns_TKEY, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildTKEY_Val(&tkey)); + } + + return true; +} + bool DNS_Interpreter::ParseRR_RRSIG(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start) { if ( ! dns_RRSIG || msg->skip_event ) { @@ -1660,6 +1704,23 @@ RecordValPtr DNS_MsgInfo::BuildEDNS_COOKIE_Val(struct EDNS_COOKIE* opt) { return r; } +RecordValPtr DNS_MsgInfo::BuildTKEY_Val(struct TKEY_DATA* tkey) { + static auto dns_tkey = id::find_type("dns_tkey"); + auto r = make_intrusive(dns_tkey); + + r->Assign(0, query_name); + r->Assign(1, answer_type); + r->Assign(2, tkey->alg_name); + r->AssignTime(3, static_cast(tkey->inception)); + r->AssignTime(4, static_cast(tkey->expiration)); + r->Assign(5, static_cast(tkey->mode)); + r->Assign(6, static_cast(tkey->error)); + r->Assign(7, tkey->key); + r->Assign(8, is_query); + + return r; +} + RecordValPtr DNS_MsgInfo::BuildTSIG_Val(struct TSIG_DATA* tsig) { static auto dns_tsig_additional = id::find_type("dns_tsig_additional"); auto r = make_intrusive(dns_tsig_additional); diff --git a/src/analyzer/protocol/dns/DNS.h b/src/analyzer/protocol/dns/DNS.h index 3b361bc937..8ed6b92d45 100644 --- a/src/analyzer/protocol/dns/DNS.h +++ b/src/analyzer/protocol/dns/DNS.h @@ -191,6 +191,15 @@ struct EDNS_COOKIE { zeek::String* server_cookie; ///< cookie value sent by the server (0 or 8-32 bytes) }; +struct TKEY_DATA { + String* alg_name; + unsigned long inception; + unsigned long expiration; + unsigned short mode; + unsigned short error; + String* key; +}; + struct TSIG_DATA { String* alg_name; unsigned long time_s; @@ -278,6 +287,7 @@ public: RecordValPtr BuildEDNS_ECS_Val(struct EDNS_ECS*); RecordValPtr BuildEDNS_TCP_KA_Val(struct EDNS_TCP_KEEPALIVE*); RecordValPtr BuildEDNS_COOKIE_Val(struct EDNS_COOKIE*); + RecordValPtr BuildTKEY_Val(struct TKEY_DATA*); RecordValPtr BuildTSIG_Val(struct TSIG_DATA*); RecordValPtr BuildRRSIG_Val(struct RRSIG_DATA*); RecordValPtr BuildDNSKEY_Val(struct DNSKEY_DATA*); @@ -361,6 +371,7 @@ protected: bool ParseRR_TXT(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start); bool ParseRR_SPF(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start); bool ParseRR_CAA(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start); + bool ParseRR_TKEY(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start); bool ParseRR_TSIG(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start); bool ParseRR_RRSIG(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start); bool ParseRR_DNSKEY(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start); diff --git a/src/analyzer/protocol/dns/events.bif b/src/analyzer/protocol/dns/events.bif index 2389162e9c..df7140e56e 100644 --- a/src/analyzer/protocol/dns/events.bif +++ b/src/analyzer/protocol/dns/events.bif @@ -578,6 +578,23 @@ event dns_EDNS_tcp_keepalive%(c: connection, msg: dns_msg, opt: dns_edns_tcp_kee ## dns_skip_all_addl dns_skip_all_auth dns_skip_auth event dns_EDNS_cookie%(c: connection, msg: dns_msg, opt: dns_edns_cookie%); +## Generated for DNS replies of type *TKEY*. For replies with multiple answers, +## an individual event of the corresponding type is raised for each. +## +## See `Wikipedia `__ for more +## information about the DNS protocol. See `RFC2930 `__ +## for more information about TKEY. Zeek analyzes both UDP and TCP DNS sessions. +## +## c: The connection, which may be UDP or TCP depending on the type of the +## transport-layer session being analyzed. +## +## msg: The parsed DNS message header. +## +## ans: The parsed TKEY reply. +## +## .. zeek:see:: dns_TSIG_addl +event dns_TKEY%(c: connection, msg: dns_msg, ans: dns_tkey%); + ## Generated for DNS replies of type *TSIG*. For replies with multiple answers, ## an individual event of the corresponding type is raised for each. ## diff --git a/testing/btest/Baseline/scripts.base.protocols.dns.tkey/dns.log b/testing/btest/Baseline/scripts.base.protocols.dns.tkey/dns.log new file mode 100644 index 0000000000..448fe06fb1 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dns.tkey/dns.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dns +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto trans_id rtt query qclass qclass_name qtype qtype_name rcode rcode_name AA TC RD RA Z answers TTLs rejected +#types time string addr port addr port enum count interval string count string count string count string bool bool bool bool count vector[string] vector[interval] bool +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.106 50138 192.168.1.108 53 tcp 52640 - 1068-ms-7.309-2c6e448.7a9463b8-b109-11ed-26a3-080027f220e5 1 C_INTERNET 249 TKEY 0 NOERROR F F F F 0 - - F +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.dns.tkey/output b/testing/btest/Baseline/scripts.base.protocols.dns.tkey/output new file mode 100644 index 0000000000..5c94919754 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dns.tkey/output @@ -0,0 +1,21 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +TKEY +query, 1068-ms-7.309-2c6e448.7a9463b8-b109-11ed-26a3-080027f220e5 +qtype, 3 +alg_name, gss-tsig +inception, 1676938156.0 +expiration, 1677024556.0 +mode, 3 +rr_error, 0 +key_data size, 3073 +is_query, 1 +TKEY +query, 1068-ms-7.309-2c6e448.7a9463b8-b109-11ed-26a3-080027f220e5 +qtype, 1 +alg_name, gss-tsig +inception, 1676938118.0 +expiration, 1677024518.0 +mode, 3 +rr_error, 0 +key_data size, 185 +is_query, 0 diff --git a/testing/btest/Traces/dns/tkey.pcap b/testing/btest/Traces/dns/tkey.pcap new file mode 100644 index 0000000000000000000000000000000000000000..23fb131ad5bc1d3e765c5b3a673801d92af0f236 GIT binary patch literal 4618 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 output +# @TEST-EXEC: btest-diff dns.log +# @TEST-EXEC: TEST_DIFF_CANONIFIER= btest-diff output +# @TEST-EXEC: test ! -f weird.log + +@load base/protocols/dns + +redef dns_skip_all_addl = F; + +event dns_TKEY(c: connection, msg: dns_msg, ans: dns_tkey) + { + print "TKEY"; + print "query", ans$query; + print "qtype", ans$qtype; + print "alg_name", ans$alg_name; + print "inception", ans$inception; + print "expiration", ans$expiration; + print "mode", ans$mode; + print "rr_error", ans$rr_error; + print "key_data size", |ans$key_data|; + print "is_query", ans$is_query; + }