Skip to content

How web apps get attacked and the standard defences. Browser-side controls (CSP, headers, cookies) are in 3. HTTP Security Headers and CSP. Principles are in 1. Security Fundamentals.

OWASP Top 10 at a glance

The OWASP Top 10 is the standard awareness list for web risks (2021 edition shown; a newer edition exists, so check owasp.org).

# Risk One-line defence
A01 Broken access control Check permissions server-side on every object and action
A02 Cryptographic failures TLS everywhere, strong hashing, no secrets in plain text
A03 Injection (SQL, command, XSS) Parameterised queries, output encoding, validate input
A04 Insecure design Threat model early, secure patterns, abuse cases
A05 Security misconfiguration Hardened defaults, no debug in production, remove samples
A06 Vulnerable and outdated components Track and patch dependencies
A07 Identification and authentication failures MFA, rate limits, safe sessions
A08 Software and data integrity failures Signed artifacts, pinned dependencies, safe deserialisation
A09 Logging and monitoring failures Log security events, alert on them
A10 Server-side request forgery (SSRF) Allow-list outbound targets, block metadata addresses

Injection

Untrusted data is interpreted as code. The fix is to keep data and commands separate.

# Vulnerable: user input becomes part of the SQL
cur.execute(f"SELECT * FROM users WHERE name = '{name}'")

# Safe: parameterised query
cur.execute("SELECT * FROM users WHERE name = %s", [name])
  • Use the ORM or parameterised queries; never build SQL by string concatenation
  • Command injection: avoid the shell; pass an argument list (subprocess.run([...], shell=False)) and validate input
  • Give the application's database user only the rights it needs (no DROP, no superuser)

Cross-Site Scripting (XSS)

Attacker-controlled script runs in a victim's browser, so it can read the page, act as the user and steal tokens.

Type How
Stored Malicious input saved, then shown to others (comments, profiles)
Reflected Input echoed straight back (search term in the page)
DOM-based Client-side JavaScript writes untrusted data into the DOM

Defences, in order: 1. Encode output for its context (HTML, attribute, JavaScript, URL). Django templates auto-escape; do not bypass with |safe or mark_safe on user data 2. Avoid innerHTML, document.write, eval; use textContent 3. Sanitise any HTML you must allow with a vetted library (for example nh3 or DOMPurify) 4. Add a Content Security Policy as the safety net: 3. HTTP Security Headers and CSP 5. Mark session cookies HttpOnly


Cross-Site Request Forgery (CSRF)

A malicious site makes the victim's browser send an authenticated request (the cookie rides along).

  • Use anti-CSRF tokens on state-changing requests (Django {% csrf_token %}, CsrfViewMiddleware)
  • SameSite=Lax or Strict cookies block most cross-site sends
  • State changes use POST / PUT / DELETE, never GET
  • APIs using Authorization: Bearer headers rather than cookies are not CSRF-prone, but must then protect the token from XSS

Authentication and Sessions

  • Hash passwords with Argon2id or bcrypt; Django's default PBKDF2 is acceptable, Argon2 is better
  • Rate-limit and lock out repeated failures; use the same message for "unknown user" and "wrong password"
  • MFA for sensitive roles; short-lived reset links, single use
  • Sessions: random unguessable IDs, Secure + HttpOnly + SameSite cookies, rotate the ID on login, expire on logout and idle timeout
  • JWTs: verify the signature and the algorithm, check exp, aud, iss; keep them short-lived; do not put secrets in the payload (it is only encoded, not encrypted)
  • OAuth2 / OIDC: use the authorisation-code flow with PKCE; never roll your own login against a provider

Broken Access Control

The most common serious bug. Examples: - IDOR: /invoices/1042 works for any logged-in user. Always check the object belongs to the requester - Forced browsing to /admin or hidden URLs - Role checks only in the front end - Missing checks on API methods (GET protected, DELETE not)

Fix: deny by default, one central authorisation layer, test with two different users, and log denied attempts.


SSRF

The server is tricked into fetching a URL the attacker picks, often reaching internal services or the cloud metadata endpoint (169.254.169.254) to steal credentials.

  • Allow-list the hosts you may call; resolve DNS and block private / link-local ranges
  • Do not return raw responses to the user
  • On AWS use IMDSv2 (session-token based metadata) and restrict the instance role: 2. AWS IAM and Security

File Uploads, Deserialisation and Others

Risk Defence
Unsafe uploads Check type by content, limit size, rename, store outside the web root or in object storage, never execute
Insecure deserialisation Do not unpickle / deserialise untrusted data; use JSON with schema validation
Open redirects Redirect only to allow-listed or relative URLs
Path traversal Normalise paths; never join raw user input to a file path
XXE Disable external entities in XML parsers
Clickjacking frame-ancestors / X-Frame-Options: 3. HTTP Security Headers and CSP
Mass assignment Whitelist the fields a request may set

API Security

  • Authenticate and authorise every endpoint; per-object checks
  • Rate limit and set request size limits
  • Validate input against a schema; return generic errors (no stack traces)
  • Version and retire old endpoints; do not expose debug / admin APIs
  • Do not leak internal IDs, emails or excessive fields in responses

Django Production Checklist

$ python manage.py check --deploy
DEBUG = False
ALLOWED_HOSTS = ["example.com"]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]   # never in git

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

Also: keep the admin off a guessable public path or behind a VPN, use django-axes or rate limiting for logins, and keep Django and packages patched.


Firewall tuning and false positives: 6. WAF in Production.


Testing

  • SAST reads source (Bandit for Python, Semgrep, CodeQL)
  • DAST attacks a running app (OWASP ZAP). Only against systems you own or have permission to test
  • Passive scanning of headers, cookies and TLS: Siteward Web Security Scanner
  • Add tests for access control (user A cannot read user B's data)
  • Wire these into CI: 5. Secure Coding and Supply Chain