DNS Hijacking in Captive Portals: How Traffic Gets Redirected
The DNS Query That Lies
A DNS query is one of the most routine network events that happens on any connected device. You go to a website, your OS sends a UDP packet to port 53 asking "what's the IP for this domain?", a server responds with an IP address, your device connects to that IP. This happens thousands of times a day without you noticing.
In a captive portal, the DNS server you've been assigned via DHCP is not a real resolver. It's a piece of software on the portal server - or a separate device - configured to return the same IP address for every single query, regardless of what domain was asked for.
That one change - a DNS server that always lies - is the core mechanism of every captive portal you've ever encountered at a coffee shop, hotel, or airport. This article explains exactly what that looks like at the protocol level, why it works for HTTP but not HTTPS, how to detect it, and how to defend against it in environments where it matters.
A Normal DNS Query
Before covering what captive portal DNS does, it helps to understand what normal DNS looks like at the packet level.
A DNS query is a UDP (or TCP for large responses) packet sent to port 53. The query contains:
- A transaction ID (16-bit, matches query to response)
- Flags (QR=0 for query, OPCODE=0 for standard query)
- Question count: 1
- The question: QNAME (the domain, encoded as length-prefixed labels), QTYPE (usually A for IPv4 address), QCLASS (usually IN for Internet)
The response has QR=1 and includes answer records. For an A record query on google.com, the answer section contains one or more A records with IP addresses like 142.250.80.46.
Normal DNS respects what was asked. Ask for google.com, get google.com's IP. Ask for apple.com, get apple.com's IP. Each domain returns its actual address.
Captive Portal DNS: What Changes
sequenceDiagram
participant Client
participant DHCP as DHCP Server
participant DNS as Captive DNS
participant Portal as Portal Server
participant Real as Real Internet
Client->>DHCP: DHCP Discover
DHCP->>Client: DHCP ACK (DNS = portal IP)
Note over Client: Device has IP, uses portal DNS
Client->>DNS: A record for google.com?
DNS->>Client: 192.168.4.1 (portal IP!)
Client->>DNS: A record for apple.com?
DNS->>Client: 192.168.4.1 (same portal IP!)
Client->>DNS: A record for mail.company.com?
DNS->>Client: 192.168.4.1 (still portal IP!)
Note over Client: Every domain resolves to portal
Client->>Portal: HTTP GET / (Host: google.com)
Portal->>Client: HTTP 302 to /login
Client->>Portal: GET /login
Portal->>Client: Login page HTML
Note over Client: User authenticates
Client->>Portal: POST /login (credentials)
Portal->>Portal: Whitelist client IP in firewall
Client->>DNS: A record for google.com?
DNS->>Client: 142.250.80.46 (real IP now!)
Client->>Real: Normal traffic resumes
Complete DNS hijacking flow in a captive portal - every domain resolves to the portal until authentication
The captive portal DNS server discards the QNAME entirely and always responds with a single A record: the portal server's IP address.
The transaction ID is preserved (so the response matches the query), flags are set correctly (QR=1, RCODE=0/NOERROR), and the answer section contains an A record with the portal IP as the RDATA. Everything looks like a valid DNS response - it just contains the wrong IP for every domain.
In tcpdump or Wireshark on a captive portal network, it looks like this:
Query: A example.com ?
Response: A example.com -> 192.168.4.1 (portal IP)
Query: A google.com ?
Response: A google.com -> 192.168.4.1 (portal IP)
Query: A mail.company.com ?
Response: A mail.company.com -> 192.168.4.1 (portal IP)
Every query returns the same IP. This is sometimes called a "wildcard DNS" response or a "DNS black hole" - every domain falls into the portal's address space.
Some portals implement a slightly more refined version: they maintain a whitelist of domains that resolve correctly (the walled garden), and only return the portal IP for unlisted domains. The walled garden might include the payment processor, the portal's own CDN assets, or specific services the operator wants to allow before authentication. But for everything outside the whitelist, the behavior is the same: portal IP for every query.
DNS TTL in Captive Portal Responses
DNS responses include a TTL (time to live) field on each record, indicating how long the client should cache the response before querying again. Legitimate records might have TTLs of 300 seconds (5 minutes) to 86400 seconds (1 day).
Captive portal DNS responses typically use very short TTLs - often 0 to 60 seconds. This is intentional: after the user authenticates, the portal needs the client to re-query DNS and get the real IP address for whatever domain it's trying to reach. If the client had cached "google.com = portal IP" with a 24-hour TTL, it would continue failing to reach Google even after authenticating.
Short TTLs ensure that post-authentication DNS queries go back to a real resolver (the portal updates the DHCP-assigned DNS server after auth, or begins forwarding queries to a real resolver) and clients get fresh, correct responses quickly.
Why This Only Works for HTTP
DNS hijacking makes every hostname resolve to the portal IP. But what actually happens when your device tries to connect to that IP depends on the protocol.
For HTTP (port 80), the connection reaches the portal server, which reads the HTTP Host header to see what domain was originally requested, and returns an HTTP 302 redirect to the portal page URL. The browser follows the redirect and loads the portal. This works cleanly.
For HTTPS (port 443), the client initiates a TLS handshake. TLS requires the server to present a certificate that is valid for the domain the client is connecting to. The portal server has a certificate for its own domain (portal.example.com or similar) - it doesn't have a valid certificate for google.com or apple.com. When the TLS handshake proceeds, the client's browser sees the wrong certificate, the hostname doesn't match, and the connection is terminated with a certificate error.
The portal can't serve its redirect page to HTTPS traffic without triggering a certificate error. This is why captive portal OS detection uses plain HTTP to a known endpoint - it's specifically designed to trigger the redirect cleanly.
There's a theoretical attack path: a malicious portal could attempt SSL stripping - intercepting the HTTP response that was supposed to redirect the client to HTTPS and returning an HTTP version of the page instead. But this only works against connections that aren't protected by HSTS, and modern browsers and services heavily use HSTS. In practice, SSL stripping is less effective than it was in 2010.
DNSSEC and Its Limitations Here
DNSSEC (DNS Security Extensions) adds cryptographic signatures to DNS responses, allowing resolvers to verify that responses came from the authoritative nameserver and haven't been tampered with. If a resolver validates DNSSEC, a forged or hijacked response should be detected and rejected.
However, DNSSEC validation happens in the resolver, not the client. When you're on a captive portal network, your DHCP-assigned DNS server is the portal's own server, not a DNSSEC-validating public resolver. The portal's DNS server simply doesn't perform DNSSEC validation - it returns whatever response it wants to return. Your client is talking to the portal's DNS server directly and trusts its responses implicitly.
Using a hardcoded DNS resolver IP in your device configuration (like 8.8.8.8 or 1.1.1.1) sidesteps the DHCP-assigned DNS server, but on most captive portal networks, DNS traffic to external resolvers on port 53 is firewall-blocked until after authentication. You can't bypass the portal DNS by hardcoding a resolver - the portal's firewall catches the traffic and either drops it or redirects it to the portal DNS server anyway.
DNS-over-HTTPS (DoH) is different. DoH sends DNS queries as HTTPS to a resolver endpoint (like cloudflare-dns.com:443). Since this traffic looks like regular HTTPS, it might bypass DNS interception on some portal networks - but it depends heavily on whether the portal blocks or redirects port 443 traffic to external IPs before authentication. Many portals do exactly that.
Detecting DNS Hijacking
graph TD
subgraph "Detection Method 1: Cross-Reference"
A["Query domain via network DNS"] --> B["Record returned IP"]
C["Query same domain via known-good DNS"] --> D["Record returned IP"]
B --> E{"IPs match?"}
D --> E
E -->|"No"| F["DNS hijacking confirmed"]
E -->|"Yes"| G["DNS appears clean"]
end
subgraph "Detection Method 2: Pattern Analysis"
H["Query multiple random domains"] --> I["Collect all returned IPs"]
I --> J{"All IPs identical?"}
J -->|"Yes"| K["Captive portal DNS hijacking"]
J -->|"No"| L["Selective hijacking or clean"]
end
subgraph "BLEShark Approach"
F --> M["Log hijacked responses"]
K --> M
M --> N["Compare against WiFi scan data"]
N --> O["Identify portal server"]
end
Methods for detecting DNS hijacking - cross-referencing resolvers and analyzing response patterns
Detecting that you're behind a hijacking DNS server is straightforward if you know what to check:
Query multiple unrelated domains and compare results. If google.com, cloudflare.com, and amazon.com all resolve to the same IP address, you're behind a wildcard DNS server. In normal operation, these domains would return completely different IPs.
Check for NXDOMAIN suppression. A real DNS resolver returns NXDOMAIN (non-existent domain) for domains that don't exist. A captive portal DNS server often returns the portal IP even for made-up domains. Query randomnonsensedomain12345.xyz - if you get an IP back instead of NXDOMAIN, the DNS server is hijacking.
Check the DNS server's IP against the gateway. If your DNS server and your default gateway are the same IP (or the DNS is on the same /24 subnet as the gateway), you're likely on a locally-controlled resolver that could be doing anything. Public networks using 8.8.8.8 or 1.1.1.1 as DNS give you more confidence, though this can also be spoofed at the firewall level.
On a BLEShark Nano hosted portal, the DNS behavior is exactly as described: every query returns the Nano's IP. This is visible in nRF Connect or any network diagnostic tool that shows DNS responses. Running nslookup google.com <nano-ip> against the Nano's DNS server will return the Nano's own IP.
The BLEShark DNS Implementation
BLEShark Nano runs a lightweight DNS server that handles all queries from connected clients. The implementation is a minimal UDP server on port 53 that parses incoming query packets, extracts the transaction ID and QTYPE, and constructs a valid DNS response with the Nano's own IP as the answer for any A record query.
AAAA (IPv6) queries get either the Nano's IPv6 address or an empty response, depending on configuration - most captive portal implementations ignore IPv6 because the portal's connectivity is IPv4. This can cause issues on aggressively IPv6-preferring clients, but most mobile OS captive portal detection falls back to IPv4 cleanly.
Queries for common OS captive portal detection endpoints (captivedetect.apple.com, connectivitycheck.gstatic.com, msftconnecttest.com) are handled specifically - the HTTP server serves appropriate redirect responses to these, ensuring that OS auto-detection fires and the portal opens automatically.
Defense Strategies
For individuals on untrusted networks:
- Use a VPN that tunnels DNS inside the VPN tunnel. Once the VPN is established, all DNS goes through the VPN provider's resolver, bypassing the captive portal DNS. The catch: you need to authenticate the portal first before the VPN can connect.
- Use a browser with DoH enabled. Chrome, Firefox, and Edge all support DoH. This doesn't help pre-authentication but protects you once you're through the portal.
- Treat any network that redirects your traffic as hostile until proven otherwise. Don't enter credentials on a portal page for an unfamiliar network.
For organizations defending against rogue portals:
- Deploy DNS monitoring that alerts on sudden changes in resolution results for known-good domains.
- Use 802.1X enterprise authentication for corporate networks - devices can't accidentally connect to a rogue AP that doesn't know the RADIUS credentials.
- Configure corporate device DNS settings via MDM to use DoH or DoT, making DNS interception harder even on untrusted networks.
- Run periodic captive portal detection tests using BLEShark Nano or similar tools to verify that rogue portals would be detected by your environment.
DNS Hijacking Is the Simple Part
DNS hijacking in captive portals is a blunt instrument - every query returns the same wrong answer. It's effective because most users never look at DNS responses, and because HTTP traffic cooperates with redirects in a way that makes the portal experience seamless.
The moment you move beyond HTTP - to HTTPS, to encrypted DNS, to VPN - the portal's control weakens significantly. The portal can block non-portal traffic at the firewall level, but it can't decrypt or forge TLS connections without triggering browser security errors.
Understanding DNS hijacking at this level means you can recognize it instantly, test for it on any network, and explain to a client exactly why their devices end up on a portal page despite never having asked to go there.