Common Web Vulnerability Classes¶
Prerequisites: Security Overview, Threat Modeling
Why This Exists¶
Threat Modeling teaches the process for finding where a system is exposed — trust boundaries, STRIDE, attack trees. This page is the other half: the specific, recurring vulnerability classes that process keeps turning up, the ones with a name and a known fix, that show up in almost every real security review regardless of what system you're looking at. XSS and CSRF already get real depth elsewhere on this site — Session Management covers XSS token theft and cookie defenses, OAuth2 & OIDC covers CSRF via the state parameter. This page covers the three that don't have a home yet: SQL injection, SSRF, and CORS misconfiguration — plus where WAF and DDoS mitigation actually fit, which is a narrower role than most candidates assume.
The interview signal isn't "can you name OWASP's list" — it's whether you can explain the mechanism well enough to say why a specific fix works and a specific almost-fix doesn't.
SQL Injection: Data and Code Sharing a Channel¶
The mechanism, stripped to its core: a query is built by concatenating a string template with user input, so the input can inject new SQL rather than just filling a value.
# VULNERABLE — user input becomes part of the SQL itself
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
# username = "admin' --" turns the query into:
# SELECT * FROM users WHERE username = 'admin' --' AND password = '...'
# Everything after -- is a SQL comment. The password check never runs.
# FIXED — parameterized query: input is DATA, never concatenated into the SQL text
cursor.execute(
"SELECT * FROM users WHERE username = %s AND password_hash = %s",
(username, hash_password(password)),
)
Why the fix actually works, not just "looks safer": in a parameterized query, the driver sends the SQL template and the parameters as two separate things over the wire — the database parses the query structure before it ever sees the parameter values, so there's no point at which user input could be interpreted as SQL syntax. String-concatenating input and then trying to "sanitize" it (stripping quotes, escaping characters) is fighting the same battle from a weaker position — it's a blocklist against an open-ended set of encodings and edge cases (Unicode normalization, database-specific escape quirks), and blocklists are exactly the failure mode threat modeling warns about: you're enumerating known attacks instead of removing the vulnerable channel.
ORMs don't automatically save you
Most ORMs parameterize queries by default, which is why raw SQL injection is rarer than it used to be — but every ORM has an escape hatch for raw queries or dynamic ORDER BY/table-name interpolation (you can't parameterize an identifier, only a value), and that escape hatch is exactly where injection bugs still show up in modern codebases.
SSRF: Tricking the Server Into Making the Request¶
Server-Side Request Forgery is different from most web vulnerabilities in a specific way: the attacker isn't attacking the user, they're attacking the server itself, by getting it to make an HTTP request the attacker chooses.
sequenceDiagram
participant Attacker
participant App as App server<br/>("fetch this image URL")
participant Internal as Internal service<br/>(no auth, trusts the VPC)
participant Meta as Cloud metadata endpoint<br/>(169.254.169.254)
Attacker->>App: POST /avatar { "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }
App->>Meta: fetches the "image" URL, as itself
Meta-->>App: returns IAM credentials (the app has network access; metadata endpoint doesn't re-check who asked)
App-->>Attacker: renders/returns the "image" (which is actually the credentials)
Note over Attacker,Meta: The attacker never touched the internal network directly —<br/>the app server did it on their behalf, using the app's own network position Any feature that fetches a URL on the server's behalf — an avatar-from-URL uploader, a webhook validator that "pings" the target URL, a PDF generator that renders a remote page — is a candidate. The attacker supplies a URL pointing at something the server can reach but the attacker can't reach directly: an internal admin panel with no auth because it "trusts anything inside the VPC," or a cloud metadata endpoint that hands out credentials to whatever's asking from that instance, with no separate authentication of its own.
Mitigation, layered (no single one is sufficient alone):
- Allowlist, not blocklist, of destinations — if the feature only ever needs to fetch from a known set of domains, allowlist those explicitly rather than trying to blocklist "don't fetch internal IPs," which is easy to bypass (DNS rebinding, IPv6 representations of the same address, redirects that resolve to an internal IP after the initial check passed).
- Network-level segmentation — the fetching service shouldn't have network access to internal admin services or the cloud metadata endpoint in the first place; this is the same "least privilege" discipline as zero trust architecture applied to a server's own outbound reach, not just inbound auth.
- Disable following redirects, or re-validate the destination after each hop — a URL that passes the allowlist check can still redirect to an internal address, and a naive fetch that blindly follows redirects re-opens the exact hole the allowlist was supposed to close.
CORS Misconfiguration: A Browser Rule, Not a Server Firewall¶
CORS (Cross-Origin Resource Sharing) is frequently misunderstood as a general authorization mechanism the server uses to block bad callers. It isn't — CORS is a protocol enforced by the browser, on behalf of the user, that decides whether JavaScript running on evil.com may make certain cross-origin requests and read their responses from your-api.com. For a CORS-safelisted ("simple") request, the actual request is sent and CORS gates access to the response. For a request that requires preflight, the browser sends OPTIONS first and withholds the actual request unless the server's CORS response permits it. Either way, CORS is browser policy, not authentication for the API.
Why this is a backend concern, not a frontend one
The enforcement happens in someone else's browser, but the thing being configured — the Access-Control-Allow-Origin allowlist, whether credentials are permitted — is a header your API sets. This is backend surface: it's the same category of decision as an auth check or a rate limit, not a UI concern, and it's exactly the kind of header-level misconfiguration that shows up in a backend security review.
sequenceDiagram
participant Page as evil.com (page in victim's browser)
participant Browser
participant API as your-api.com
Page->>Browser: fetch("https://your-api.com/account", {credentials: "include"})
Browser->>API: sends this safelisted GET with cookies only if<br/>their SameSite policy permits cross-site use<br/>(typically SameSite=None; Secure)
API-->>Browser: response (e.g. account balance)
Browser->>Browser: check Access-Control-Allow-Origin header
alt Server correctly scoped the origin
Browser--xPage: BLOCKED — evil.com's script cannot read the response
else Server sent Access-Control-Allow-Origin: * (or reflected evil.com) + credentials allowed
Browser->>Page: response delivered — evil.com's script reads the victim's account data
end The dangerous misconfiguration, specifically: Access-Control-Allow-Origin: * combined with Access-Control-Allow-Credentials: true — browsers actually reject that exact combination (the spec forbids a wildcard origin alongside credentials), which pushes sloppy implementations toward the real hole: reflecting whatever Origin header the request sent back as the allowed origin, which technically satisfies "not a wildcard" while functionally allowing every origin. A correctly configured API validates the incoming Origin against an actual allowlist of known frontend origins, not "whatever showed up in the header."
credentials: "include" tells fetch that credentials may be included; it does not override cookie policy. In the cross-site example above, a browser sends a cookie only if attributes such as SameSite=None; Secure, domain, path, and expiry permit it. SameSite=Lax and SameSite=Strict normally prevent that cookie from accompanying a cross-site fetch, which is why SameSite is useful defense in depth against CSRF.
CORS protects the browser's same-origin model — it does nothing for non-browser clients
A curl request, a mobile app, or a server-to-server call was never subject to CORS in the first place — the browser is the only thing that enforces it, because it's the browser's own same-origin policy CORS is relaxing. If an API needs to reject unauthorized callers generally (not just browser scripts from unauthorized origins), that's authentication and authorization — CORS headers alone are not an access-control mechanism.
WAF and DDoS Mitigation: A Backstop, Not a Fix¶
A Web Application Firewall sits in front of the application and pattern-matches requests against known attack signatures (a request body that looks like a SQL injection payload, a path traversal attempt) before they reach the app. It's valuable as a second layer — catching known attack patterns generically, across every endpoint, including ones a specific developer forgot to harden — but it is not a substitute for fixing the underlying vulnerability. A WAF rule is a blocklist, and blocklists miss novel encodings and edge cases the same way ad-hoc SQL-injection sanitization does; the difference is a WAF is a broad, centrally-managed blocklist rather than one improvised per endpoint. The correct mental model: WAF buys time and catches the obvious stuff; parameterized queries, output encoding, and a real CORS allowlist are what actually closes the hole.
DDoS mitigation is a related but distinct concern — it's about request volume overwhelming capacity, not a malicious payload exploiting a code flaw. The standard layers: network/transport-level scrubbing (a CDN or dedicated DDoS mitigation service absorbing volumetric floods before they reach your infrastructure at all — see Cloud Load Balancers for where this sits in the request path), rate limiting at the application edge (see Rate Limiting) for abuse that looks like legitimate traffic shape but at damaging volume, and autoscaling as the last line, absorbing load that gets through the first two layers rather than preventing it.
Interview Questions¶
Q: Why does a parameterized query prevent SQL injection when string-escaping the input doesn't (as reliably)?
"A parameterized query sends the SQL structure and the user-supplied values as two separate things to the database — the database parses the query shape before it ever looks at the values, so there's no way for a value to be interpreted as part of the SQL syntax. Escaping tries to neutralize dangerous characters in a string that's still going to be concatenated into the query text, which means it has to correctly handle every character encoding and database-specific escaping quirk that could still slip something through — it's a blocklist against an open-ended attack surface, where parameterization removes the vulnerable channel entirely."
Q: A teammate says 'we're safe from CSRF because we use CORS to restrict which origins can call our API.' What's wrong with that reasoning?
"CORS and CSRF protect against different things and aren't substitutes for each other. For a safelisted cross-origin request, the browser can send the request — potentially with the user's cookies — and CORS then determines whether the calling script may read the response. Requests with non-safelisted methods or headers are preflighted, and the browser withholds the actual request if that check fails. But a classic CSRF attack deliberately uses a form or another safelisted request that needs only to cause a side effect, not read the result, so denying CORS access to the response does not stop it. Real CSRF defenses include an unpredictable CSRF token, appropriate SameSite cookies, and server-side Origin/Referer validation as defense in depth."
Q: A team building an 'import from URL' feature (users paste a link, the server fetches and processes it) asks you to review it for SSRF before launch. Walk through what you'd check.
"First I'd map what the fetching service can actually reach on the network — if it has no route to internal admin services or the cloud metadata endpoint in the first place, the blast radius of an SSRF bug is much smaller even if the application-level checks have a gap; that's the network-segmentation layer, and I'd treat it as the real backstop, not the allowlist. Then at the application layer: is the destination validated against an explicit allowlist of expected domains, or a blocklist trying to exclude internal IP ranges — because blocklists here are specifically weak against DNS rebinding (a domain that resolves to a public IP at check-time and an internal IP at fetch-time) and IPv6/alternate-encoding representations of internal addresses. I'd also check redirect handling specifically: does the fetch follow redirects, and if so, is the destination re-validated after each hop, because an allowlisted URL that 302s to an internal address defeats an allowlist that only checked the original URL.
Beyond the mechanics, I'd ask what actually happens with the fetched content — if it's rendered back to the user or another user, that's a second vulnerability class (stored content becoming an XSS vector) layered on top of the SSRF risk, and I'd want both addressed before this ships, not just SSRF in isolation."
Key Takeaways¶
Remember
- SQL injection is a data/code confusion bug — parameterized queries fix it structurally by sending the query shape and the values separately; escaping is a weaker blocklist against the same problem.
- SSRF attacks the server's own network position, not the user — the fix is layered: network segmentation (the server shouldn't be able to reach internal services at all), an allowlist of destinations, and re-validating redirects.
- CORS is browser-enforced policy, not API authentication — simple requests may be sent before the response is blocked; preflighted requests are withheld unless the preflight succeeds; non-browser callers are not constrained by CORS at all.
Access-Control-Allow-Originreflecting the request'sOriginheader is the common real-world CORS bug — it technically avoids a wildcard while functionally allowing every origin.- A WAF and DDoS mitigation are backstops, not fixes — they catch known attack signatures and absorb volume, but the actual defense is the underlying code (parameterized queries, allowlists, rate limiting) they're layered in front of.
Back to: Security