SQL Injection
The search q parameter is concatenated directly into the SQL query with no parameter binding. A quote ends the string literal.
127.0.0.1 — never put it on the internet.
Security lab
Click any link to see the vulnerable endpoint. Use the forms below to fire a payload — responses come back on the same page.
The search q parameter is concatenated directly into the SQL query with no parameter binding. A quote ends the string literal.
The name parameter on /greet is rendered as template source, so both HTML and Jinja2 expressions execute.
The host parameter is passed to sh -c "ping -c 3 {host}" without escaping. Semicolons and pipes run arbitrary commands.
The file parameter is joined to /app/files/ with no check for .. sequences. Read any accessible file.
The url parameter is passed to Flask's redirect() with no validation. External URLs work just as well.
The /api/import endpoint parses XML with resolve_entities=True, allowing entity expansion and file disclosure.
/api/orders/{id} returns any order by id alone, never checking if you own it. Try any integer: 1, 2, 3, 10, 100.
/admin requires no login and dumps the full user and order table. It is also listed in robots.txt under Disallow as a hint.
/.env and /backup.zip are routed endpoints that serve fake secrets. Real apps expose these accidentally.
/boom raises an error and renders the full Python traceback as HTML. Production code leaks these in 500 responses.
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.
The Flask session signing key is a hardcoded string in app.py and also printed in /.env.
Real session cookies can be forged offline.
The session cookie is set with no Secure, HttpOnly, or SameSite flags.
It is sent over HTTP and readable from JavaScript.
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.