A self-hosted "exploit server" for hosting CSRF/XSS/clickjacking proof-of-concepts while doing authorized security testing, CTF challenges, or lab work — the same idea as PortSwigger's Web Security Academy exploit server, but under your own domain.
- Craft a response: set a file path, HTTP headers, and body, then store it.
- Public delivery: each stored exploit is served at
https://exploit.univel.uz/u/<your-id>/<path>. - Access log: see every request (IP, user agent, timestamp) your exploit received.
- Auth: passwordless magic-link login by email (via Resend) — no passwords to manage. You must be signed in to store an exploit; if you try to store one while signed out, your draft is kept and you're sent to sign in, then brought right back to it.
- Export/import: back up or migrate your exploits as JSON.
- Backend: FastAPI + PostgreSQL
- Frontend: React (Vite)
- Reverse proxy inside the frontend container: nginx (serves the SPA, proxies
/apiand/uto the backend) - Exposure: Cloudflare Tunnel (
cloudflared) — no inbound ports need to be opened on the VPS - Everything runs via Docker Compose
cp .env.example .env
# edit .env — at minimum set JWT_SECRET; RESEND_API_KEY can stay empty locally
# (magic links are logged to the backend console instead of emailed)
docker compose up --buildFrontend dev server (hot reload) instead of the built container, if you prefer:
cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload
cd frontend && npm install && npm run dev- Provision the VPS (Ubuntu), then install Docker:
curl -fsSL https://get.docker.com | sh - Clone this repo on the VPS:
git clone https://github.com/<you>/exploit-server.git cd exploit-server cp .env.example .env
- Fill in
.env:JWT_SECRET: a long random string (openssl rand -base64 48)POSTGRES_PASSWORD: a strong passwordRESEND_API_KEY: from your Resend dashboardEMAIL_FROM: an address on a domain you've verified in Resend (Resend won't send "from" a gmail.com address — verifyunivel.uzin Resend and use e.g.noreply@univel.uz; until then you can use Resend's shared test senderonboarding@resend.dev)PUBLIC_BASE_URL:https://exploit.univel.uz
- Set up the Cloudflare Tunnel (so the VPS needs no open inbound ports, and TLS is handled by
Cloudflare):
- Cloudflare dashboard → Zero Trust → Networks → Tunnels → Create a tunnel (Cloudflared)
- Add a public hostname:
exploit.univel.uz→ servicehttp://frontend:80 - Copy the tunnel token into
CLOUDFLARE_TUNNEL_TOKENin.env
- Start everything:
docker compose up -d --build
- Visit
https://exploit.univel.uz— sign in with your email, and you're ready to craft exploits.
cd exploit-server
git pull
docker compose up -d --build(A GitHub Actions workflow that SSHes in and runs this automatically can be added once you share your VPS host/SSH details — ask and it can be wired up.)
GET /api/exploits/export/all(used by the "Export all" button on the dashboard) downloads all of your exploits as JSON.- The "Import" button on the dashboard restores from that JSON (upserts by file path).
- The Postgres data itself lives in the
pgdataDocker volume; back it up withdocker compose exec postgres pg_dump -U exploit exploit_server > backup.sql.
- This tool serves arbitrary attacker-controlled HTML/JS to whoever visits a stored exploit's public URL — only use it against systems you're authorized to test, and don't paste secrets into exploit bodies.
- Sessions are httpOnly,
Secure,SameSite=LaxJWT cookies; magic-link tokens are single-use, expire in 15 minutes, and are stored hashed (SHA-256), never in plaintext.