Exercises
Six exercises that walk through real OWASP Top 10 bugs and their minimum fixes. Try each before peeking; the answer is the exact line you would write in a PR.
Six OWASP exercises with fixes
EXAMPLE
# ============================================================
# Drill 1 — A01 Broken Access Control
# ============================================================
# VULNERABLE
# Route::get('/orders/{id}', fn($id) => Order::find($id));
#
# TASK: scope the lookup to the authenticated user.
# SOLUTION
# Route::get('/orders/{id}', fn(Request $r, $id) =>
# $r->user()->orders()->findOrFail($id)
# )->middleware('auth');
# ============================================================
# Drill 2 — A02 Cryptographic Failures
# ============================================================
# VULNERABLE (PHP)
# $hash = sha1($password . $salt);
#
# TASK: replace with a real password hasher.
# SOLUTION
# $hash = password_hash($password, PASSWORD_ARGON2ID);
# // verify
# if (!password_verify($password, $hash)) abort(401);
# ============================================================
# Drill 3 — A03 Injection
# ============================================================
# VULNERABLE (Node, pg)
# const sql = \`SELECT * FROM users WHERE email = '${req.query.email}'\`;
# const r = await pool.query(sql);
#
# TASK: parameterise.
# SOLUTION
# const r = await pool.query(
# 'SELECT id, name FROM users WHERE email = $1',
# [req.query.email],
# );
# ============================================================
# Drill 4 — A05 Security Misconfiguration
# ============================================================
# VULNERABLE
# In .env on production: APP_DEBUG=true
#
# TASK: ship a CI check that fails when prod config exposes debug mode.
# SOLUTION (bash)
# # scripts/check-prod-config.sh
# if grep -E '^(APP_DEBUG|DEBUG)=true' .env.production; then
# echo 'debug mode is on in production config' >&2; exit 1
# fi
# # In CI:
# # - name: prod config sanity; run: bash scripts/check-prod-config.sh
# ============================================================
# Drill 5 — A07 Authentication
# ============================================================
# VULNERABLE
# Login does not rotate session id after password change.
#
# TASK: regenerate the session on every auth event.
# SOLUTION (Laravel)
# public function changePassword(Request $r) {
# $user = $r->user();
# $user->update(['password' => Hash::make($r->password)]);
# $r->session()->regenerate(); # new session id
# auth()->logoutOtherDevices($r->password); # forces others off
# return back();
# }
# ============================================================
# Drill 6 — A10 SSRF
# ============================================================
# VULNERABLE
# $pdf = file_get_contents($_GET['url']); // server-side fetch of user-supplied URL
#
# TASK: validate scheme + block private ranges.
# SOLUTION
# function safe_fetch(string $url): string {
# $parts = parse_url($url);
# if (($parts['scheme'] ?? '') !== 'https') abort(400);
# $host = $parts['host'] ?? '';
# $ip = gethostbyname($host);
# if (filter_var($ip, FILTER_VALIDATE_IP,
# FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false)
# abort(400, 'private range');
# return file_get_contents($url);
# }
# Also: enable IMDSv2 on EC2 so SSRF cannot read instance creds.
# ============================================================
# Bonus — the WORST 'fix' to ship
# ============================================================
# - Telling the user to 'just be more careful'
# - Hiding the vulnerable endpoint behind 'security through obscurity'
# - Stripping characters with regex instead of parameterising
# - Lowering the severity to 'wont fix' because exploitation is 'hard'
# ============================================================
# Scoring
# ============================================================
# 6 / 6 -> ready for security code review
# 4 / 6 -> revisit owasp/cheatsheet
# < 6 -> a focused half-day with OWASP cheat sheet series
Why it matters
Every Top 10 fix should be expressible as a unit test on the route. The moment your test suite says "POST /orders/123 returns 403 when /123 belongs to another user", you have permanent protection against that specific bug class — and CI gates the next refactor against re-introducing it.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…