Protocol Tunneling — Command and Control via Cloudflared Quick Tunnel and Token-Based Cloudflare Tunnel
The cloudflared daemon is Cloudflare's legitimate, signed connector for Cloudflare Tunnel. It makes only outbound connections to Cloudflare's edge (region*.v2.argotunnel.com, port 7844 over QUIC or HTTP/2) and Cloudflare then proxies inbound requests back through that connection to any local service. Adversaries abuse two modes. In quick-tunnel mode, `cloudflared tunnel --url http://localhost:PORT` requires no account and returns a random *.trycloudflare.com hostname, giving an attacker a throwaway HTTPS front for a web shell, C2 listener or file server. In token mode, `cloudflared tunnel run --token <JWT>` or `cloudflared service install <token>` binds the host to an attacker-controlled Cloudflare account and can route RDP, SSH or SMB to the operator, persistently if installed as a service. Because the traffic is outbound TLS/QUIC to a high-reputation CDN, it bypasses inbound firewall rules, egress allowlists that trust Cloudflare, and domain-reputation controls. This record differs from T1090.002/T1572 base logic and THREAT-C2-VSCodeTunnelGitHubDeviceCodeC2 in keying on cloudflared-specific indicators: tunnel subcommand arguments, argotunnel/trycloudflare destinations, UDP/TCP 7844, and binaries outside the managed install path. Detection scores three independent signals per host (CLI bootstrap, edge egress, unmanaged binary path) and alerts when two or more coincide.
What is THREAT-C2-CloudflaredQuickTunnelC2 Command and Control via Cloudflared Quick Tunnel and Token-Based Cloudflare Tunnel?
Command and Control via Cloudflared Quick Tunnel and Token-Based Cloudflare Tunnel (THREAT-C2-CloudflaredQuickTunnelC2) is a sub-technique of Protocol Tunneling (T1572) in the MITRE ATT&CK framework. It maps to the Command and Control tactic — the adversary is trying to communicate with compromised systems to control them.
This page provides production-ready detection logic for Command and Control via Cloudflared Quick Tunnel and Token-Based Cloudflare Tunnel, covering the data sources and telemetry it touches: Process: Process Creation, Command: Command Execution, Network Traffic: Network Connection Creation, Microsoft Defender for Endpoint advanced hunting (DeviceProcessEvents, DeviceNetworkEvents). The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
- Tactic
- Command and Control
let LookbackWindow = 24h;
let TunnelBinaries = dynamic(["cloudflared.exe", "cloudflared"]);
let EdgeHosts = dynamic(["argotunnel.com", "trycloudflare.com", "cftunnel.com"]);
let ManagedPaths = dynamic([@"Program Files\cloudflared", @"Program Files (x86)\cloudflared", "/usr/local/bin/cloudflared", "/usr/bin/cloudflared"]);
let CliExecution =
DeviceProcessEvents
| where Timestamp > ago(LookbackWindow)
| where FileName in~ (TunnelBinaries) or ProcessVersionInfoOriginalFileName =~ "cloudflared.exe"
| where ProcessCommandLine has "tunnel" and (ProcessCommandLine has_any ("--url", "--token", "run", "service install"))
| project Timestamp, DeviceId, DeviceName, Signal = "CliExecution", Detail = strcat(FolderPath, " | ", ProcessCommandLine);
let EdgeEgress =
DeviceNetworkEvents
| where Timestamp > ago(LookbackWindow)
| where InitiatingProcessFileName in~ (TunnelBinaries) or RemoteUrl has_any (EdgeHosts) or RemotePort == 7844
| where RemoteUrl has_any (EdgeHosts) or RemotePort == 7844
| project Timestamp, DeviceId, DeviceName, Signal = "EdgeEgress", Detail = strcat(InitiatingProcessFileName, " -> ", RemoteUrl, ":", tostring(RemotePort));
let UnmanagedPath =
DeviceProcessEvents
| where Timestamp > ago(LookbackWindow)
| where FileName in~ (TunnelBinaries) and ProcessCommandLine has "tunnel"
| where not(FolderPath has_any (ManagedPaths))
| project Timestamp, DeviceId, DeviceName, Signal = "UnmanagedPath", Detail = FolderPath;
union CliExecution, EdgeEgress, UnmanagedPath
| summarize EventCount = count(), SignalTypes = make_set(Signal), Details = make_set(Detail, 20), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceId, DeviceName
| extend TunnelScore = array_length(SignalTypes)
| where TunnelScore >= 2
| project FirstSeen, LastSeen, DeviceName, DeviceId, TunnelScore, EventCount, SignalTypes, Details
| sort by TunnelScore desc, EventCount desc Scores three arms per device on MDE telemetry: cloudflared tunnel bootstrap command lines (--url, --token, run, service install), outbound connections to argotunnel/trycloudflare hosts or port 7844, and the binary running outside managed install paths. Alerts when at least two arms fire.
Data Sources
Required Tables
False Positives
- Platform or DevOps teams running an approved Cloudflare Tunnel (cloudflared service) to publish internal web applications through Zero Trust, which will show the same token-run command line and argotunnel egress
- Developers using a quick tunnel (trycloudflare.com) to demo a local web app or test webhooks from a laptop
- Web/SRE hosts where cloudflared is installed from the official package into a managed path and runs as a service under change control
Sigma rule & cross-platform mapping
The detection logic for Command and Control via Cloudflared Quick Tunnel and Token-Based Cloudflare Tunnel (THREAT-C2-CloudflaredQuickTunnelC2) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
category: process_creation
product: windows Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for THREAT-C2-CloudflaredQuickTunnelC2
References (8)
- https://attack.mitre.org/techniques/T1572/
- https://attack.mitre.org/techniques/T1090/002/
- https://attack.mitre.org/tactics/TA0011/
- https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/
- https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/
- https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/tunnel-with-firewall/
- https://github.com/cloudflare/cloudflared
- https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1 Cloudflared Quick Tunnel to Local Web Server (Linux)
Expected signal: Process create for cloudflared with args 'tunnel --url http://localhost:8089'; DNS lookups for api.trycloudflare.com and region*.v2.argotunnel.com; outbound connections to port 7844.
- Test 2 Cloudflared Quick Tunnel from Temp Directory (Windows)
Expected signal: Sysmon Event ID 1 for %TEMP%\cloudflared.exe with tunnel --url; Event ID 22 for *.trycloudflare.com / argotunnel.com; Event ID 3 to port 7844.
- Test 3 Cloudflared Token-Based Service Install (Windows, Invalid Token)
Expected signal: Process create with 'service install <token>'; Windows service creation event 7045 / Sysmon registry events for the Cloudflared service key.
Response Playbook
Triage
- Confirm which arms fired. A quick-tunnel command line (`--url`) or a `--token` run from a user-writable path is near-conclusive; a service install under Program Files may be an approved Zero Trust connector.
- Extract the full command line. For `--url`, note the local service being exposed (e.g. 3389, 445, 8080) — this reveals what the attacker wanted reachable. For `--token`, decode the JWT payload (base64) to obtain the account tag (a) and tunnel ID (t) for takedown requests and cross-host correlation.
- Identify the parent process and launching user. A scripted parent (powershell, cmd, wmic, a scheduled task, a web server worker) is far more suspicious than an administrator shell.
- Check with platform/network teams whether the organisation operates Cloudflare Tunnel and whether the account tag matches the corporate account.
- Look for the binary's origin: download via PowerShell/curl/bitsadmin from github.com/cloudflare/cloudflared releases or an unusual host shortly before execution (T1105).
- Search the fleet for the same tunnel ID or token fragment and for other hosts resolving *.argotunnel.com from non-approved processes.
Containment
- Isolate the host via EDR network containment; if the tunnel exposes RDP/SSH the attacker may have an interactive session right now.
- Kill the cloudflared process and stop/remove the service (`sc stop cloudflared`, `cloudflared service uninstall`), then preserve the binary and token first.
- Block the tunnel at the edge: sinkhole *.trycloudflare.com for non-approved segments and restrict outbound 7844/UDP+TCP to approved connector hosts only.
- Report the account tag/tunnel ID to Cloudflare abuse so the attacker tunnel is disabled.
- Reset credentials for accounts active on the host and any service exposed through the tunnel.
Evidence Collection
- Copy cloudflared binary, its hash, and any config (`config.yml`, `cert.pem`, credentials JSON) from %USERPROFILE%\.cloudflared or /etc/cloudflared
- Process creation records with full command line and parent chain; Windows service registry key HKLM\SYSTEM\CurrentControlSet\Services\Cloudflared for token-installed persistence
- Network logs for connections to 7844 and argotunnel/trycloudflare hostnames, with byte counts and session duration
- Proxy/DNS logs for the trycloudflare subdomain to identify what the exposed service was and who accessed it
- Authentication logs on the exposed service (RDP 4624 type 10, SSH auth logs) covering the tunnel window
Escalation Criteria
- ! Tunnel exposes RDP, SSH, SMB or a shell-like service and the host is a server, domain controller or holds sensitive data
- ! Token-based tunnel registered to an account that is not the organisation's
- ! Cloudflared found on multiple hosts, or installed as a service for persistence
- ! Evidence of preceding intrusion activity (phishing execution, exploitation, credential dumping) on the host
Investigation Guide
Forensic Artifacts
- >
cloudflared binary hash and Authenticode signature/OriginalFileName (renamed copies keep OriginalFileName cloudflared.exe) - >
%USERPROFILE%\.cloudflared\ (cert.pem, <tunnel-id>.json credentials, config.yml) or /etc/cloudflared/ - >
Windows service key HKLM\SYSTEM\CurrentControlSet\Services\Cloudflared whose ImagePath embeds the tunnel token - >
Sysmon Event ID 1 command line and Event ID 22 DNS records for *.argotunnel.com and *.trycloudflare.com - >
Sysmon Event ID 3 connections to destination port 7844 from the cloudflared process - >
Prefetch/Amcache entries for cloudflared.exe from non-standard directories
Tuning Guidance
If the organisation uses Cloudflare Tunnel, inventory approved connector hosts first and exclude those DeviceIds (not subnets or accounts). Better still, allowlist your own account tag by comparing the decoded token in the command line. Quick-tunnel (`--url` / trycloudflare.com) usage has almost no enterprise justification outside developer subnets and should alert without an allowlist. Raise the score threshold to 3 for developer segments; keep it at 2 for servers.
Hunting Queries
Finds non-browser processes resolving or connecting to Cloudflare tunnel infrastructure across 30 days, surfacing cloudflared installs that have not yet been bootstrapped from a monitored command line.
// Hunt: any host contacting Cloudflare tunnel edge or quick-tunnel domains by a non-browser process
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where RemoteUrl has_any ("argotunnel.com", "trycloudflare.com") or RemotePort == 7844
| where InitiatingProcessFileName !in~ ("chrome.exe", "msedge.exe", "firefox.exe")
| summarize Connections = count(), Procs = make_set(InitiatingProcessFileName, 10), Urls = make_set(RemoteUrl, 10), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName
| sort by Connections desc index=windows sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=22
| eval q=lower(QueryName)
| where like(q, "%.argotunnel.com") OR like(q, "%.trycloudflare.com")
| stats count, values(Image) as Images, min(_time) as FirstSeen, max(_time) as LastSeen by ComputerName, q
| sort - count Atomic Red Team Tests
Starts a throwaway local HTTP server and exposes it with a cloudflared quick tunnel, exercising CLI and edge-egress arms. Requires cloudflared installed; no Cloudflare account needed.
Command
command -v cloudflared >/dev/null || { echo 'cloudflared not installed; skipping'; exit 0; }
python3 -m http.server 8089 --directory /tmp >/dev/null 2>&1 &
echo $! > /tmp/atomic_http.pid
timeout 30 cloudflared tunnel --url http://localhost:8089 --no-autoupdate >/tmp/atomic_cf.log 2>&1 || true
grep -o 'https://[a-z0-9-]*\.trycloudflare\.com' /tmp/atomic_cf.log | head -1 Cleanup
pkill -f 'cloudflared tunnel --url' 2>/dev/null; kill $(cat /tmp/atomic_http.pid) 2>/dev/null; rm -f /tmp/atomic_http.pid /tmp/atomic_cf.log; true Expected Telemetry
Process create for cloudflared with args 'tunnel --url http://localhost:8089'; DNS lookups for api.trycloudflare.com and region*.v2.argotunnel.com; outbound connections to port 7844.
Expected Detection
CLI and edge-egress arms both fire, giving TunnelScore >= 2 and an alert on the host.
Runs cloudflared.exe from a user-writable temp path with the quick-tunnel flag to trigger all three arms (CLI, unmanaged path, edge egress). Requires a cloudflared.exe copied to %TEMP%.
Command
$exe = Join-Path $env:TEMP 'cloudflared.exe'
if (-not (Test-Path $exe)) { Write-Host 'Place cloudflared.exe in %TEMP% first; skipping'; exit 0 }
Start-Process -FilePath $exe -ArgumentList 'tunnel','--url','http://localhost:8080','--no-autoupdate' -WindowStyle Hidden
Start-Sleep -Seconds 25
Write-Host 'cloudflared quick tunnel test complete' Cleanup
Get-Process -Name cloudflared -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Remove-Item (Join-Path $env:TEMP 'cloudflared.exe') -Force -ErrorAction SilentlyContinue Expected Telemetry
Sysmon Event ID 1 for %TEMP%\cloudflared.exe with tunnel --url; Event ID 22 for *.trycloudflare.com / argotunnel.com; Event ID 3 to port 7844.
Expected Detection
All three arms fire (TunnelScore = 3); KQL, SPL, EQL and YARA-L rules alert.
Attempts `cloudflared service install` with a deliberately invalid token to produce the persistence command line and service creation artifacts without binding to any real account. Run only on a lab host.
Command
$exe = (Get-Command cloudflared -ErrorAction SilentlyContinue).Source
if (-not $exe) { Write-Host 'cloudflared not installed; skipping'; exit 0 }
& $exe service install eyJhIjoiMDAwMCIsInQiOiIwMDAwIiwicyI6IjAwMDAifQ== 2>&1 | Out-Host Cleanup
& (Get-Command cloudflared).Source service uninstall 2>$null
sc.exe delete Cloudflared 2>$null Expected Telemetry
Process create with 'service install <token>'; Windows service creation event 7045 / Sysmon registry events for the Cloudflared service key.
Expected Detection
CLI arm fires on service install; combine with edge DNS from the service start to reach TunnelScore 2.