The Core Idea
WebRTC is normally used for peer-to-peer audio and video in browsers. Under the hood, it establishes direct UDP paths between peers using a process called ICE (Interactive Connectivity Establishment), negotiated via SDP offer/answer messages. One of its lesser-known features is the DataChannel — a reliable or unreliable bidirectional message channel that has nothing to do with media.
Snowflake, the Tor pluggable transport, uses browser-based volunteers to run WebRTC proxies. A censored client connects to a volunteer's browser via WebRTC and forwards its Tor traffic through it. The volunteer resolves the bridge; the censored client never does.
This lab implements the same idea at a much smaller scale: a Go proxy, a signaling broker, a controlled target, and a CLI or browser client — all wired together over WebRTC DataChannels.
[Test Client] -- HTTP signaling --> [Broker]
[Test Client] == WebRTC DataChannel ==> [Proxy Server]
[Proxy Server] -- HTTP/HTTPS --> [Controlled Target]
The client never touches the target directly. The proxy does all the resolving and fetching.
Lab Architecture
The project has four main components:
Broker (cmd/broker) — A lightweight HTTP server that holds SDP offer/answer messages so peers can find each other. It does not relay traffic; it only facilitates WebRTC signaling.
Proxy (cmd/relay) — The heart of the lab. It connects to the broker, waits for a client to signal, establishes a WebRTC DataChannel, and then proxies HTTP/HTTPS requests to the configured target URL. It is deliberately restricted: only GET and POST are allowed, it connects only to the -TargetUrl you configure, and it does not implement command execution, persistence, credential access, file collection, process hiding, or an open proxy.
Client (cmd/webclient / cmd/browserui) — Either a CLI client that sends synthetic requests over the DataChannel, or a local browser viewer that creates a WebRTC DataChannel from the browser and renders sanitized HTML responses. The browser viewer strips scripts, forms, external assets, and cross-site links.
Target (cmd/target) — A minimal controlled HTTP server for local verification that logs every hit, making it easy to confirm the proxy host IP shows up rather than the client IP.
What the Lab Tested
Test 1: Client-Side DNS Filter (NextDNS)
Setup:
- Device B: monitored client running a NextDNS client, with the target domain blocked by policy
- Device A: proxy host running the broker and proxy server
- Client interface:
browseruiathttp://127.0.0.1:7777on Device B
Result:
- Direct access from Device B to the target was blocked by NextDNS, as expected.
- The target loaded through the WebRTC proxy viewer on Device B.
- Device B's DNS logs showed no query for the target domain — it never resolved it.
- Device A resolved and fetched the target, and the target's server logs showed Device A's IP as the requester.
Interpretation:
Client-side DNS filtering did not block the destination when the monitored client never resolved the destination domain and instead requested it through a separate WebRTC proxy host. This is not a novel vulnerability — it is the same architectural reality that Snowflake exploits for censorship circumvention, and that any proxy or VPN exploits. What the lab adds is a clean, repeatable measurement of exactly which tool sees what.
This finding does not imply traffic invisibility. The client's firewall and NDR still see:
- HTTP signaling to the broker
- STUN lookups
- WebRTC/UDP flows to the proxy
The proxy's DNS resolver and network logs still show the target. The destination is not hidden from a full network view — only from a client-side DNS filter that does not also inspect WebRTC payload content.
Test 2: Remote Router WebRTC (Off-LAN)
Setup:
- Device A: proxy host behind a home router
- Device B: client on a separate network
- Router forwarded TCP
8080(signaling) and UDP40000(ICE) to Device A - Proxy run with
-IcePortMin 40000 -IcePortMax 40000 -AdvertiseIP <public_ip>
Result:
- Before advertising the public IP, the remote client's firewall showed UDP
40000checks toward private addresses (192.168.x.x,172.16.x.x) — the proxy was leaking internal ICE candidates. - After adding
-AdvertiseIP, the WebRTC path connected successfully across the NAT.
Interpretation:
For an off-LAN path, broker reachability (TCP signaling) is necessary but not sufficient. The proxy must also expose a reachable UDP ICE candidate. Advertising the public IP with a fixed ICE port is what made the remote DataChannel work. Without TURN, strict enterprise egress policies or symmetric NATs will still prevent connection.
Why Endpoint Tools May Flag This
The lab's DETECTION_NOTES.md is worth reading for any defender who wants to understand what behavioral signals this kind of tool leaves behind:
- Unsigned Go binary with little reputation
- Browser-independent WebRTC/DataChannel networking (unusual for a non-browser process)
- Broker, proxy, heartbeat, and session naming patterns
- Synthetic request forwarding through a proxy host
- PowerShell wrapper scripts with Mark of the Web
None of these are malicious in isolation, but together they look like something an attacker might build. That is intentional — the lab is designed to resemble the behavioral pattern so that detection tools can be tuned against it.
The recommended approach for defensive lab use: build from source, keep source available for review, use an owned test target, and handle any EDR exceptions through normal change-control rather than by obfuscating or packing the binaries.
What Defenders Should Take Away
DNS filters are one control layer, not a complete picture. If a client never resolves a blocked domain, a client-side DNS filter has nothing to block. This is true for WebRTC proxies, VPNs, SOCKS proxies, and any other mechanism that offloads DNS resolution to another host.
WebRTC outside a browser is a detection signal. Most WebRTC traffic originates from browsers. A standalone process performing WebRTC DataChannel communication is unusual and worth logging. NDR and EDR tools that model this behavior will catch it even when the DNS filter doesn't.
Full-path visibility matters. The proxy host's DNS, network, and process logs still show the target. An organization that monitors only the client's DNS resolver will miss the traffic; one that also monitors the proxy host (or the network path between them) will see it.
STUN and UDP are part of the surface. Blocking or restricting STUN servers and outbound UDP will prevent or degrade WebRTC-based proxy paths. This is already common in some enterprise environments and is worth validating explicitly.
The Snowflake model is not new. This lab replicates a technique that has been in production in censorship circumvention tools for years. Defenders who are not already familiar with how WebRTC DataChannels can be used as a transport layer should be.
Running the Lab Yourself
The lab is designed to be run only in owned or explicitly authorized environments. The proxy is bounded to a single configured target URL — it is not an open proxy.
Local quick test (all components on one machine):
Linux:
python3 scripts/run_lab.py proxy-local --session proxy-local --no-stun
Windows:
.\scripts\run-lab.ps1 -Role proxy-local -Session proxy-local -NoStun
Success looks like:
Broker: stored offer, stored answer
Proxy: proxy data channel "lab-proxy" open, proxy request id=... target=...
Client: proxy response id=... status=200
Remote/off-LAN test:
Forward TCP 8080 and UDP 40000 on your router to the proxy host, then:
.\scripts\run-lab.ps1 -Role proxy -BrokerUrl http://127.0.0.1:8080 `
-Session split-test -TargetUrl https://your-owned-target.example `
-IcePortMin 40000 -IcePortMax 40000 -AdvertiseIP YOUR_PUBLIC_IP
If the remote firewall shows UDP checks to private addresses, the proxy is advertising internal ICE candidates — add -AdvertiseIP to fix it.
Limitations and What This Lab Doesn't Cover
- No TURN. The lab does not bundle a TURN server. Paths where direct UDP cannot connect (symmetric NAT, strict enterprise egress) will fail. TURN is the standard WebRTC fallback and should be part of a more complete test.
- No TLS inspection. The lab does not measure what a TLS-inspecting proxy sees of the proxied traffic.
- No EDR bypass. The lab does not test or demonstrate endpoint detection evasion. The detection notes are descriptive, not prescriptive.
- Bounded scope. This is a defensive network-behavior lab, not a red-team toolkit. It does not implement command execution, persistence, credential access, file collection, or process hiding.
Conclusion
Client-side DNS filtering is a useful control, but it operates on what the client resolves — and a WebRTC proxy can shift all resolution to a separate host. The lab makes this concrete and measurable: a controlled target, a real DNS filter, real DNS logs, and a real DataChannel connection that the filter never sees.
The more interesting question for defenders is not "can a client bypass a DNS filter" — the answer has always been yes, through many mechanisms — but "what does the full network and endpoint view look like when they do?" The lab is designed to answer that question with reproducible evidence.
Repository: snowflake-inspired-webrtc-proxy-lab
Built in Go. Run only in owned or explicitly authorized environments.