Local only This shop is broken on purpose for scanner practice. Keep it on 127.0.0.1 — don't put it online.
Vulnshop. second-hand merch, first-hand mistakes

A practice shop · runs on 127.0.0.1 only

A junk shop that trusts everything it's told.

12 things on the shelf. Search box builds SQL by hand, the greeter page renders what you type as a template, and /api/exec?cmd=id does exactly what it looks like. It's all on purpose — point a scanner at it and take notes.

Sign in as admin / admin123. Docs at /api/docs.

11Orders placed
12Items for sale
5Accounts
0Things fixed

What's wrong with it

Full list →

On the shelf

All 12 →

A few loose floorboards

Try them in the lab →

These go somewhere real. The search box runs your words as SQL, /greet?name={{7*7}} answers 49, and /fetch?url=file:///etc/passwd reads files. Nothing here is faked.

GET

SQL Injection

The q parameter is concatenated into SQL with no binding.

/search?q=shirt
GET

Reflected XSS + SSTI

User input rendered with Jinja2 without escaping.

/greet?name=guest
GET

Command Injection

Host parameter passed to shell ping command.

/api/ping?host=127.0.0.1
GET

Path Traversal

File parameter not sanitized for .. sequences.

/download?file=readme.txt
GET

Open Redirect

URL parameter passed directly to redirect().

/redirect?url=/
GET

IDOR

Order endpoint returns any order by id, no ownership check.

/api/orders/1
POST

XXE (XML External Entity)

XML parser accepts external entity declarations.

/api/import
GET

SSRF (fetch any URL)

No allowlist: http(s), file:// and cloud metadata 169.254.169.254.

/fetch?url=http://127.0.0.1:5000/health
GET

SSRF aliases

Same sink under many param names scanners fuzz.

/api/ssrf?url=http://127.0.0.1:5000/health
GET

LFI / RFI

Local file read with ../ allowed; http(s) refs fetched remotely.

/page?page=readme.txt
GET

Reflected XSS (raw)

Input echoed with no escaping.

/reflect?input=guest
GET

RCE (command exec)

cmd parameter passed to shell.

/api/exec?cmd=id
GET

Open Redirects

Unvalidated redirects under several paths.

/go?url=/
GET

CSRF state change

Password change via GET, no token.

/change-password?user=bob&password=hacked
GET

Blind SQLi (time-based)

SLEEP()/BENCHMARK in id stall the response - timing oracle.

/api/blind?id=1
GET

Header SQLi

X-Forwarded-For / User-Agent concatenated into SQL.

/api/track
POST

JWT kid SQLi

JWT kid header interpolated into a key-lookup query.

/api/verify-kid
GET

HPP + JSONP XSS

Repeated params joined into SQL; callback reflected as JS.

/api/user-jsonp?callback=showUser&id=1
GET

SSRF filter bypass

Blocklist rejects literals, then decodes decimal/hex IPs and fetches.

/fetch2?url=http://127.0.0.1:5000/health
GET

Redirect validation bypass

Naive http-block lets //evil.com through.

/go2?url=//example.com
GET

Schema + table dump

Full DB schema and raw table access for sqlmap.

/api/schema
GET

Broken auth (enum/2FA/juggle)

User enumeration, guessable 2FA, type-juggling login.

/api/enumerate?username=admin
GET

IDOR API keys + mass assignment

Anyone reads anyone's key; profile update sets is_admin.

/api/apikey?user=admin
GET

OAuth redirect theft

redirect_uri never validated; code leaks to attacker domain.

/oauth/authorize?client_id=vulnshop&redirect_uri=https://example.com
POST

Zip Slip

Archive members with ../ write outside the files dir.

/api/unzip
GET

Backup files + console

database.sql, db.sqlite, app.py.bak and a PIN-shown console.

/database.sql
Start with admin / admin123 — same pair you'll find in /.env. Place an order, open its receipt at /api/orders/<id>, then change the number.