Get Elastic SIEM Access on hunt-forward.com โ€” 7-day free trial, then $5/month

How to use this lab: Read the story to understand the attack. Then follow the Hunt section to find it yourself in Elastic SIEM. Document your findings in your Hunt Notebook as you go โ€” you'll use them to build your GitHub portfolio at the end.

None

๐Ÿ“– Part 1: The Scenario

Wednesday, 9:14 AM. Boston.

Alex Chen is four months in. Dana drops a ticket on his desk without sitting down โ€” "Anomaly flag on DNS. Probably nothing. Take a look."

He opens the DNS logs. The firm handles M&A cases worth hundreds of millions. Their firewall blocks everything except HTTPS and DNS. The partners are proud of it.

10:00:01  WORKSTATION-07  A  d2h5aXMtdGhpcy1hLWxvbmctc3Vic2RvbWFpbg.exfil-c2.net
10:00:03  WORKSTATION-07  A  Y2xpZW50LWRhdGEtY2h1bmstMQ.exfil-c2.net
10:00:05  WORKSTATION-07  A  Y2xpZW50LWRhdGEtY2h1bmstMg.exfil-c2.net
10:00:07  WORKSTATION-07  A  Y2xpZW50LWRhdGEtY2h1bmstMw.exfil-c2.net

He decodes the first subdomain in CyberChef. why-is-this-a-long-subdomain. The next: client-data-chunk-1.

His hands go cold.

DNS queries carry names, not data โ€” but nobody filters them because blocking DNS breaks the internet. Attackers encode data inside subdomains and fire them at an attacker-controlled nameserver that logs every query. WORKSTATION-07 has been doing this for six days at 200+ queries per hour. The attacker's domain, exfil-c2.net, is twelve days old. The Veridian acquisition โ€” $2.4 billion, closing Friday โ€” belongs to the paralegal on that machine.

The firewall blocked everything. The data left anyway.

Now it's your turn to find it.

How DNS Tunneling Works โ€” Simply Explained

Before you hunt, you need to understand what you're looking at. Let's build this up from scratch.

Step 1: What is DNS?

Every time you type google.com into a browser, your computer asks a question behind the scenes: "What is the IP address for google.com?" A DNS server answers: "It's 142.250.80.46." Your browser then connects to that IP.

That question-and-answer is a DNS query. It happens thousands of times a day on any corporate network โ€” every website visit, every software update, every cloud service call triggers one. It's so fundamental to how the internet works that blocking it would break everything. Firewalls almost never inspect or block DNS.

Attackers know this.

Step 2: What is a subdomain?

A domain like google.com can have subdomains in front of it โ€” mail.google.com, drive.google.com, docs.google.com. The part before the main domain is the subdomain. Your computer asks: "What is the IP for mail.google.com?" โ€” and the DNS server answers.

Here's the key insight: the subdomain is just text. It can be anything.

Step 3: How the tunnel works

An attacker installs malware on a victim's machine. Instead of sending stolen data directly over the network โ€” which the firewall would see and might block โ€” the malware breaks the data into small chunks, encodes each chunk into a string of characters, and uses each chunk as the subdomain of a DNS query:

Normal DNS query:
  "What is mail.google.com?"
  โ†’ Asking for an email server. Makes sense.
Tunnel DNS query:
  "What is Y2xpZW50LWRhdGEtY2h1bmstMQ.exfil-c2.net?"
  โ†’ That random-looking subdomain? Decoded: "client-data-chunk-1"
  โ†’ It's not asking for an IP. It's sending stolen data disguised as a question.

The attacker owns exfil-c2.net. Their nameserver receives every query and logs the subdomain โ€” collecting the stolen data one chunk at a time. The malware doesn't care what IP address comes back. The response is irrelevant. The query itself is the transmission.

Step 4: Why the firewall misses it

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  What the firewall sees:   DNS traffic โ†’ ALLOWED (DNS always passes)         โ”‚
โ”‚  What's actually in it:    Stolen legal documents, encoded as Base64         โ”‚
โ”‚                                                                               โ”‚
โ”‚  The firewall isn't broken. It's doing exactly what it was configured to do. โ”‚
โ”‚  DNS tunneling abuses trust โ€” not a vulnerability.                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Think of it like this: imagine a building where guards check every package that leaves โ€” but they always wave through anyone just asking a question. A thief learns to hide documents inside the questions themselves. "Excuse me, does aGVyZS1pcy10aGUtZmlsZS1jb250ZW50 work here?" The guard hears a question and waves them through. The thief has just walked out with your data.

