# DNS Log Desk > Hunt through one DNS query log for data exfiltration over DNS tunnelling, DGA malware and command-and-control beacons. The free check runs in the browser and uploads nothing: per registered domain the queries, unique subdomains, subdomain length, Shannon entropy, the label alphabet and the bytes the labels could carry, TXT/NULL share and NXDOMAIN; per host the DGA-looking domains, NXDOMAIN bursts, queries on a timer and volume outliers; DNS-over-HTTPS lookups and tunnelling tool signatures. The paid hunt gives a verdict, the malicious and suspicious findings, the hosts to look at, the flags that are expected noise, containment steps and follow-up hunts, and the page checks every domain, host, query count and byte estimate against the log. App: https://dnslog-desk.skillsafe.ai/ API tutorial: https://dnslog-desk.skillsafe.ai/api.html ## Formats read - Zeek dns.log as TSV (the #fields header decides the columns) or as JSON lines. - BIND query log lines ("client ... query: NAME IN TYPE"). A query log records questions, not answers, so there are no response codes. - dnsmasq and Pi-hole (pihole-FTL) logs: query[TYPE] lines, with the reply, cached, config and gravity-blocked lines attached to the query they answer. - Windows DNS Server debug logs (dnsserver.log): received questions are the queries, and the sent response with the same client and transaction id supplies the response code. - CSV or TSV from a SIEM search (Splunk, Sentinel, Elastic, QRadar and similar), columns found by name; Cisco Umbrella DNS reports with a header, and the header-less Umbrella log export. An identity column (a host or user name) is kept next to the address. - JSON arrays or JSON lines: Route 53 Resolver query logs, Suricata EVE dns events, Cloud DNS logs and Elasticsearch hits, with field names matched across exporters. - A plain list of names, one per line, optionally with a count. ## What the free check computes - Registered domain: two labels, or three under a short list of two-part country suffixes (co.uk, com.au and similar) and hosting suffixes where each customer gets a name (github.io, herokuapp.com and similar). Reverse look-ups group under in-addr.arpa and ip6.arpa. - Per domain: queries, unique names, unique subdomains, average and longest subdomain, longest label, average Shannon entropy in bits per character over unique subdomains of 8+ characters, the alphabet of the data-bearing labels (hex, base32, base64 or text) and the capacity: the unique labels' characters times 0.5 (hex), 0.625 (base32), 0.75 (base64) or 0.6 (text) bytes. The capacity is an upper bound on what could have left outward, not a measured transfer. - TXT, NULL and ANY queries count toward tunnelling only when they carry a subdomain and are not email authentication or service discovery names (_dmarc, _domainkey, _ldap._tcp and the like); TXT at a domain's apex is SPF and site verification. - T01 long subdomains (20+ queries over 50 characters, or 10+ unique subdomains averaging 30+); T02 average entropy of 3.5 or more over 10+ unique subdomains; T03 50+ unique subdomains making up 80%+ of a domain's queries; T04 TXT/NULL/ANY-heavy traffic (10+ or 20+ queries and half or more of the domain's queries); T05 60%+ of long labels hex, base32 or base64; T06 dnscat2 or dns2tcp names, or three or more NULL-record queries; T07 a host with 100+ TXT/NULL queries. - G01 five or more DGA-like registered domains from one host (a second-level label of 10+ characters with an entropy of 3.0+ and few vowels or many digits); G02 20+ NXDOMAIN answers across 10+ domains from one host; G03 a generated-looking domain that answered among many that did not. - B01 a beacon: 8+ queries from one host to one domain (repeats within 2 s merged) with a median gap of 5 s or more and a jitter (standard deviation over mean of the gaps) of 25% or less. - V01 a host with 200+ queries and five times the median of the others; medium only when it also reached a flagged domain. - D01 a public DNS-over-HTTPS endpoint looked up; D02 the Firefox canary domain use-application-dns.net. - Known reputation look-ups (Sophos, Team Cymru and similar), mail blocklists (Spamhaus, Barracuda and similar), reverse look-ups, CDN edge names and connectivity, clock and update checks are recognised as noisy by design; their flags are capped at low. Internal zones (.local, .lan, .corp, home.arpa) are capped the same way. - Data flags: X01 no queries read (nothing is sent to the paid hunt), X02 lines not read, X03 the log was cut, X04 no timestamps, X05 no client addresses, X06 no response codes, X07 a window under ten minutes. - Exports: the check as Markdown and every domain as CSV (spreadsheet formula characters are escaped). Logs that record blocks (Pi-hole, dnsmasq, Umbrella) get a Blocked column. - Since your last hunt: when you paste the next log of the same format, the page lists flagged domains that are new, still present (with query counts before and after) or gone, against your last saved hunt. Free, nothing sent. ## The paid hunt Input: {"task": "hunt", "facts": "", "note": "optional"}. The facts are the computed check plus up to 40 domains (every flagged one first, then the busiest) with up to four real sample names each, up to 25 hosts, the DGA, beacon and DoH tables and every flag; the raw log is never sent. Output: one JSON object with verdict (tunnelling_exfil, dga_malware, c2_beacon, suspicious_unconfirmed, dns_bypass, benign, insufficient_data), secondary_verdicts, severity, confidence, headline, findings (domain, kind, assessment, clients, queries, est_bytes, evidence, flags), hosts (client, role, domains, reading), false_positives (domain, flags, why), containment (priority, action, target), hunts (where, look_for, why), questions and summary. After a hunt, the domains it did not clear become a blocklist file: RPZ zone lines (BIND, Unbound, PowerDNS), dnsmasq / Pi-hole config, an Adblock list (AdGuard Home, Pi-hole v6) or a hosts file; internal zones, known lookup services and DoH endpoints are left out. The page checks that every domain and host is in the log, that a host said to have queried a domain did, that query counts match, that no byte estimate exceeds the labels' capacity, that every high flag is addressed, that a malicious verdict has a finding behind it and a severity that fits, that "benign" is not given over an unexplained high flag, and that a tunnelling tool signature is not dismissed as expected. Runs on the gpt-terra model alias and costs credits; the free check does not. ## What it does not do It reads one log. It does not see answer payloads beyond what the log records, EDR, proxy or firewall data, passive DNS or threat intelligence, so it cannot say which process sent a query or what came back in a TXT answer; those are the follow-up hunts it proposes. DNS sent over HTTPS never reaches a DNS server's log. ## Sources - Source skill: analyzing-dns-logs-for-exfiltration by mahipal (mukul975/anthropic-cybersecurity-skills), Apache-2.0 - https://skillsafe.ai/skill/@mukul975/analyzing-dns-logs-for-exfiltration and https://github.com/mukul975/anthropic-cybersecurity-skills (skills/analyzing-dns-logs-for-exfiltration) - MITRE ATT&CK, Application Layer Protocol: DNS (T1071.004): https://attack.mitre.org/techniques/T1071/004/ - MITRE ATT&CK, Exfiltration Over Unencrypted Non-C2 Protocol (T1048.003): https://attack.mitre.org/techniques/T1048/003/ - MITRE ATT&CK, Dynamic Resolution: Domain Generation Algorithms (T1568.002): https://attack.mitre.org/techniques/T1568/002/ - Zeek dns.log: https://docs.zeek.org/en/master/logs/dns.html - DNS message format and record types, RFC 1035: https://www.rfc-editor.org/rfc/rfc1035 - DNS over HTTPS, RFC 8484: https://www.rfc-editor.org/rfc/rfc8484 - Public Suffix List: https://publicsuffix.org/