diff --git a/scripts/policy/protocols/dns/log-original-query-case.zeek b/scripts/policy/protocols/dns/log-original-query-case.zeek new file mode 100644 index 0000000000..a6213f4364 --- /dev/null +++ b/scripts/policy/protocols/dns/log-original-query-case.zeek @@ -0,0 +1,19 @@ +##! This script adds the query with its original letter casing +##! to the DNS log. + +@load base/protocols/dns/main + +module DNS; + +export { + redef record Info += { + ## Query with original letter casing + original_query: string &log &optional; + }; +} + +event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count, original_query: string) + { + if ( ! c$dns?$original_query ) + c$dns$original_query = original_query; + } \ No newline at end of file diff --git a/scripts/test-all-policy.zeek b/scripts/test-all-policy.zeek index 8a2721dc14..23dd9bc483 100644 --- a/scripts/test-all-policy.zeek +++ b/scripts/test-all-policy.zeek @@ -73,6 +73,7 @@ @load protocols/dhcp/sub-opts.zeek @load protocols/dns/auth-addl.zeek @load protocols/dns/detect-external-names.zeek +@load protocols/dns/log-original-query-case.zeek @load protocols/ftp/detect-bruteforcing.zeek @load protocols/ftp/detect.zeek @load protocols/ftp/software.zeek diff --git a/src/analyzer/protocol/dns/DNS.cc b/src/analyzer/protocol/dns/DNS.cc index e505c61b81..e5f1ce1c11 100644 --- a/src/analyzer/protocol/dns/DNS.cc +++ b/src/analyzer/protocol/dns/DNS.cc @@ -171,7 +171,7 @@ bool DNS_Interpreter::ParseQuestion(DNS_MsgInfo* msg, u_char name[513]; int name_len = sizeof(name) - 1; - u_char* name_end = ExtractName(data, len, name, name_len, msg_start); + u_char* name_end = ExtractName(data, len, name, name_len, msg_start, false); if ( ! name_end ) return false; @@ -196,9 +196,16 @@ bool DNS_Interpreter::ParseQuestion(DNS_MsgInfo* msg, if ( dns_event && ! msg->skip_event ) { - BroString* question_name = - new BroString(name, name_end - name, true); - SendReplyOrRejectEvent(msg, dns_event, data, len, question_name); + BroString* original_name = new BroString(name, name_end - name, true); + + // Downcase the Name to normalize it + for ( u_char* np = name; np < name_end; ++np ) + if ( isupper(*np) ) + *np = tolower(*np); + BroString* question_name = + new BroString(name, name_end - name, true); + + SendReplyOrRejectEvent(msg, dns_event, data, len, question_name, original_name); } else { @@ -353,9 +360,15 @@ bool DNS_Interpreter::ParseAnswer(DNS_MsgInfo* msg, return status; } +u_char* DNS_Interpreter::ExtractName(const u_char*& data, int& len, + u_char* name, int name_len, + const u_char* msg_start) { + return DNS_Interpreter::ExtractName(data, len, name, name_len, msg_start, true); +} + u_char* DNS_Interpreter::ExtractName(const u_char*& data, int& len, u_char* name, int name_len, - const u_char* msg_start) + const u_char* msg_start, bool downcase) { u_char* name_start = name; @@ -375,9 +388,10 @@ u_char* DNS_Interpreter::ExtractName(const u_char*& data, int& len, } // Convert labels to lower case for consistency. - for ( u_char* np = name_start; np < name; ++np ) - if ( isupper(*np) ) - *np = tolower(*np); + if (downcase) + for ( u_char* np = name_start; np < name; ++np ) + if ( isupper(*np) ) + *np = tolower(*np); return name; } @@ -1388,7 +1402,8 @@ bool DNS_Interpreter::ParseRR_CAA(DNS_MsgInfo* msg, void DNS_Interpreter::SendReplyOrRejectEvent(DNS_MsgInfo* msg, EventHandlerPtr event, const u_char*& data, int& len, - BroString* question_name) + BroString* question_name, + BroString* original_name) { RR_Type qtype = RR_Type(ExtractShort(data, len)); int qclass = ExtractShort(data, len); @@ -1400,7 +1415,8 @@ void DNS_Interpreter::SendReplyOrRejectEvent(DNS_MsgInfo* msg, msg->BuildHdrVal(), make_intrusive(question_name), val_mgr->Count(qtype), - val_mgr->Count(qclass) + val_mgr->Count(qclass), + make_intrusive(original_name) ); } diff --git a/src/analyzer/protocol/dns/DNS.h b/src/analyzer/protocol/dns/DNS.h index ff6ae42de8..8af36d85dd 100644 --- a/src/analyzer/protocol/dns/DNS.h +++ b/src/analyzer/protocol/dns/DNS.h @@ -240,9 +240,12 @@ protected: bool ParseAnswer(DNS_MsgInfo* msg, const u_char*& data, int& len, const u_char* start); + u_char* ExtractName(const u_char*& data, int& len, + u_char* label, int label_len, + const u_char* msg_start); u_char* ExtractName(const u_char*& data, int& len, u_char* label, int label_len, - const u_char* msg_start); + const u_char* msg_start, bool downcase); bool ExtractLabel(const u_char*& data, int& len, u_char*& label, int& label_len, const u_char* msg_start); @@ -308,7 +311,7 @@ protected: const u_char* msg_start); void SendReplyOrRejectEvent(DNS_MsgInfo* msg, EventHandlerPtr event, const u_char*& data, int& len, - BroString* question_name); + BroString* question_name, BroString* original_name); analyzer::Analyzer* analyzer; bool first_message; diff --git a/src/analyzer/protocol/dns/events.bif b/src/analyzer/protocol/dns/events.bif index 35e9ffa0fd..2a33611c86 100644 --- a/src/analyzer/protocol/dns/events.bif +++ b/src/analyzer/protocol/dns/events.bif @@ -34,12 +34,14 @@ event dns_message%(c: connection, is_orig: bool, msg: dns_msg, len: count%); ## ## msg: The parsed DNS message header. ## -## query: The queried name. +## query: The queried name (normalized to all lowercase). ## ## qtype: The queried resource record type. ## ## qclass: The queried resource record class. ## +## original_query: The queried name, with the original case kept intact +## ## .. zeek:see:: dns_AAAA_reply dns_A_reply dns_CNAME_reply dns_EDNS_addl ## dns_HINFO_reply dns_MX_reply dns_NS_reply dns_PTR_reply dns_SOA_reply ## dns_SRV_reply dns_TSIG_addl dns_TXT_reply dns_SPF_reply dns_WKS_reply dns_end @@ -47,6 +49,7 @@ event dns_message%(c: connection, is_orig: bool, msg: dns_msg, len: count%); ## dns_mapping_unverified dns_mapping_valid dns_message dns_query_reply ## dns_rejected dns_max_queries dns_session_timeout dns_skip_addl ## dns_skip_all_addl dns_skip_all_auth dns_skip_auth +event dns_request%(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count, original_query: string%); event dns_request%(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count%); ## Generated for DNS replies that reject a query. This event is raised if a DNS @@ -63,12 +66,14 @@ event dns_request%(c: connection, msg: dns_msg, query: string, qtype: count, qcl ## ## msg: The parsed DNS message header. ## -## query: The queried name. +## query: The queried name (normalized to all lowercase). ## ## qtype: The queried resource record type. ## ## qclass: The queried resource record class. ## +## original_query: The queried name, with the original case kept intact +## ## .. zeek:see:: dns_AAAA_reply dns_A_reply dns_CNAME_reply dns_EDNS_addl ## dns_HINFO_reply dns_MX_reply dns_NS_reply dns_PTR_reply dns_SOA_reply ## dns_SRV_reply dns_TSIG_addl dns_TXT_reply dns_SPF_reply dns_WKS_reply dns_end @@ -76,6 +81,7 @@ event dns_request%(c: connection, msg: dns_msg, query: string, qtype: count, qcl ## dns_mapping_unverified dns_mapping_valid dns_message dns_query_reply ## dns_request dns_max_queries dns_session_timeout dns_skip_addl ## dns_skip_all_addl dns_skip_all_auth dns_skip_auth +event dns_rejected%(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count, original_query: string%); event dns_rejected%(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count%); ## Generated for each entry in the Question section of a DNS reply. diff --git a/testing/btest/Baseline/scripts.policy.misc.dump-events/all-events.log b/testing/btest/Baseline/scripts.policy.misc.dump-events/all-events.log index 4637f34ce5..8aeda1e24e 100644 --- a/testing/btest/Baseline/scripts.policy.misc.dump-events/all-events.log +++ b/testing/btest/Baseline/scripts.policy.misc.dump-events/all-events.log @@ -19,6 +19,7 @@ [2] query: string = mail.patriots.in [3] qtype: count = 1 [4] qclass: count = 1 + [5] original_query: string = mail.patriots.in 1254722767.492060 protocol_confirmation [0] c: connection = [id=[orig_h=10.10.1.4, orig_p=56166/udp, resp_h=10.10.1.1, resp_p=53/udp], orig=[size=34, state=1, num_pkts=0, num_bytes_ip=0, flow_label=0, l2_addr=00:e0:1c:3c:17:c2], resp=[size=0, state=0, num_pkts=0, num_bytes_ip=0, flow_label=0, l2_addr=00:1f:33:d9:81:60], start_time=1254722767.49206, duration=0 secs, service={\x0a\x0a}, history=D, uid=CHhAvVGS1DHFjwGM9, tunnel=, vlan=, inner_vlan=, successful=F, dpd=, dpd_state=, conn=, extract_orig=F, extract_resp=F, thresholds=, dce_rpc=, dce_rpc_state=, dce_rpc_backing=, dhcp=, dnp3=, dns=[ts=1254722767.49206, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=10.10.1.4, orig_p=56166/udp, resp_h=10.10.1.1, resp_p=53/udp], proto=udp, trans_id=31062, rtt=, query=mail.patriots.in, qclass=1, qclass_name=C_INTERNET, qtype=1, qtype_name=A, rcode=, rcode_name=, AA=F, TC=F, RD=T, RA=F, Z=0, answers=, TTLs=, rejected=F, total_answers=, total_replies=, saw_query=F, saw_reply=F], dns_state=[pending_query=[ts=1254722767.49206, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=10.10.1.4, orig_p=56166/udp, resp_h=10.10.1.1, resp_p=53/udp], proto=udp, trans_id=31062, rtt=, query=mail.patriots.in, qclass=1, qclass_name=C_INTERNET, qtype=1, qtype_name=A, rcode=, rcode_name=, AA=F, TC=F, RD=T, RA=F, Z=0, answers=, TTLs=, rejected=F, total_answers=, total_replies=, saw_query=F, saw_reply=F], pending_queries=, pending_replies=], ftp=, ftp_data_reuse=F, ssl=, http=, http_state=, irc=, krb=, modbus=, mysql=, ntlm=, ntp=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smb_state=, smtp=, smtp_state=, socks=, ssh=, syslog=] diff --git a/testing/btest/Baseline/scripts.policy.protocols.dns.original_case/dns.log b/testing/btest/Baseline/scripts.policy.protocols.dns.original_case/dns.log new file mode 100644 index 0000000000..89e868919a --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.dns.original_case/dns.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dns +#open 2020-06-17-14-47-26 +#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 original_query +#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 string +1592402712.248901 CHhAvVGS1DHFjwGM9 192.168.3.138 63374 192.168.3.1 53 udp 20877 - us.v27.distributed.net 1 C_INTERNET 1 A - - F F T F 2 - - F Us.V27.DiStRiBuTeD.NET +#close 2020-06-17-14-47-26 diff --git a/testing/btest/Traces/dns_original_case.pcap b/testing/btest/Traces/dns_original_case.pcap new file mode 100644 index 0000000000..b9348e051b Binary files /dev/null and b/testing/btest/Traces/dns_original_case.pcap differ diff --git a/testing/btest/scripts/policy/protocols/dns/original_case.zeek b/testing/btest/scripts/policy/protocols/dns/original_case.zeek new file mode 100644 index 0000000000..c3b1d07388 --- /dev/null +++ b/testing/btest/scripts/policy/protocols/dns/original_case.zeek @@ -0,0 +1,3 @@ +# @TEST-EXEC: zeek -r $TRACES/dns_original_case.pcap %INPUT +# @TEST-EXEC: btest-diff dns.log +@load protocols/dns/log-original-query-case