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

Security lab

85+ vulnerabilities · indexed and runnable

Click any link to see the vulnerable endpoint. Use the forms below to fire a payload — responses come back on the same page.

Web vulnerabilities (scanner-detectable)

GET

SQL Injection

The search q parameter is concatenated directly into the SQL query with no parameter binding. A quote ends the string literal.

/search?q=shirt
GET

Reflected XSS + SSTI

The name parameter on /greet is rendered as template source, so both HTML and Jinja2 expressions execute.

/greet?name=guest
GET

Command Injection

The host parameter is passed to sh -c "ping -c 3 {host}" without escaping. Semicolons and pipes run arbitrary commands.

/api/ping?host=127.0.0.1
GET

Path Traversal

The file parameter is joined to /app/files/ with no check for .. sequences. Read any accessible file.

/download?file=readme.txt
GET

Open Redirect

The url parameter is passed to Flask's redirect() with no validation. External URLs work just as well.

/redirect?url=/
POST

XXE (XML External Entity)

The /api/import endpoint parses XML with resolve_entities=True, allowing entity expansion and file disclosure.

/api/import (POST)
POST

SSRF (Server-Side Request Forgery)

The /api/fetch endpoint fetches user-supplied URLs with no validation. Can access internal services on localhost.

/api/fetch (POST)
POST

LDAP Injection

The /api/ldap-search endpoint constructs LDAP filters from user input without escaping. Use wildcards and operators.

/api/ldap-search (POST)

SSRF · LFI/RFI · RCE · Redirects (GET, scanner-friendly)

GET

SSRF (GET, any URL)

/fetch?url= (aliases: /image, /avatar, /preview, /thumbnail, /proxy, /api/ssrf) fetches any URL with no allowlist. Supports file:// and cloud metadata 169.254.169.254.

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

LFI / RFI

/page?page= (aliases /view?file=, /include?file=, /template?template=) reads local files with .. allowed and fetches http(s) refs remotely.

/page?page=readme.txt
GET

RCE (GET command exec)

/api/exec?cmd= (aliases /run, /shell) passes cmd straight to the shell.

/api/exec?cmd=id
GET

Reflected XSS (raw)

/reflect?input= echoes HTML with no escaping. /dom?xss= sinks it into innerHTML plus an eval() sink.

/reflect?input=<script>alert(1)
GET

Open Redirects + CSRF-GET

/go?url=, /out?next=, /r?u= redirect anywhere. /change-password?user=&password=, /promote?user=, /transfer?to=&amount= change state via GET with no CSRF token.

/go?url=https://example.com
GET

Disclosure & GraphQL

/graphql has introspection on and dumps users with no auth. Also: /actuator/env, /config.json, /.git/config, /server-status, /phpinfo.php, /api/env.

/graphql?query={users{id}}

Second wave — blind SQLi · filter bypasses · broken auth · backups

new
GET

Blind SQLi (time-based)

/api/blind?id= sleeps on SLEEP()/BENCHMARK — confirm injection with a stopwatch. /api/track does the same trick with your X-Forwarded-For header.

/api/blind?id=1
GET

SSRF filter bypass + open-redirect bypass

/fetch2 blocks the literal 169.254.169.254, then decodes decimal/hex IPs and fetches anyway. /go2 blocks http but waves //evil.com through.

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

HPP + JSONP XSS

/api/hpp?user=a&user=b joins repeats into SQL. /api/user-jsonp?callback= reflects the callback as executable JavaScript.

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

Broken auth bundle

/api/enumerate tells you who exists, /api/2fa accepts 0000, /api/loose-login falls to array passwords, and /oauth/authorize sends codes to any redirect_uri.

/api/enumerate?username=admin
GET

Schema dump + backups + console

/api/schema hands sqlmap the table list. /database.sql, /db.sqlite and /app.py.bak leak everything. /console prints its own PIN, then runs your command.

GET

Clickjacking + SSI + Zip Slip

/frame changes email via a frameable GET link. /api/ssi?msg= evaluates <!--#exec cmd="id" -->. /api/unzip extracts ../ zip members outside the files dir.

/frame (frame me)

