Interviewers love asking “name a few web vulnerabilities and how you’d fix them”. The good news — most of them share one root cause: we trusted input we shouldn’t have. Let’s walk the classics. Each one gets a definition, why it happens, and the fix.
SQL Injection — attacker writes our query
In simple language — SQL injection is when user input sneaks out of the “data” slot and becomes part of the SQL command itself.
It happens whenever we build a query by gluing strings together. The database can’t tell where our intended query ends and the attacker’s text begins, so it just runs the whole thing.
// VULNERABLE — user input is concatenated straight into SQL
const email = req.body.email; // attacker sends: ' OR '1'='1
db.query(`SELECT * FROM users WHERE email = '${email}'`);
// becomes: SELECT * FROM users WHERE email = '' OR '1'='1' → returns everyone
That ' OR '1'='1 closes our quote and adds a condition that’s always true. A nastier payload like '; DROP TABLE users; -- can delete data outright.
The fix is parameterized queries (prepared statements). We send the query and the values on separate channels, so the driver treats input as pure data — it can never become SQL.
-- FIXED — the ? / $1 is a placeholder, the value is bound separately
SELECT * FROM users WHERE email = $1;
// The value never touches the query string — it's bound as data
db.query("SELECT * FROM users WHERE email = $1", [req.body.email]);
An ORM does this for us under the hood, but the moment we drop to raw SQL, parameterize. Never concatenate. Never “just escape it myself”.
XSS — attacker runs JS in our page
Cross-Site Scripting (XSS) is when an attacker gets their JavaScript to run in our user’s browser. Once their script runs on our page, it can read cookies, steal tokens, or act as the user.
Same root cause as SQLi, different target: we took untrusted input and dropped it into HTML without escaping, so the browser executed it as code instead of showing it as text.
Two flavours interviewers want:
- Stored XSS — the payload is saved on the server (a comment, a profile bio) and served to everyone who views it. Most dangerous, it’s persistent.
- Reflected XSS — the payload rides in the request (a search query in the URL) and is echoed straight back in the response. Needs the victim to click a crafted link.
The fix is output escaping plus a Content-Security-Policy:
- Escape on output — turn
<script>into<script>so the browser renders it as harmless text. Modern frameworks (React, Vue) auto-escape by default; the danger is when we bypass them with things likedangerouslySetInnerHTMLorinnerHTML. - CSP header — a browser-enforced allowlist of where scripts may load from. Even if a payload slips through,
Content-Security-Policy: script-src 'self'blocks inline and third-party scripts from running.
CSRF — attacker rides our logged-in session
Cross-Site Request Forgery (CSRF) tricks a logged-in user’s browser into firing a request we didn’t intend — like a hidden form on a malicious site that POSTs to ourbank.com/transfer. The only difference from other attacks: the attacker never sees the response, they just want the side effect to happen.
Why it works: cookies auto-send on every request to our domain, no matter who triggered it. So the browser happily attaches our valid session cookie to the attacker’s forged request, and the server thinks it’s us.
Fixes:
- CSRF tokens — the server plants a random unpredictable token in our form/page, and requires it back on state-changing requests. The attacker’s site can’t read our token (same-origin policy), so it can’t forge a valid request.
SameSitecookie attribute —SameSite=LaxorStricttells the browser “don’t send this cookie on cross-site requests”, which kills most CSRF at the source. This is the modern first line of defence.
Note the flip side: CSRF is a cookie-auth problem. Token-in-header auth (JWT in an Authorization header) isn’t auto-sent by the browser, so it sidesteps CSRF — but then it’s exposed to XSS instead. Pick your poison.
SSRF — the server makes the request for them
Server-Side Request Forgery (SSRF) — briefly — is when we let user input control a URL that our server then fetches. Think an “import from URL” or a webhook feature.
The attacker points it at internal stuff they can’t reach from outside: http://169.254.169.254/ (the cloud metadata endpoint that hands out credentials) or internal admin services on localhost. Our server, sitting inside the trusted network, fetches it for them.
Fix: validate and allowlist which hosts/schemes we’ll fetch, block private/internal IP ranges, and never blindly follow redirects to them.
Where these sit — OWASP Top 10
The OWASP Top 10 is the industry’s periodically-updated list of the most critical web risks — worth naming in an interview. Recent lists put Broken Access Control at #1 (users reaching data/actions they shouldn’t) and Injection (which folds in SQLi and XSS) near the top. We don’t need to memorize all ten; we need to show we know the categories and their fixes.
Interview soundbite
Most web vulns come from trusting input. SQLi: input becomes SQL → fix with parameterized queries, never string concat. XSS: input becomes runnable JS → escape output and add a CSP; stored (saved server-side) is worse than reflected (echoed from the request). CSRF: the browser auto-sends our cookie on a forged request → fix with CSRF tokens and SameSite. SSRF: user input steers a server-side fetch toward internal services → allowlist hosts. And know the OWASP Top 10 exists, with Broken Access Control and Injection near the top.