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 — don't put it online.
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.
The /api/fetch endpoint fetches user-supplied URLs with no validation. Can access internal services on localhost.
The /api/ldap-search endpoint constructs LDAP filters from user input without escaping. Use wildcards and operators.
/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.
/page?page= (aliases /view?file=, /include?file=, /template?template=) reads local files with .. allowed and fetches http(s) refs remotely.
/api/exec?cmd= (aliases /run, /shell) passes cmd straight to the shell.
/reflect?input= echoes HTML with no escaping. /dom?xss= sinks it into innerHTML plus an eval() sink.
/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.
/graphql has introspection on and dumps users with no auth. Also: /actuator/env, /config.json, /.git/config, /server-status, /phpinfo.php, /api/env.
/api/blind?id= sleeps on SLEEP()/BENCHMARK — confirm injection with a stopwatch. /api/track does the same trick with your X-Forwarded-For header.
/fetch2 blocks the literal 169.254.169.254, then decodes decimal/hex IPs and fetches anyway. /go2 blocks http but waves //evil.com through.
/api/hpp?user=a&user=b joins repeats into SQL. /api/user-jsonp?callback= reflects the callback as executable JavaScript.
/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/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.
/frame changes email via a frameable GET link. /api/ssi?msg= evaluates <!--#exec cmd="id" -->. /api/unzip extracts ../ zip members outside the files dir.
Bookmarklet for friends: steal-env.js ⤴ (yes, a javascript: URI — that's the finding)
The /api/generate-token creates weak JWT tokens with hardcoded secret. /api/verify-token accepts 'none' algorithm.
The /api/register endpoint accepts JSON with is_admin=true in the request body, allowing users to register as admin.
The /api/login-api endpoint accepts hardcoded API keys like sk-1234567890abcdef or weak passwords.
/api/user/<id> returns any user's profile without authorization. Try IDs 1, 2, 3, etc.
/api/users concatenates the username parameter directly into SQL. Also vulnerable to column name injection via type parameter.
The /api/hash endpoint demonstrates weak hashing with MD5 and SHA1. Should use bcrypt or argon2.
The /api/deserialize endpoint uses pickle.loads() on base64-encoded user input. Allows arbitrary code execution.
Pickle deserialization is dangerous and can lead to RCE. Requires crafted payload.
The /api/reset-password endpoint generates a 4-digit reset token (only 10,000 possibilities). Easily brute-forced.
/api/search-advanced has SQL injection in query, filter (column name), and sort parameters.
Multiple injection points in a single endpoint.
The /api/debug endpoint exposes SECRET_KEY, JWT_SECRET, API_KEYS, database paths, and environment variables.
The /api/files endpoint lists directory contents. The dir parameter is vulnerable to path traversal.
The /api/file-upload endpoint accepts any file with any name. No file type or content validation.
Can upload malicious files including PHP/JSP shells.
The /api/comments endpoint stores user input without sanitization, then renders it directly in responses.
The /api/proxy endpoint forwards requests to any target URL with any headers/body. Acts as an open proxy.
Can be used to bypass firewall rules or attack internal systems.
The /api/sql-exec endpoint directly executes arbitrary SQL queries. No input validation whatsoever.
The /api/eval endpoint directly executes Python code with eval(). Allows remote code execution.
/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.