Web Application Vulnerabilities
XSS, CSRF, SQL/NoSQL injection, SSRF, SSTI, path traversal, command injection, insecure deserialization and file-upload threats.
17 questions
JuniorTheoryVery commonWhat is SQL injection and how do you defend against it?
What is SQL injection and how do you defend against it?
SQL injection happens when untrusted input is concatenated into a query, letting an attacker alter its structure — bypass auth, dump data, even run commands. The defense is parameterized queries: data travels separately from SQL, so input cannot change it. Least privilege limits the damage.
Common mistakes
- ✗Treating manual quote escaping as a reliable replacement for parameterization
- ✗Thinking stored procedures that concatenate input are protected
- ✗Assuming injection affects only reads, not writes/RCE
Follow-up questions
- →Why does a parameterized query make the query structure unchangeable by input?
- →How does least privilege on the DB account reduce the damage from injection?
JuniorTheoryVery commonWhat is XSS and how do you defend against it?
What is XSS and how do you defend against it?
XSS injects attacker JavaScript into a page that runs in the victim's browser, stealing sessions or acting as them. The primary defense is context-aware output encoding of untrusted data, backed by a Content Security Policy. httpOnly/secure flags reduce cookie theft but don't stop XSS.
Common mistakes
- ✗Confusing XSS with CSRF — they are different classes of attack
- ✗Treating the httpOnly/secure flags as sufficient protection against XSS itself
- ✗Escaping input instead of context-aware output encoding
Follow-up questions
- →How do stored, reflected, and DOM-based XSS differ?
- →How does a Content Security Policy limit execution of an injected script?
JuniorDebuggingCommonFind the RCE in the Flask nslookup wrapper
Find the RCE in the Flask nslookup wrapper
OS command injection — domain goes into a shell string with shell=True, so shell metacharacters run arbitrary commands (RCE). Fix: shell=False with an argument list, keeping input one argument, plus hostname validation.
Common mistakes
- ✗Treating it as a DoS or SSRF problem instead of command injection
- ✗Thinking a blacklist of dangerous characters is safer than an argument list and shell=False
- ✗Assuming shell=True is required to run an external command
Follow-up questions
- →Why does shell=False with an argument list eliminate the injection at its root?
- →Why is a blacklist of special characters an unreliable defense here?
JuniorTheoryCommonWhat is CSRF and under what conditions is a money endpoint vulnerable?
What is CSRF and under what conditions is a money endpoint vulnerable?
CSRF tricks a logged-in victim's browser into sending a forged state-changing request. POST /pay is exploitable only if the session rides in a cookie auto-sent cross-site, there is no anti-CSRF token, and the cookie lacks SameSite. Tokens and SameSite=Lax close it.
Common mistakes
- ✗Confusing CSRF with XSS (CSRF does not run a script on the victim)
- ✗Believing that POST or TLS alone protect against CSRF
- ✗Overlooking the role of the cookie's SameSite attribute
Follow-up questions
- →Why is a request with Content-Type application/json harder to forge via CSRF?
- →How does an anti-CSRF token confirm the request came from your page?
JuniorDebuggingCommonFind the vulnerability in reading a file by its name from the request
Find the vulnerability in reading a file by its name from the request
Path traversal — fileName is concatenated into the path unchecked, so ../ sequences climb out of open-media/ and read arbitrary files. Fix: normalize the resolved path and verify it stays inside the base directory, or map requests to an allowlist of filenames.
Common mistakes
- ✗Believing that filtering only the file extension stops directory traversal
- ✗Relying on stripping the '../' substring without normalizing the resolved path
- ✗Confusing path traversal with XSS because user input is present
Follow-up questions
- →Why must you validate the normalized path, not the raw string?
- →Why is an allowlist-of-names approach more reliable than filtering '../'?
MiddleTheoryCommonWhat is SSRF and how do attackers bypass a local-address filter?
What is SSRF and how do attackers bypass a local-address filter?
SSRF tricks the server into fetching an attacker-chosen URL, reaching internal services or cloud metadata the client cannot. Blocklists of localhost fall to alternate IP forms, open redirects, TOCTOU gaps, and DNS rebinding. Defend by validating the final resolved IP against an allowlist.
Common mistakes
- ✗Confusing SSRF with CSRF (the request direction is the opposite)
- ✗Treating a blocklist of the strings localhost/127.0.0.1 as sufficient protection
- ✗Ignoring blind SSRF, treating it as harmless
Follow-up questions
- →How does DNS rebinding bypass a check performed before the request?
- →Why is it more reliable to validate the final resolved IP rather than the URL string?
MiddleDebuggingOccasionalFind the blind SQL injection via text() in SQLAlchemy
Find the blind SQL injection via text() in SQLAlchemy
SQL injection — text() runs the concatenated f-string verbatim instead of parameterizing it, so table_name injects arbitrary SQL. Blind because nothing returns; only response timing confirms execution. Fix: bound parameters and an allowlist.
Common mistakes
- ✗Thinking
text()parameterizes the interpolated f-string - ✗Assuming a blind injection is impossible without visible output
- ✗Mistaking the injection for a race condition or information disclosure
Follow-up questions
- →How does an attacker extract data bit by bit through a time-based blind injection?
- →Why do bound parameters eliminate the injection while a blacklist does not?
MiddleDebuggingOccasionalFind the insecure deserialization of untrusted data
Find the insecure deserialization of untrusted data
Insecure deserialization — readObject instantiates arbitrary attacker-chosen classes from untrusted bytes, so a gadget chain of library classes runs code during deserialization (RCE). Fix: do not deserialize untrusted input; use JSON with an explicit schema or a class allowlist.
Common mistakes
- ✗Believing Java deserialization is type-safe and incapable of RCE
- ✗Thinking a try/catch around readObject removes the threat
- ✗Confusing it with XXE or with a leak through logging
Follow-up questions
- →What is a gadget chain and why is it enough for RCE without your own class?
- →Why is a safe data format (JSON) more reliable than native object serialization?
MiddleDebuggingOccasionalFind the DOM XSS in the postMessage handler
Find the DOM XSS in the postMessage handler
DOM XSS: any window can post {type:'go_to_link', url:'javascript:alert(1)'} and the handler runs it via location.assign, executing script. Two flaws: no event.origin allowlist and no URL-scheme validation. Fix: check e.origin against a trusted list, then accept only http/https URLs (reject javascript:/data:) before navigating.
Common mistakes
- ✗Mistaking DOM XSS for DoS, CSRF, or missing output escaping
- ✗Thinking
location.assigndoes not execute thejavascript:scheme - ✗Fixing only one of the two problems (origin OR URL scheme)
Follow-up questions
- →Why is an
event.origincheck insufficient without validating the URL scheme? - →How does DOM XSS differ from reflected XSS in where it executes?
MiddleDebuggingOccasionalFind the local file read that bypasses the image check in PHP
Find the local file read that bypasses the image check in PHP
Local File Read — readfile($url) serves any path, so a php://filter wrapper reads arbitrary files. The mime gate is bypassed because a filter chain can re-encode the leading bytes until getimagesize() accepts them as an image, or via TOCTOU between check and read. Fix: allowlist sources, block php://, download once, re-encode server-side.
Common mistakes
- ✗Considering the getimagesize() check sufficient protection against file reads
- ✗Seeing only SSRF and missing the Local File Read via php://filter
- ✗Overlooking the TOCTOU between the check and readfile
Follow-up questions
- →How does a php://filter chain turn a file's bytes into something resembling an image?
- →Why does TOCTOU bypass the mime check even without the php:// wrapper?
MiddleDebuggingOccasionalFind the NoSQL injection in the Mongoose login
Find the NoSQL injection in the Mongoose login
NoSQL injection via incorrect type handling — username is never coerced to a string, so an attacker sends an object of MongoDB operators that changes query semantics and bypasses the lookup. Fix: cast to a string and query an explicit field instead of passing the raw request object.
Common mistakes
- ✗Believing NoSQL is immune to injection because it lacks SQL syntax
- ✗Passing the raw request object into the DB driver without type coercion
- ✗Confusing it with a classic SQL injection or a race condition
Follow-up questions
- →How does a $regex operator on the password field let it be brute-forced character by character?
- →Why is casting to a string at the source more reliable than filtering out operators?
MiddleDebuggingOccasionalIs SQL injection possible via literal() in an ORM query?
Is SQL injection possible via literal() in an ORM query?
Yes — literal() is a raw SQL escape hatch; the ORM does NOT parameterize text inside it. The :firstName binding is only substituted by string replacement, and on an outdated version a crafted firstName (and lastName set to :firstName) breaks out of the literal and injects SQL. Fix: never put user input near literal(); rely on the ORM's parameterized where operators and upgrade the library.
Common mistakes
- ✗Thinking the ORM parameterizes raw SQL inside
literal() - ✗Believing the presence of
replacementsmakes the whole query safe - ✗Mistaking the injection via literal for a performance bug
Follow-up questions
- →Why do
replacementsnot protect the text passed intoliteral()? - →When is using
literal()in an ORM query ever justified?
MiddleDebuggingOccasionalFind the SSTI in a lodash template built from user input
Find the SSTI in a lodash template built from user input
Server-side template injection — name becomes part of the template source, and escapeHTML only neutralizes HTML, not template syntax. Any ${...} expression is compiled and executed server-side, giving RCE. Fix: pass name as data to a fixed template, never build the template from input.
Common mistakes
- ✗Treating escapeHTML as protection against template-syntax injection
- ✗Confusing SSTI with reflected XSS (execution on the server, not in the browser)
- ✗Building the template string from user input
Follow-up questions
- →Why doesn't HTML escaping prevent execution of the ${...} expression?
- →What's the difference between passing input as data versus as template text?
JuniorDebuggingRareFind the directory traversal in the nginx config using alias
Find the directory traversal in the nginx config using alias
Alias path traversal from the missing trailing slash on the location. Because /assets has no trailing slash, nginx appends the URI part after /assets to the alias as-is, so /assets../app/db.php resolves to /var/www/webapp/app/db.php — one level above static/. Fix: give both the location and the alias a trailing slash.
Common mistakes
- ✗Treating the missing trailing slash as a style choice rather than a security flaw
- ✗Thinking nginx normalizes
..itself before matching against the alias - ✗Mistaking the directory traversal for information disclosure or DoS
Follow-up questions
- →Why does a trailing slash on
locationprevent the URI remainder from being appended? - →How does
aliasbehave differently fromrootwhen mapping the path?
JuniorTheoryRareWhat do the link attributes rel=noopener and rel=noreferrer protect against?
What do the link attributes rel=noopener and rel=noreferrer protect against?
A target="_blank" link gives the opened page a window.opener reference back to yours, letting it redirect your page to a phishing clone (reverse tabnabbing). rel="noopener" severs that reference. rel="noreferrer" additionally strips the Referer header, hiding the source URL.
Common mistakes
- ✗Believing noopener protects the destination page, not yours
- ✗Confusing reverse tabnabbing with CSRF or XSS
- ✗Thinking noreferrer affects only analytics, not security
Follow-up questions
- →How exactly does the opened tab use window.opener to replace the original page?
- →Why do modern browsers apply noopener by default for target=_blank?
MiddleDebuggingRareFind the race condition in the money-transfer endpoint
Find the race condition in the money-transfer endpoint
A TOCTOU race: read-check-write isn't atomic, so concurrent requests read the same balance, all pass the >= check, and each debits — the attacker spends far more than they have. Fix: make it atomic — a transaction with row-level (pessimistic) locking, or one conditional update (UPDATE ... SET balance = balance - :amt WHERE balance >= :amt).
Common mistakes
- ✗Mistaking the race for a CSRF or rate-limiting problem
- ✗Believing the >= check before the write protects under concurrency
- ✗Treating the symptom with a request limit instead of an atomic operation
Follow-up questions
- →Why does a single conditional UPDATE eliminate the race at its root?
- →How does a pessimistic row lock differ from a request limit here?
MiddleTheoryRareWhat attacks are possible against a service that renders office documents (xlsx, docx, csv) as images?
What attacks are possible against a service that renders office documents (xlsx, docx, csv) as images?
xlsx/docx are ZIP archives of XML, so attack surface is large. Attacks: Zip Slip — a crafted archive path escapes the extraction dir; XXE — external entities in the XML reach files/SSRF; CSV injection — a cell like =cmd|... executes on open; macros; library CVEs. Defend by validating paths, disabling external entities and macros, sandboxing, and patching.
Common mistakes
- ✗Considering rendering to an image safe and ignoring the document's contents
- ✗Not knowing that xlsx/docx are ZIP archives of XML (Zip Slip, XXE)
- ✗Thinking CSV is inert data and missing formula injection
Follow-up questions
- →Why does the xlsx format, as a ZIP archive, open up Zip Slip and XXE vectors?
- →How do you fingerprint the rendering engine via SSRF and metadata?