1.CSRF, XSS, SQL injection, and clickjacking
Django defends against all four of these by default, and each defence has one specific way to switch it off. **CSRF** — `CsrfViewMiddleware` requires a secret token on unsafe methods, so a form on another site cannot make an authenticated request on the user's behalf; `@csrf_exempt` removes it. **XSS** — templates autoescape the characters that are dangerous in HTML; `|safe`, `mark_safe()` and `{% autoescape off %}` remove it. **SQL injection** — the ORM parameterises every query, sending SQL and values separately; string-formatting into `.raw()`, `extra()` or `RawSQL()` removes it. **Clickjacking** — `XFrameOptionsMiddleware` sends `X-Frame-Options: DENY`, so your pages cannot be loaded in an invisible frame over a page the attacker controls. Knowing where each defence ends is more useful than knowing that it exists.
Key features
- CSRF protection applies to unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`); DRF additionally enforces it only for session-authenticated requests, not anonymous ones
- Autoescaping covers element content; an **unquoted** HTML attribute is a separate context where an escaped value can still execute — the Django docs give `<style class={{ var }}>` as the example
- `mark_safe()`, the `|safe` filter, and `{% autoescape off %}` each disable escaping for their scope — treat every use as a line that needs a reason beside it
- The ORM parameterises: SQL text and values travel separately, so a value can never become syntax. `.raw()` and `cursor.execute()` do too — **if** you pass `params`, not an f-string
- `extra()` and `RawSQL()` are the two the Django docs single out for caution, because parts of what you pass are interpolated into SQL rather than parameterised
- `X_FRAME_OPTIONS` defaults to `"DENY"` with `XFrameOptionsMiddleware` enabled; `@xframe_options_exempt` opts one view out
Four defences, and the exact line that disables each
- Untrusted input
- Into the HTML parser — XSS
- Autoescaped by default — the dangerous HTML characters are escaped in element content
- |safe · mark_safe() — every use needs a reason written next to it
- Unquoted attributes — class={{ v }} executes — autoescaping does not cover this context
- Into the SQL parser — SQL injection
- The ORM parameterises everything — SQL and values travel separately
- .raw(sql, params) is safe too — the params list is what makes it safe, not the method
- f-string · extra() · RawSQL() — the three shapes to grep for in review
- Into your endpoint, from elsewhere — CSRF and clickjacking
- CsrfViewMiddleware — a secret token on POST/PUT/PATCH/DELETE
- X-Frame-Options: DENY — the default — your page cannot be framed
- @csrf_exempt · @xframe_options_exempt — the two decorators worth a second reviewer
Command
Order.objects.raw("SELECT * FROM orders WHERE reference = %s", [reference])
# %s is a placeholder the driver fills in, NOT Python string formattingAll four attacks are untrusted input reaching an interpreter, and Django closes each boundary by default — so the vulnerability is almost always the line that opts out. Grep for those four shapes: `@csrf_exempt`, `|safe`/`mark_safe()`, `@xframe_options_exempt`, and an f-string inside `.raw()`/`extra()`/`RawSQL()`. Sanitise with an allow-list on write, so `|safe` lives in one auditable place. And quote every HTML attribute: autoescaping covers element content, not an unquoted attribute value.