Step 5: Why it leaves tracks

DNS tunnelling can't hide one thing: its behaviour. Because the malware must send hundreds of queries to transmit a full file, and because each query carries encoded data instead of a real hostname, the pattern is statistically abnormal:

  • Real hostnames are short and readable โ€” mail, cdn, api, s3
  • Encoded data subdomains are long and random-looking โ€” Y2xpZW50LWRhdGEtY2h1bmstMQ
  • Real apps query many different domains throughout the day
  • A tunnel queries one parent domain over and over, hundreds of times per hour

None of that is a signature. No antivirus rule catches it. But an analyst with the right ES|QL query can spot it in seconds.

That's exactly what you're about to do.

๐ŸŽฏ Part 2: Your Mission

By the end of this lab you will have:

  • โœ… Identified anomalously long DNS subdomains indicating data encoding
  • โœ… Detected high-frequency queries to a single suspicious parent domain
  • โœ… Measured subdomain entropy to distinguish encoded data from legitimate hostnames
  • โœ… Correlated the tunnel to a specific host and user
  • โœ… Estimated the volume of data exfiltrated through the tunnel
  • โœ… Documented your full investigation in your Hunt Notebook

๐Ÿ”ง Part 3: Lab Setup

You'll need a Hunt Forward account for this lab. Your Elastic SIEM environment has the dns-tunnel-lab-logs dataset pre-loaded โ€” DNS query logs spanning a full week, with tunnelling activity buried in legitimate corporate DNS traffic.

๐Ÿ‘‰ Sign up at huntforward.com โ€” 7-day free trial, then $5/month

Once you're in Click Here or :

  1. Open Kibana โ†’ hamburger menu (top left) โ†’ Discover
  2. Select the index dns-tunnel-lab-logs
  3. Set the time range to April 20โ€“26, 2024 โ€” the tunnel runs across multiple days

You should see DNS query logs with fields for source host, query name, query type, response code, and timestamps. That's your crime scene.

A Quick Word on ES|QL

Throughout this lab we use ES|QL โ€” Elasticsearch Query Language โ€” instead of clicking through visualisation menus. ES|QL lets you filter, group, count, and calculate statistics on your logs in a single query, directly in Discover.

Every ES|QL query starts with FROM and pipes data through commands using |. Think of each | as "then do this next thing to the results."

To run ES|QL in Kibana:

  1. Select ES|QL on the top right corner next to share button in discover.
  2. Paste the query and press Run (โ–ถ)

๐Ÿ” Part 4: The Hunt

Hunt 1 โ€” Find Abnormally Long Subdomains

The first and most reliable DNS tunnelling signal is subdomain length. Legitimate hostnames are designed to be human-readable โ€” mail.hargrove-lyle.com, cdn.slack.com, s3.us-east-1.amazonaws.com. Even the longest legitimate subdomains rarely exceed 30โ€“35 characters. Base64-encoded data chunks routinely hit 50โ€“63 characters (the DNS spec maximum per label).

The dataset already has dns.question.subdomain as a pre-parsed field โ€” the part of the query name before the registered domain. We use EVAL to measure its length, then aggregate by host and parent domain to find anomalies:

esql

What each line does:

  • WHERE dns.question.type == "A" โ€” only A record lookups, the most common tunnel carrier
  • AND dns.question.subdomain IS NOT NULL โ€” skip queries with no subdomain (bare domain lookups like google.com)
  • EVAL subdomain_length = LENGTH(dns.question.subdomain) โ€” measures the character count of the subdomain portion only
  • STATS AVG/MAX/COUNT BY source.ip, host.name, registered_domain โ€” groups by attacking host and target domain, producing one row per combination with its length statistics
  • WHERE avg_subdomain_len > 30 โ€” filters to domains where the average subdomain is suspiciously long. Legitimate domains cluster at 8โ€“15 characters
  • SORT avg_subdomain_len DESC โ€” worst offender at the top

What you're looking for: One registered domain where the average subdomain length sits far above everything else. In the dataset, exfil-c2.net from WORKSTATION-07 will average over 50 characters โ€” more than 4x the next highest legitimate entry.

