Education
Detecting ‘Dark Tunnels’ is an important element to corporate security, much like detecting unauthorised RMM usage. But what is a dark tunnel?
according to GROK:
A dark tunnel (sometimes called a “dark pool tunnel” or simply a secure reverse tunnel in networking contexts) refers to a type of secure, outbound-only tunneling technology that allows private access to internal services, devices, or networks without exposing them to the public internet. The “dark” aspect emphasizes that the tunnel is hidden or invisible from external scanners—there’s no inbound port forwarding, firewall holes, or public IP exposure required. Instead, it relies on encrypted outbound connections from the internal resource to a cloud-based relay or peer-to-peer mesh, enabling zero-trust access (e.g., via authentication tokens or keys).
This approach is popular in DevOps, IoT, remote work, and cybersecurity for bridging on-premises or edge devices to the cloud securely, often bypassing NAT traversal issues or legacy VPN complexities.
Some KQL (Created by GROK)
So let’s use our AI friends to create a detection rule (this is an example, you will need to create your own rules you are happy with):
// === Detect Reverse Tunneling Tools (ngrok, Cloudflare, ZeroTier, frp, etc.) ===
// Table: DeviceNetworkEvents
// Scope: Outbound connections from internal devices to known tunnel endpoints
DeviceNetworkEvents
| where Timestamp > ago(1d) // Adjust time range as needed
| where InitiatingProcessFileName !in~ ("svchost.exe", "msedge.exe", "chrome.exe", "firefox.exe") // Exclude common browsers/services
| extend
Domain = tolower(RemoteUrl),
Process = tolower(InitiatingProcessFileName),
CommandLine = tolower(InitiatingProcessCommandLine),
LocalPort = LocalPort,
RemotePort = RemotePort
| where
// 1. Known ngrok domains
Domain has_any ("ngrok.io", "ngrok.com", "ngrok-free.app", "ngrok.app", "ngrok-free.com")
// 2. Cloudflare Tunnel (cloudflared)
or Domain has "cloudflare.com" and Domain has "trycloudflare.com"
or Process == "cloudflared.exe"
or CommandLine contains "cloudflared tunnel"
// 3. ZeroTier
or Domain has "zerotier.com" and RemotePort == 9993
or Process == "zerotier_one_x64.exe" or Process == "zerotier-cli.exe"
// 4. frp (fast reverse proxy)
or Process == "frpc.exe" or Process == "frps.exe"
or CommandLine contains "frpc" or CommandLine contains "frps"
// 5. bore (Rust TCP tunnel)
or Process == "bore.exe"
or CommandLine contains "bore local"
// 6. inlets / Zrok / PageKite / Loophole
or Process in~ ("inlets.exe", "zrok.exe", "pagekite.py", "loophole.exe")
or CommandLine contains "inlets" or CommandLine contains "zrok share"
// 7. Localtunnel / Serveo / pinggy (via SSH-like patterns)
or (Domain has "loca.lt" or Domain has "serveo.net" or Domain has "pinggy.io")
// 8. Tailscale (control plane + funnel)
or Domain has "tailscale.com" and RemotePort in (443, 3478, 41641)
or Process == "tailscale.exe" or Process == "tailscaled.exe"
// 9. VSCode Remote Tunnels
or Domain has "vscode.dev" and Domain has "tunnel"
or CommandLine contains "code tunnel"
// 10. Generic SSH reverse tunnel (autossh, ssh -R)
or (Process == "ssh.exe" and CommandLine matches regex @"-R\s+\d+:\w+:\d+")
or (Process == "autossh.exe")
// 11. High ports + TLS to suspicious domains (heuristic)
or (RemotePort == 443 and LocalPort > 50000 and Domain !has "microsoft.com" and Domain !has "windowsupdate.com")
and Domain matches regex @"^[a-z0-9-]+\.ngrok\.|trycloudflare\.|loca\.lt$"
// Project & Summarize
| project
Timestamp,
DeviceName,
InitiatingProcessFileName,
InitiatingProcessCommandLine,
RemoteUrl,
RemoteIP = RemoteIP,
RemotePort,
LocalPort,
ActionType,
Protocol = case(
RemotePort in (80, 443), "HTTP/S",
RemotePort == 22, "SSH",
RemotePort == 9993, "ZeroTier",
"Other"
),
DetectedTool = case(
Domain has "ngrok", "ngrok",
Domain has "trycloudflare.com" or Process == "cloudflared.exe", "Cloudflare Tunnel",
Process == "zerotier", "ZeroTier",
Process contains "frp", "frp",
Process == "bore.exe", "bore",
Process contains "inlets", "inlets",
Domain has "loca.lt", "Localtunnel",
Domain has "serveo.net", "Serveo",
Domain has "pinggy.io", "pinggy",
Process contains "zrok", "Zrok",
Process contains "pagekite", "PageKite",
Process contains "loophole", "Loophole",
Process contains "tailscale", "Tailscale",
CommandLine contains "code tunnel", "VSCode Tunnel",
Process == "ssh.exe" and CommandLine matches regex @"-R", "SSH Reverse Tunnel",
"Heuristic (High Port TLS)"
)
| order by Timestamp desc
| summarize
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
EventCount = count(),
SampleProcesses = make_set(InitiatingProcessFileName, 10),
SampleCommands = make_set(InitiatingProcessCommandLine, 5),
SampleDomains = make_set(RemoteUrl, 5)
by DeviceName, DetectedTool
| extend AlertSeverity = case(
DetectedTool in ("ngrok", "Cloudflare Tunnel", "SSH Reverse Tunnel", "frp", "bore"), "High",
DetectedTool in ("ZeroTier", "Tailscale", "VSCode Tunnel"), "Medium",
"Low"
)
| project-reorder DeviceName, DetectedTool, AlertSeverity, FirstSeen, LastSeen, EventCount, SampleProcesses, SampleCommands, SampleDomains
Summary
There’s a bunch of cat and mouse here with regard to having a near infinite number of tools possible and only a finite level of detection management capability. Hopefully this helps inspire you to think about policy but also detection engineering and response!








