Backend security cheat sheet
The 12 Backend security interview answers in 30 seconds each, plus quick facts. Printable, free.
One page per topic: print it or save it as PDF from your browser's print menu. Practise these questions
Rules of thumb
- Check ownership or tenancy on every object access.
- Parameters for every SQL value; allow-list identifiers.
- Fetching user URLs: https only, check resolved IPs, recheck redirects.
- Cookie sessions: SameSite plus CSRF tokens; no state on GET.
- Sign webhooks: HMAC of timestamp + raw body, constant-time compare.
- Secrets in a manager, injected at runtime, rotated with overlap.
- Allow-list input schemas; never copy the whole body onto a model.
Broken object level authorization (BOLA)
BOLA happens when an endpoint takes an object id from the request and returns or changes that object without checking the caller may access it: change /invoices/1001 to /invoices/1002 and you see someone else's invoice. It tops the OWASP API Security Top 10 (2023). Fix it by checking ownership or tenancy on every object access, ideally inside the data-access layer (WHERE id = $1 AND tenant_id = $2), so no handler can forget.
SQL injection and parameterised queries
Injection happens when user input is pasted into SQL text, so a value like x' OR '1'='1 changes the query's meaning. Parameterised queries send the SQL and the values separately; the database never parses the value as SQL, so the attack becomes a harmless string. Use parameters for every value, an allow-list for identifiers you can't parameterise (column names, sort direction), and a database role with only the rights the app needs.
import psycopg
with psycopg.connect(autocommit=True) as conn:
conn.execute("CREATE TEMP TABLE users (name text, admin bool)")
conn.execute(
"INSERT INTO users VALUES ('ana', false), ('root', true)"
)
evil = "x' OR '1'='1"
unsafe = f"SELECT count(*) FROM users WHERE name = '{evil}'"
print(conn.execute(unsafe).fetchone()) # injectable
safe = "SELECT count(*) FROM users WHERE name = %s"
print(conn.execute(safe, (evil,)).fetchone())Server-side request forgery (SSRF)
An attacker can point it at internal addresses: the cloud metadata service at 169.254.169.254 (which can hand out credentials), localhost admin ports or private databases. Defend in layers: allow only https and expected ports, resolve the host and reject private, loopback and link-local addresses, pin the request to the address you checked (so DNS can't change between check and fetch), don't follow redirects blindly, and run fetchers in a network zone that can't reach internals.
CSRF and how cookies cause it
Browsers attach cookies to requests to your site even when another site triggers them, so a hidden form on evil.example can POST to /transfer with the victim's session. Defences: SameSite=Lax or Strict session cookies, an anti-CSRF token tied to the session on every state-changing request, checking the Origin header, and never changing state on GET. APIs authenticated by an Authorization header (not cookies) aren't exposed this way, because browsers don't add that header automatically.
XSS from a backend's point of view
Treat all user content as data, never markup: encode it for the context where it lands (HTML body, attribute, URL, JavaScript), or let a templating engine with auto-escaping do it. If you must accept rich text, sanitise it with a maintained allow-list sanitiser on output. Send a Content Security Policy that blocks inline scripts, return JSON with Content-Type: application/json, and keep session cookies HttpOnly so a slip doesn't hand over sessions.
Managing secrets
Not in the repository, image or client app. Keep them in a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager) and inject them at runtime through the platform's identity, so the app never holds a long-lived bootstrap secret. Grant each service only its own secrets, audit reads, and rotate on a schedule and on any suspicion, supporting two valid credentials at once so rotation needs no downtime. Prefer short-lived, dynamically issued credentials where you can.
Signing requests and webhooks with HMAC
Share a secret with the receiver and send an HMAC-SHA256 of a timestamp plus the exact raw body in a header. The receiver recomputes it over the raw bytes, compares in constant time, and rejects timestamps older than a few minutes so captured requests can't be replayed. Storing processed event ids gives replay protection inside the window too. For signatures third parties verify without a shared secret, use asymmetric signatures (Ed25519).
Credential stuffing and brute force
Rate-limit by account and by source (IP, subnet, device fingerprint), with growing delays rather than permanent lockouts that let attackers lock out real users. Check passwords against breach corpora at signup and login, push MFA or passkeys, add bot detection or a challenge when signals look bad, and notify users of new-device sign-ins. Return the same error for 'no such user' and 'wrong password' so the login doesn't confirm which emails exist.
Encryption in transit and at rest
TLS encrypts and authenticates the connection: the client checks the server's certificate chain and hostname, and nobody on the path can read or alter traffic. Use TLS 1.2 or 1.3 everywhere, including inside your network. Encryption at rest protects disks and backups if the media is stolen, but the running database decrypts for anyone with a valid login, so it doesn't stop application-level breaches. For especially sensitive fields add application-level encryption with keys in a KMS.
Mass assignment
It happens when an endpoint copies every field of the request body onto a model, so a user can send "is_admin": true or "balance": 1000000 and the server saves it. It's in the OWASP API Top 10 as broken object property level authorization. Prevent it with explicit input schemas per endpoint that list only the fields a caller may set, and separate response schemas so internal fields aren't exposed either.
Dependency and supply-chain security
Pin exact versions with a lock file and hashes, update deliberately with automated pull requests, and scan for known vulnerabilities (pip-audit, OSV, Dependabot). Watch for typosquatted and newly hijacked packages; prefer well-maintained ones. Lock down CI: least-privilege tokens, pinned actions by commit, protected branches and reviewed changes. Produce an SBOM and sign build artifacts so you can trace and verify what runs in production.
HTTP security headers
Strict-Transport-Security forces HTTPS on future visits. Content-Security-Policy limits where scripts, styles and frames load from and blocks inline script. X-Content-Type-Options: nosniff stops browsers guessing content types. frame-ancestors in CSP (or X-Frame-Options) blocks clickjacking. Referrer-Policy limits what URLs leak to other sites. For APIs, set accurate Content-Type and Cache-Control: no-store on sensitive responses.
Quick facts
- OWASP API #1
- Broken Object Level Authorization is first in the OWASP API Security Top 10 (2023 edition).
- Cloud metadata IP
- AWS, GCP and Azure serve instance metadata at the link-local address 169.254.169.254.
- hmac.compare_digest
- Compares in time that doesn't depend on where the inputs differ, avoiding timing leaks.
- Breached-password lookups
- Have I Been Pwned's range API takes the first 5 hex characters of a SHA-1 hash (k-anonymity).
- CORS isn't CSRF protection
- Simple cross-site form POSTs are sent without a preflight, so CORS rules don't block them.