
Generate Caddy redirector configs from Cobalt Strike or Sliver C2 profiles.
CaddySmith is a small Python script that reads a Cobalt Strike or Sliver C2 profile and forges a Caddy web server config out of it. The generated Caddyfile turns a regular Linux box into a redirector: legitimate beacon traffic gets reverse-proxied to your team server, and anything else (scanners, search bots, blue team probes, random curl) gets bounced to a decoy URL.
I wrote this because Caddy is a much friendlier web server to stand up quickly than Apache, single static binary, automatic Let's Encrypt certs, no a2enmod dance.
Authorized engagements only. This is offensive security tooling. Only run it against environments you have written permission to test.
CaddySmith auto-detects the format from the file contents:
set uri "/foo" directives. Each HTTP-GET / HTTP-POST URI becomes its own exact-path route with the profile's client headers enforced.path /api* /static* /resources*) and doing a substring match on the Chrome build number from the UA (which survives Sliver's per-platform UA rewrites).You can also force a specific parser with --profile-type cobaltstrike or --profile-type sliver.
Given a profile file, the script pulls out:
--server-name)set host_stage)Then it builds a Caddyfile that:
.env, /wp-admin, .php, etc.)pip install needed)python3 caddysmith.py my.profile \
--backend https://teamserver.internal:443 \
--decoy https://www.example.com/ \
--server-name redirector.example.com \
--email [email protected] \
--forbid-http \
-o redirector.caddy
This writes the generated config to redirector.caddy and prints a summary of what it did to stderr.
Copy the generated file to the redirector and run Caddy against it. You need the --adapter caddyfile flag because Caddy defaults to JSON config:
caddy run --config /etc/caddy/redirector.caddy --adapter caddyfile
To reload a running instance with an updated config:
caddy reload --config /etc/caddy/redirector.caddy --adapter caddyfile
If Caddy warns about formatting inconsistencies, clean them up with:
caddy fmt --overwrite /etc/caddy/redirector.caddy
Drop the file in /etc/caddy/ and import it from your main Caddyfile:
# /etc/caddy/Caddyfile
import /etc/caddy/redirector.caddy
If you passed --email (recommended), the generated snippet already contains the global options block, so the main Caddyfile only needs the import line. If you didn't, add the email manually to a { } block above the import.
Then validate and reload:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Caddy will auto-provision a Let's Encrypt cert for the domain in --server-name as long as the A record points at the redirector.
Strict is what you usually want. Lax is useful when you're debugging why a real beacon isn't connecting.
Say you have a profile that mimics an Amazon endpoint and you want to deploy it on redirector.0xtb.sh:
python3 caddysmith.py amazon.profile \
--backend https://10.1.1.10:443 \
--decoy https://www.amazon.com/ \
--server-name redirector.0xtb.sh \
--forbid-http \
--policy strict \
-o /etc/caddy/redirector.caddy
The summary shows you exactly which routes got built, e.g.:
Routes built: 2
- [profile-get] /broadcast
- [profile-post] /1/events/com.amazon.csm.csa.prod
If you regenerate and see no routes, the script probably failed to parse your profile — check the warnings on stderr.
For a Sliver implant config (JSON):
python3 caddysmith.py sliver-implant.json \
--backend https://10.1.1.10:443 \
--decoy https://www.amazon.com/ \
--server-name redirector.0xtb.sh \
--email [email protected] \
--forbid-http \
--policy strict \
-o /etc/caddy/redirector.caddy
The summary will tell you that Sliver was detected and show the prefix route:
Profile type: sliver
Routes built: 1
- [sliver] /api /public /resources /services /static (prefix)
Because Sliver generates URIs randomly from path × file × extension combinations, the generated path matcher uses prefix globs (path /api* /public* /resources* /services* /static*) rather than exact paths. The User-Agent matcher uses a substring of the Chrome build number (e.g. 3921.146), which Sliver preserves across its per-platform UA rewrites.
# Plain HTTP should be 403'd (if you used --forbid-http)
curl -I http://redirector.0xtb.sh/
# Bare hostname should redirect to the decoy
curl -kI https://redirector.0xtb.sh/
# Bad UA should also redirect
curl -kI -A "curl/8.4.0" https://redirector.0xtb.sh/broadcast
# A request with the right UA + path should proxy through (200)
# You need to also send all the profile's client headers in strict mode.
set host_stage "true" (or doesn't set it at all), the script will warn you and skip stager URIs. Add set host_stage "false"; to your profile, or pass the stager URIs explicitly via --extra-uri./api, /static, etc. Scanner probes that happen to use those prefixes (e.g. /api/.env) will be sent to the team server rather than blocked locally — but Sliver's own HTTP transport authenticates via implant ID, so unauthorized requests get rejected at the C2 layer. The UA gating still keeps most scanners out.--backend. If you need multiple team servers, run the script multiple times and merge by hand.set uri "/path1 /path2"; works (multiple paths on one line), but unusual formatting might trip up the parser. Check the route list in the summary to confirm.tls_insecure_skip_verify. If your backend has a real cert, delete that line from the generated file.The Apache-based Malleable-Redirector was the starting point for what this script generates, same three-track URI model (profile URIs / extra URIs / lax URIs), same policy modes, same general layout. CaddySmith just translates the output to Caddy syntax instead of Apache .htaccess.
MIT
| Flag | Default | What it does |
|---|
profile | (required) | Path to the .profile file |
--backend | https://teamserver.local:443 | Where to proxy matched traffic |
--decoy | https://www.example.com/ | Where unmatched traffic redirects |
--server-name | c2.example.com | Your redirector's domain name |
--policy | strict | strict, lax, or none |
--profile-type | auto | Force cobaltstrike or sliver (default: auto-detect) |
--extra-uri PATH | — | Extra URI to proxy (with UA check). Can repeat. |
--lax-uri PATH | — | Extra URI to proxy (no checks). Can repeat. |
--allow-ua STRING | — | Extra UA allowed on --extra-uri routes. Can repeat. |
--forbid-http | off | Return 403 on plain HTTP |
--email EMAIL | — | Email for Let's Encrypt registration and renewal notices |
-o, --output FILE | stdout | Write the config to a file |