๐Ÿ“ Hunt Notebook checkpoint: Record the source IP, hostname, registered domain, average subdomain length, maximum subdomain length, and query count. Note the gap between the top result and all others โ€” that delta is your confidence level.

โœ… Anomalous subdomain lengths confirmed. One host is generating DNS queries with subdomains averaging 54 characters โ€” 4x the legitimate baseline.

๐Ÿ Milestone 1 of 4 โ€” Long Subdomain Anomaly Identified Open your Hunt Notebook and paste this template. Fill in your findings.

## ๐Ÿ“ก Milestone 1: Abnormal Subdomain Length Detected

**Date of Hunt:** [today's date]
**Lab:** Hunt Forward #003 โ€” DNS Tunneling Detection
**Analyst:** [your name]

### Finding
DNS queries from a single host show average subdomain lengths far exceeding
the legitimate baseline, consistent with Base64-encoded data exfiltration.

| Field                | Value          |
|----------------------|----------------|
| Source IP            | [your finding] |
| Parent domain        | [your finding] |
| Avg subdomain length | [your finding] |
| Max subdomain length | [your finding] |
| Total query count    | [your finding] |
| Legitimate baseline  | 12โ€“15 chars    |

### ES|QL Query Used
```esql
FROM dns-tunnel-lab-logs
| WHERE dns.question.type == "A"
  AND dns.question.subdomain IS NOT NULL
| EVAL subdomain_length = LENGTH(dns.question.subdomain)
| WHERE subdomain_length > 0
| STATS
    avg_subdomain_len = AVG(subdomain_length),
    max_subdomain_len = MAX(subdomain_length),
    query_count       = COUNT()
    BY source.ip, host.name, dns.question.registered_domain
| WHERE avg_subdomain_len > 30
| SORT avg_subdomain_len DESC
| LIMIT 20

Notes

Legitimate DNS subdomains average 12โ€“15 characters. Values above 30 indicate probable encoding. Values above 50 are near-certain data tunnelling.

Severity: High Confidence: High

Hunt 2 โ€” Detect High-Frequency Queries to a Single Domain

Length analysis finds what looks suspicious. Frequency analysis finds how much data is moving. A DNS tunnel exfiltrating a large file must send hundreds or thousands of queries โ€” one chunk per query. That sustained high-rate querying of a single domain is statistically unmistakable.

FROM dns-tunnel-lab-logs
| WHERE dns.question.type == "A"
| EVAL hour_bucket = DATE_TRUNC(1 hour, @timestamp)
| STATS
    queries_per_hour  = COUNT(),
    unique_subdomains = COUNT_DISTINCT(dns.question.name),
    first_seen        = MIN(@timestamp),
    last_seen         = MAX(@timestamp),
    active_days       = COUNT_DISTINCT(DATE_TRUNC(1 day, @timestamp))
    BY source.ip, dns.question.registered_domain, hour_bucket
| WHERE queries_per_hour > 50
| SORT queries_per_hour DESC
| LIMIT 20

What each line does:

  • EVAL hour_bucket = DATE_TRUNC(1 hour, @timestamp) โ€” groups time into 1-hour windows so we can measure query rate per hour
  • STATS COUNT(), COUNT_DISTINCT(dns.question.name) โ€” total queries AND unique subdomains per window. In a tunnel, every query has a unique subdomain (a new data chunk). unique_subdomains โ‰ˆ queries_per_hour is a strong tunnel indicator
  • COUNT_DISTINCT(DATE_TRUNC(1 day, @timestamp)) โ€” how many calendar days this pattern has been running โ€” dwell time
  • WHERE queries_per_hour > 50 โ€” 50+ A record queries per hour to the same registered domain is well above normal application behaviour
  • SORT queries_per_hour DESC โ€” peak tunnel activity windows rise to the top

What you're looking for: One source IP querying one parent domain at 100+ queries per hour, with unique_subdomains nearly equal to queries_per_hour (meaning every query carries different data), across multiple days.

In the dataset, WORKSTATION-07 has been querying exfil-c2.net at over 200 queries per hour, sustained across 6 days. Every subdomain is unique.

๐Ÿ“ Hunt Notebook checkpoint: Record the peak queries-per-hour, the ratio of unique subdomains to total queries (should be near 1:1 for a tunnel), first_seen, last_seen, and active_days. Calculate total query count across the full tunnel duration.

๐Ÿ Milestone 2 of 4 โ€” High-Frequency Tunnel Activity Confirmed

## ๐ŸŒŠ Milestone 2: High-Frequency DNS Query Pattern

### Frequency Analysis
| Field                     | Value          |
|---------------------------|----------------|
| Source IP                 | [your finding] |
| Parent domain             | [your finding] |
| Peak queries/hour         | [your finding] |
| Unique subdomains / total | [your finding] |
| First seen                | [timestamp]    |
| Last seen                 | [timestamp]    |
| Active days               | [your finding] |

### ES|QL Query Used
```esql
FROM dns-tunnel-lab-logs
| WHERE dns.question.type == "A"
| EVAL hour_bucket = DATE_TRUNC(1 hour, @timestamp)
| STATS
    queries_per_hour  = COUNT(),
    unique_subdomains = COUNT_DISTINCT(dns.question.name),
    first_seen        = MIN(@timestamp),
    last_seen         = MAX(@timestamp),
    active_days       = COUNT_DISTINCT(DATE_TRUNC(1 day, @timestamp))
    BY source.ip, dns.question.registered_domain, hour_bucket
| WHERE queries_per_hour > 50
| SORT queries_per_hour DESC
| LIMIT 20

Conclusion

Unique subdomain ratio near 1:1 confirms every query carries a different data chunk. [X] active days of tunnelling represents significant dwell time and substantial data volume.

Severity: Critical Confidence: High

Hunt 3 โ€” Correlate to Host and User, Estimate Exfiltration Volume

Knowing the technique and the domain isn't enough. You need to know who and how much. This hunt pins the tunnel to a specific workstation and user, then estimates total bytes exfiltrated by treating each subdomain as a data chunk

FROM dns-tunnel-lab-logs
| WHERE dns.question.registered_domain == "exfil-c2.net"
  AND dns.question.type == "A"
| EVAL subdomain        = LEFT(dns.question.name,
                            LENGTH(dns.question.name) - LENGTH("exfil-c2.net") - 1)
| EVAL chunk_size_bytes = LENGTH(subdomain) * 6 / 8
| STATS
    total_queries         = COUNT(),
    unique_chunks         = COUNT_DISTINCT(dns.question.name),
    estimated_bytes_exfil = SUM(chunk_size_bytes),
    first_query           = MIN(@timestamp),
    last_query            = MAX(@timestamp)
    BY source.ip, host.name, user.name
| EVAL estimated_kb = ROUND(estimated_bytes_exfil / 1024, 1)
| EVAL estimated_mb = ROUND(estimated_bytes_exfil / 1048576, 2)
| SORT estimated_bytes_exfil DESC

What each line does:

  • WHERE dns.question.registered_domain == "exfil-c2.net" โ€” scoped to the confirmed malicious domain from Hunt 1 and 2
  • EVAL chunk_size_bytes = LENGTH(subdomain) * 6 / 8 โ€” Base64 encodes 6 bits per character, so each character represents 6/8 of a byte. This formula estimates the raw bytes encoded in each subdomain label
  • STATS SUM(chunk_size_bytes) โ€” sums estimated bytes across all queries to get total exfiltration volume
  • BY source.ip, host.name, user.name โ€” groups by all three identity fields so we see exactly who this is
  • EVAL estimated_kb/mb โ€” converts raw bytes to human-readable KB and MB using ROUND for clean output
  • SORT estimated_bytes_exfil DESC โ€” largest exfiltration volume first

What you're looking for: A single row tying the tunnel to one workstation and one user, with an estimated exfiltration volume in the megabytes. In the dataset, WORKSTATION-07 / ryan.porter will show an estimated 4โ€“6MB exfiltrated โ€” consistent with multiple case documents.

๐Ÿ“ Hunt Notebook checkpoint: Record the hostname, username, total query count, unique chunk count, estimated KB and MB exfiltrated, and the full tunnel date range. This is your user attribution and scope estimate.

โœ… Tunnel attributed. ryan.porter on WORKSTATION-07 exfiltrated an estimated [X] MB via DNS tunnel to exfil-c2.net over 6 days

๐Ÿ Milestone 3 of 4 โ€” Host, User, and Volume Identified

## ๐ŸŽฏ Milestone 3: Attribution and Exfiltration Scope

### Attribution
| Field                | Value          |
|----------------------|----------------|
| Source IP            | [your finding] |
| Hostname             | [your finding] |
| Username             | [your finding] |

### Exfiltration Volume Estimate
| Field                | Value          |
|----------------------|----------------|
| Total queries        | [your finding] |
| Unique chunks        | [your finding] |
| Estimated bytes      | [your finding] |
| Estimated KB         | [your finding] |
| Estimated MB         | [your finding] |
| Tunnel start         | [timestamp]    |
| Tunnel end           | [timestamp]    |
| Duration (days)      | [your finding] |

### ES|QL Query Used
```esql
FROM dns-tunnel-lab-logs
| WHERE dns.question.registered_domain == "exfil-c2.net"
  AND dns.question.type == "A"
| EVAL subdomain        = LEFT(dns.question.name,
                            LENGTH(dns.question.name) - LENGTH("exfil-c2.net") - 1)
| EVAL chunk_size_bytes = LENGTH(subdomain) * 6 / 8
| STATS
    total_queries         = COUNT(),
    unique_chunks         = COUNT_DISTINCT(dns.question.name),
    estimated_bytes_exfil = SUM(chunk_size_bytes),
    first_query           = MIN(@timestamp),
    last_query            = MAX(@timestamp)
    BY source.ip, host.name, user.name
| EVAL estimated_kb = ROUND(estimated_bytes_exfil / 1024, 1)
| EVAL estimated_mb = ROUND(estimated_bytes_exfil / 1048576, 2)
| SORT estimated_bytes_exfil DESC

Conclusion

[X]MB of data was exfiltrated across [N] days. At this volume, multiple documents could have been transmitted. Given the timing relative to the Veridian acquisition opening, case documents must be assumed compromised.

Severity: Critical Confidence: High

Hunt 4 โ€” Scope the Blast Radius: Are Other Hosts Tunnelling?

One infected machine is an incident. Multiple infected machines is a breach. Before issuing any containment order, you need to know whether WORKSTATION-07 is the only source or if the tunnel tooling has spread.

FROM dns-tunnel-lab-logs
| WHERE dns.question.type == "A"
  AND dns.question.subdomain IS NOT NULL
| EVAL subdomain_length = LENGTH(dns.question.subdomain)
| STATS
    avg_subdomain_len = AVG(subdomain_length),
    query_count       = COUNT(),
    unique_domains    = COUNT_DISTINCT(dns.question.registered_domain)
    BY source.ip, host.name
| EVAL tunnel_score = CASE(
    avg_subdomain_len > 50 AND query_count > 500,  "HIGH โ€” likely tunnel",
    avg_subdomain_len > 35 AND query_count > 100,  "MEDIUM โ€” investigate",
    avg_subdomain_len > 25,                         "LOW โ€” monitor",
    "NORMAL"
  )
| WHERE tunnel_score != "NORMAL"
| SORT avg_subdomain_len DESC

What each line does:

  • This query runs the subdomain length analysis across every host simultaneously โ€” not just the known bad one
  • EVAL tunnel_score = CASE(...) โ€” scores each host with a risk label based on combined subdomain length AND query volume. A host needs both long subdomains AND high volume to score HIGH โ€” this eliminates false positives from hosts that occasionally query long-named but legitimate services
  • WHERE tunnel_score != "NORMAL" โ€” filters out all clean hosts so results focus only on anomalies
  • SORT avg_subdomain_len DESC โ€” highest-risk hosts first

What you're looking for: Ideally, only WORKSTATION-07 appears with a HIGH score. Any other host appearing as MEDIUM or HIGH demands immediate investigation โ€” the tunnel tool may have spread via lateral movement.

In the dataset, WORKSTATION-07 scores HIGH. All other hosts score NORMAL. The blast radius is contained to one machine.

๐Ÿ“ Hunt Notebook checkpoint: Record the tunnel_score for every host that appears in results. Confirm whether any hosts other than WORKSTATION-07 show elevated scores. Document the conclusion: contained or spreading.

๐Ÿ Milestone 4 of 4 โ€” Blast Radius Scoped

## ๐Ÿ” Milestone 4: Blast Radius Assessment

### Host Risk Scores
| Hostname          | Source IP      | Avg Subdomain Len | Query Count | Tunnel Score    |
|-------------------|----------------|-------------------|-------------|-----------------|
| [your finding]    | [your finding] | [your finding]    | [your finding]| [your finding]|
| [other hosts]     | ...            | ...               | ...         | NORMAL          |

### ES|QL Query Used
```esql
FROM dns-tunnel-lab-logs
| WHERE dns.question.type == "A"
  AND dns.question.subdomain IS NOT NULL
| EVAL subdomain_length = LENGTH(dns.question.subdomain)
| STATS
    avg_subdomain_len = AVG(subdomain_length),
    query_count       = COUNT(),
    unique_domains    = COUNT_DISTINCT(dns.question.registered_domain)
    BY source.ip, host.name
| EVAL tunnel_score = CASE(
    avg_subdomain_len > 50 AND query_count > 500,  "HIGH โ€” likely tunnel",
    avg_subdomain_len > 35 AND query_count > 100,  "MEDIUM โ€” investigate",
    avg_subdomain_len > 25,                         "LOW โ€” monitor",
    "NORMAL"
  )
| WHERE tunnel_score != "NORMAL"
| SORT avg_subdomain_len DESC

Conclusion

Blast radius is [contained to WORKSTATION-07 / SPREADING โ€” X hosts affected]. [Proceed with single-host containment / Escalate to full incident response.]

Severity: [Critical โ€” contained / Critical โ€” spreading] Confidence: High

Recommended Immediate Actions

  • Isolate WORKSTATION-07 from the network
  • Block exfil-c2.net at DNS resolver and perimeter firewall
  • Preserve forensic image of WORKSTATION-07
  • Notify Hargrove & Lyle partners โ€” Veridian deal may be compromised
  • Contact Veridian acquisition team โ€” their counterparty data may be exposed
  • Preserve all DNS logs for the past 30 days as legal evidence
  • Engage outside counsel โ€” attorney-client privilege considerations apply
  • Notify cyber insurance carrier
  • Investigate ryan.porter โ€” victim of compromise or insider threat?

๐Ÿ“‹ Part 5: Building Your Timeline

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  INCIDENT TIMELINE โ€” Hargrove & Lyle LLP / WORKSTATION-07                       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Apr 20        โ”‚ Veridian acquisition case opened; ryan.porter assigned          โ”‚
โ”‚  (Day 0)       โ”‚ โ†’ Case documents loaded onto WORKSTATION-07                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Apr 20        โ”‚ First DNS query to exfil-c2.net                               โ”‚
โ”‚  ~10:00 AM     โ”‚ โ†’ Tunnel established; Base64-encoded data begins flowing       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Apr 20โ€“25     โ”‚ Sustained DNS tunnelling โ€” 200+ queries/hour during work hours โ”‚
โ”‚  (Days 1โ€“6)    โ”‚ โ†’ Every query carries 40โ€“63 bytes of encoded document data    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Apr 25        โ”‚ Anomaly flag triggers on DNS volume                            โ”‚
โ”‚  (Day 6)       โ”‚ โ†’ Ticket assigned to Alex Chen as "probably nothing"           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Apr 26        โ”‚ Alex decodes first subdomain โ€” "client-data-chunk-1"          โ”‚
โ”‚  9:14 AM       โ”‚ โ†’ Tunnel identified; full investigation begins                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Apr 26        โ”‚ Full LOLBAS chain mapped; attribution to ryan.porter confirmed โ”‚
โ”‚  10:47 AM      โ”‚ โ†’ WORKSTATION-07 isolated, exfil-c2.net blocked               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Apr 26        โ”‚ Partners notified โ€” Veridian deal paused pending investigation โ”‚
โ”‚  11:30 AM      โ”‚ โ†’ Outside counsel engaged; $2.4B acquisition at risk           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“ Part 6: Export Your Hunt Notebook โ†’ GitHub Portfolio

If you followed the milestone blocks throughout this lab, your Hunt Notebook contains four completed sections covering subdomain anomaly, frequency analysis, user attribution with volume estimate, and blast radius scope.

Two paths to GitHub:

Option A โ€” Merge your milestones. Combine all four blocks, add a cover section with your name, date, executive summary and IOC table, and push as hunt-003-dns-tunneling-detection.md.

Option B โ€” Use the Hunt Forward pre-written report. Download the completed reference report from your Hunt Forward dashboard and push it directly.

That said โ€” write your own.

This lab produced something most analysts never calculate in training: an actual estimated exfiltration volume derived from query data. That chunk_size_bytes = LENGTH(subdomain) * 6 / 8 formula โ€” and the reasoning behind it โ€” is the kind of thing that makes a technical interviewer pause. Write the section where you explain it in your own words. That's the part nobody else's portfolio has.

Export from the Hunt Notebook and push to GitHub as:

hunt-003-dns-tunneling-detection.md

๐Ÿ›ก๏ธ Part 7: What Alex Did Next

WORKSTATION-07 was isolated by 10:53 AM and the Veridian deal paused by noon โ€” a $2.4 billion transaction held because of a DNS anomaly that almost got filed as a misconfigured endpoint. Early forensics pointed to spear-phishing six days earlier, not an insider; Alex's Hunt Notebook entry, including the ES|QL exfiltration volume estimate, became the first page of the legal hold notice filed that afternoon.

๐ŸŽ“ The Takeaway

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  DETECTION TECHNIQUES โ€” Hunt Forward Lab #003: DNS Tunneling                                      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Technique                 โ”‚  What It Finds                        โ”‚  ES|QL Pattern              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Hunt 1                    โ”‚  Abnormally long DNS subdomains       โ”‚  EVAL subdomain_length =    โ”‚
โ”‚  Subdomain length analysis โ”‚  consistent with Base64 encoding     โ”‚    LENGTH(LEFT(...))        โ”‚
โ”‚                            โ”‚                                       โ”‚  | STATS AVG(subdomain_len) โ”‚
โ”‚                            โ”‚                                       โ”‚    BY source.ip, domain     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Hunt 2                    โ”‚  Sustained high-rate queries to one  โ”‚  EVAL hour_bucket =         โ”‚
โ”‚  Query frequency analysis  โ”‚  domain with unique subdomains       โ”‚    DATE_TRUNC(1 hour, ...)  โ”‚
โ”‚                            โ”‚                                       โ”‚  | STATS COUNT(),           โ”‚
โ”‚                            โ”‚                                       โ”‚    COUNT_DISTINCT(name)     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Hunt 3                    โ”‚  Tunnel attributed to specific host   โ”‚  EVAL chunk_bytes =         โ”‚
โ”‚  Attribution + volume      โ”‚  and user; MB estimate calculated    โ”‚    LENGTH(sub) * 6 / 8      โ”‚
โ”‚                            โ”‚                                       โ”‚  | STATS SUM(chunk_bytes)   โ”‚
โ”‚                            โ”‚                                       โ”‚    BY user.name, host.name  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Hunt 4                    โ”‚  Identifies all hosts showing tunnel  โ”‚  EVAL tunnel_score =        โ”‚
โ”‚  Blast radius scoring      โ”‚  behaviour across the environment    โ”‚    CASE(avg_len > 50        โ”‚
โ”‚                            โ”‚                                       โ”‚    AND count > 500,         โ”‚
โ”‚                            โ”‚                                       โ”‚    "HIGH", ...)             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The lesson DNS tunnelling teaches that signature-based detection can't: protocol trust is not the same as protocol safety. DNS is trusted everywhere because breaking it breaks the internet. That trust is exactly what makes it the perfect covert channel. The only defence is behavioural โ€” and behavioral detection requires analysts who can write the right queries.

๐Ÿš€ Ready for the Next Lab?

Coming up in the Hunt Forward series:

  • Lab #004: Credential Dumping โ€” Hunting LSASS Access in Endpoint Logs
  • Lab #005: Persistence Mechanisms โ€” Registry Run Keys, Scheduled Tasks & Startup Folders
  • Lab #006: Lateral Movement โ€” Pass-the-Hash and Token Impersonation

๐Ÿ‘‰ Access all labs at huntforward.com โ€” 7-day free trial, then $5/month

Follow Hunt Forward on Medium to get notified when Lab #004 drops.

Hunt Forward Lab #003 โ€” DNS Tunneling Detection MITRE ATT&CK: T1071.004 (DNS C2) | T1048.003 (Exfiltration Over Unencrypted Protocol) Dataset: dns-tunnel-lab-logs | Difficulty: Intermediate