Web App Security Checklist

High Importance

1. Authorisation Failures

  • All auth to be done solely in the backend
  • All endpoints must have authN and authZ, anonymous routes must have a carve out.
  • IDOR: Just because a role has access to an endpoint doesn't give it unrestricted access to all objects via that endpoint. eg
    • Admin's that have access to createUserAccount(username, role) should not be allowed to pass role=SuperAdmin. Following should not be allowed:
      • Admin creating SuperAdmin
      • Admin self escalating to SuperAdmin
      • Admin updating someone to SuperAdmin
      • Admin deleting a SuperAdmin
      • Admin modifying a SuperAdmin
    • A User role having access to readPost(id) so they can read their own post must not allow them to read private posts from others simply by passing their ids.
  • Permissions must update instantly after change (eg. role update)
  • Session and other token deactivation after user deletion must be instant

2. Local Login

  • Proper hashing, salting
    • Only use Argon2id, scrypt or bcrypt. PBKDF2-SHA256 may be used with 600k iterations on legacy systems.
  • Length checking (>8)
  • Password reset:
    • should generate a short-lived cryptographically random string or a uuid.
    • It should not indicate whether email exists or not.
    • Invalidate all active sessions for that user
  • Avoid local login if possible

3. HTML Sanitisation/XSS Prevention

  • Safe DOM input methods:
    • innerText and textContent
  • Dangerous dynamic functions:
    • (eval|Function|HTMLScriptElement)\(
    • getElementById().onclick, getElementById().onmouseover
      • getElementById\(
  • Dangerous DOM sinks
    • (outerHTML|innerHTML|markupstring|@Html\.Raw|\|raw|eval\(|document\.write|document\.domain|onevent|dangerouslySetInnerHTML|insertAdjacentHTML|window.location)
  • Dangerous jQuery sinks
    • (add\(|after\(|append\(|animate\(|insertAfter\(|insertBefore\(|before\(|html\(|prepend\(|replaceAll\(|replaceWith\(|wrap\(|wrapInner\(|wrapAll\(|has\(|constructor\(|init\(|index\(|jQuery.parseHTML\(|\$\.parseHTML\(|ng-app)

4. Security Headers/CORS policy

  • If Backend and Frontend are served on separate origins they must both have headers set.
  • CORS should be disabled unless required (such as if the frontend and backend are on separate origins). Never reflect Origin header.
  • General HTTP header requirements:
    • x-frame-options: DENY,
    • strict-transport-security: max-age=63072000; includeSubDomains; preload,
    • x-xss-protection: 0,
    • x-content-type-options: nosniff,
    • x-dns-prefetch-control: off
  • Content security policy requirements:
    • default-src 'none' or 'self'
    • script-src:
      • Should never include 'unsafe-inline', should ideally not include 'unsafe-eval' or 'self' or 'data'.
      • When adding URLs to this directive, ensure the most specific URL is entered. eg. Never enter https://cdn.jsdelivr.net/ as all paths under that origin will be allowed, instead use https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js (individually to be done for all required packages).
      • Ideally use strict-dynamic with nonces
      • Ideally use trusted types
    • object-src 'none'
  • Use SRI on imported external JS
  • Should pass https://csp-evaluator.withgoogle.com/ and https://securityheaders.com/

5. File Upload Validations

  • To be done solely in the backend
  • Randomise filenames
    • If filenames must be kept, it should be sanitised from path traversal characters: forward/backwards slash and dot as a minimum. Length checking may be necessary if host OS has limitations.
  • Limit filesize
  • Limit allowed extensions, may further check magic bytes
  • Strip EXIF data from images for added privacy of users
  • Ideally use a virus scanner

6. Hardcoded Secrets

  • Have secrets ever been committed to repo? If so, remove and revoke immediately
  • No API keys, db passwords, private certs etc. allowed on both the codebase, or browser (such as saving passwords in web storage)
  • .gitignore includes .env/appsettings.json

7. Cookie Security Settings

  • HttpOnly, Secure, SameSite all set
    • For SPAs that can't set HttpOnly, security headers and HTML sanitisation must be very thorough.
  • Path should be specified if there are multiple applications on the same origin
  • Domain must be left blank unless specifically required

8. SQL Injection

  • All queries must either be parameterised or use an ORM.
  • Never construct raw SQL from user-controlled input (such as ExecuteSqlRawAsync() in .NET)
  • Minimise database application's privileges on the host machine, disallow certain commands such as DROP from the user account

9. Encryption

  • When encryption is necessary, use AES-GCM or CCM with a cryptographically safe random IV parameter that is unique per operation.
  • If data authenticity is not required AES-CTR or CBC modes may be used. The hash of the encrypted data may be used as a form of authenticity.
  • Never store hardcoded keys in codebase, never reuse IV or nonce parameters, never use AES-ECB mode.

Medium Importance

1. Docker

  • Keep Docker engine up-to-date
  • Only install production dependencies (--omit dev) and explicitly set NODE_ENV to production
  • Docker Compose
    • Add security_opt: - no-new-privileges:true
  • Dockerfile
    • Base image must be currently supported, LTS preffered.
    • Use a minimal version of the image (such as alpine) rather than full-sized ones.
    • Add USER non-root where non-root is a user that lacks root privileges. (for Node.js images this is node)

2. Entra/OIDC Login

  • Only sub or oid to be used for identifying users, emails cannot be used
    • For OIDC flows with multiple authentication mechanisms (ie. Google and Microsoft): sub+iss combined is the only identifier that can be used to identify users.
  • Usage of implicit flow (idtoken in .NET), is not recommended. Switch to auth code flow.
    • If must use idtoken, check expiration and signature of JWT.
    • PKCE SHA256 must be enabled for implicit flow.

3. Request Size Controls

  • Application-wide maximum request size must be set

4. Rate-Limiting

  • Implement global rate-limiting or at least for critical endpoints (login/signup)
  • Correct usage of X-Forwarded-For:
    • Get the last IP if 1 proxy used, second last if 2 proxies etc. Never trust first IP in the list!
  • .*(x-forwarded-for|x-real-ip|ipaddress)

5. Info Leak Through Logs and Errors

  • Client should never receive full error stacks or error messages from the application.
  • Secrets such as user passwords or session tokens should never be logged.
  • (log|warn|error)\(.*(.*\$|{|[+,])`
  • [=({$]\s*(err|ex)\S*\.

6. Open Redirect

  • Application should never trust URLs taken from the request for redirection.
    • eg. The url example.com/login?returnURL=https://attacker.com can redirect users to a malicious website if the application doesn't verify redirects which can allow phishing.
    • Can be limited to paths under the same origin by enforcing returnURL start with a forward slash, or not allowing it to start with https etc.
    • (returnUrl|next|redirect)

7. Command Injection

  • Never take user input into an OS command (including user editable fields such as filenames or even usernames).

6. Dependancies

  • Framework must be currently supported, LTS preferred.
  • Routine npm audit runs, or part of CI/CD chain.
  • No high+ vulnerability in codebase, unless it doesn't affect the application
  • Lockfile must be present

Low Importance

  • Unsafe deserialisation
    • JSON.parse(data)
  • Disable Source Maps in Production
  • Use strong random for cryptographic operations that need it
    • Math.random() or System.random() are unsafe.
  • Misconfiguration (tsconfig, next.config.js etc.)
  • User Enumeration
    • Return 401 Unauthorised before 404 Not Found (check auth before running query)
    • Forgot password functionality should not leak whether email exists
  • Lack of Input Sanitisation
    • Must be done in backend
  • Global error catching, restart after unrecoverable crash.
  • SSRF: Prevent application from sending arbitrary requests to arbitrary websites.
  • Timing analysis vulnerable comparison
    • API keys or passwords must always use timing safe comparison
  • SSL must always be enabled even for server-side requests

Programming Problems

  • try-catch either swallowing or not handling properly
    • catch\s*\(.*\)
  • [JS/TS] using ==/!= instead of ===/!==
    • [^!=]={2}[^=]
  • [.NET] public/protected/private classes/variables must be set with lowest access required.

Mobile

  • Disable cleartext traffic at network_security_config.xml
  • Enable ProGuard/R8
  • SSL Pinning may be enabled
  • iOS: NSAllowsArbitraryLoads, NSAllowsLocalNetworking

Run

  • [JS/TS] Linter
    • npx eslint . --ignore-pattern "**/*.js" --quiet
  • SCA, aikido
  • SAST, semgrep, opengrep, DAST
    • semgrep ci --exclude="*.js" --exclude-rule="javascript.browser.security.insecure-document-method.insecure-document-method" 
  • check TLS config

Notes

  • Highlighted are regex patterns
  • When vulnerability is found, check commit for other code edits

You'll only receive email when they publish something new.

More from Kevin
All posts