Bookmarklet for friends: steal-env.js ⤴ (yes, a javascript: URI — that's the finding)

Authentication & Access Control (6 vulnerabilities)

POST

JWT Algorithm Downgrade

The /api/generate-token creates weak JWT tokens with hardcoded secret. /api/verify-token accepts 'none' algorithm.

/api/verify-token (POST)
POST

Mass Assignment (Privilege Escalation)

The /api/register endpoint accepts JSON with is_admin=true in the request body, allowing users to register as admin.

/api/register (POST)
POST

Weak API Key Authentication

The /api/login-api endpoint accepts hardcoded API keys like sk-1234567890abcdef or weak passwords.

/api/login-api (POST)
GET

IDOR on User Profiles

/api/user/<id> returns any user's profile without authorization. Try IDs 1, 2, 3, etc.

/api/user/1
GET

SQL Injection (User Listing)

/api/users concatenates the username parameter directly into SQL. Also vulnerable to column name injection via type parameter.

/api/users?username=admin

Cryptography & Data Handling (4 vulnerabilities)

POST

Weak Cryptography (MD5/SHA1)

The /api/hash endpoint demonstrates weak hashing with MD5 and SHA1. Should use bcrypt or argon2.

/api/hash (POST)
POST

Insecure Deserialization (Pickle)

The /api/deserialize endpoint uses pickle.loads() on base64-encoded user input. Allows arbitrary code execution.

/api/deserialize (POST)

Pickle deserialization is dangerous and can lead to RCE. Requires crafted payload.

POST

Weak Random Token Generation

The /api/reset-password endpoint generates a 4-digit reset token (only 10,000 possibilities). Easily brute-forced.

/api/reset-password (POST)
POST

SQL Injection (Multiple Parameters)

/api/search-advanced has SQL injection in query, filter (column name), and sort parameters.

/api/search-advanced (POST)

Multiple injection points in a single endpoint.

Information Disclosure & File Handling (5 vulnerabilities)

GET

Debug Information Disclosure

The /api/debug endpoint exposes SECRET_KEY, JWT_SECRET, API_KEYS, database paths, and environment variables.

View Debug Info
GET

Directory Listing

The /api/files endpoint lists directory contents. The dir parameter is vulnerable to path traversal.

/api/files?dir=/app/files
POST

Arbitrary File Upload

The /api/file-upload endpoint accepts any file with any name. No file type or content validation.

/api/file-upload (POST)

Can upload malicious files including PHP/JSP shells.

POST

Stored XSS (Comments)

The /api/comments endpoint stores user input without sanitization, then renders it directly in responses.

/api/comments (POST)

Advanced Injection & Proxy (4 vulnerabilities)

POST

Open Proxy / Request Forwarding

The /api/proxy endpoint forwards requests to any target URL with any headers/body. Acts as an open proxy.

/api/proxy (POST)

Can be used to bypass firewall rules or attack internal systems.

POST

Direct SQL Execution

The /api/sql-exec endpoint directly executes arbitrary SQL queries. No input validation whatsoever.

/api/sql-exec (POST)
POST

Code Evaluation (Python eval())

The /api/eval endpoint directly executes Python code with eval(). Allows remote code execution.

/api/eval (POST)

Data exposure (4 real but unguarded)

GET

IDOR (Insecure Direct Object Reference)

/api/orders/{id} returns any order by id alone, never checking if you own it. Try any integer: 1, 2, 3, 10, 100.

/api/orders/1
GET

Unauthenticated Admin Panel

/admin requires no login and dumps the full user and order table. It is also listed in robots.txt under Disallow as a hint.

Open panel
GET

Exposed Secrets & Backups

/.env and /backup.zip are routed endpoints that serve fake secrets. Real apps expose these accidentally.

GET

Stack Trace Disclosure

/boom raises an error and renders the full Python traceback as HTML. Production code leaks these in 500 responses.

Trigger error

Configuration issues (4 found by reading the code)

SAST / Code Review

Plaintext Passwords

User passwords are stored and compared as plaintext in the database. No hashing, no salt. See app.py login route and the users table schema.

SAST / Code Review

Hardcoded Secrets

The Flask session signing key is a hardcoded string in app.py and also printed in /.env. Real session cookies can be forged offline.

SAST / Code Review

Insecure Session Cookie

The session cookie is set with no Secure, HttpOnly, or SameSite flags. It is sent over HTTP and readable from JavaScript.

SAST / Code Review

Missing Security Headers

No response includes X-Frame-Options, X-Content-Type-Options, Content-Security-Policy, or Strict-Transport-Security. Scanners flag all of them.

Safe to exploit: VulnShop binds to 127.0.0.1:5000 only and is meant to stay in Docker. Tear down with docker-compose down -v when done. All passwords and secrets shown here are intentional; changing them would break the demo.