Most modern attacks don't target the network or the OS — they target your code. Application security is the discipline of finding and fixing those vulnerabilities, ideally before attackers do.
The OWASP Top 10 (2021)
The Open Worldwide Application Security Project publishes a top-10 list of web application risks every few years. The 2021 edition:
- A01 Broken Access Control — users can act as other users, escalate privileges, or access unauthorised data. Mitigation: deny by default, enforce server-side, never trust client input for who-am-I.
- A02 Cryptographic Failures — data exposed because of weak or missing crypto. Mitigation: TLS everywhere, strong algorithms, no plaintext secrets.
- A03 Injection — SQL, NoSQL, OS command, LDAP. Mitigation: parameterized queries, ORMs, input validation, escape output.
- A04 Insecure Design — flaws baked into architecture, not just bugs. Mitigation: threat modeling, secure design patterns, defence in depth.
- A05 Security Misconfiguration — default credentials, verbose errors, unpatched components, open S3 buckets. Mitigation: hardening baselines, IaC scanning.
- A06 Vulnerable and Outdated Components — running libraries with known CVEs. Mitigation: SCA tools (Snyk, Dependabot, Renovate), patch SLAs.
- A07 Identification and Authentication Failures — credential stuffing, weak password reset, missing MFA, session fixation.
- A08 Software and Data Integrity Failures — trusting unsigned code, insecure deserialization, untrusted CI/CD pipelines. Mitigation: signed artifacts (Sigstore, cosign), SLSA.
- A09 Security Logging and Monitoring Failures — you can't respond to what you don't see.
- A10 Server-Side Request Forgery (SSRF) — server makes attacker-controlled requests. Mitigation: allow-list outbound destinations, block metadata endpoints (e.g., 169.254.169.254).
SQL Injection — The Canonical Example
Vulnerable code (any language, same shape):
query = f"SELECT * FROM users WHERE email = '{user_input}'"
db.execute(query)
If user_input is ' OR 1=1 --, you just dumped the user table. Fix: use parameterized queries — the database library substitutes parameters safely:
db.execute("SELECT * FROM users WHERE email = %s", (user_input,))
The same idea applies to OS command injection (use subprocess.run([...]) with a list, never shell=True with concatenation), LDAP injection, and template injection. Never glue untrusted input into an interpreter without escaping.
XSS (Cross-Site Scripting)
Attacker injects JavaScript that runs in another user's browser. Three flavours: stored, reflected, DOM-based.
Defences:
- Contextual output encoding — modern frameworks (React, Vue, Angular) escape by default; only
dangerouslySetInnerHTMLopts out - Content Security Policy (CSP) — HTTP header restricting what scripts can run and from where
- HttpOnly cookies so JavaScript can't steal sessions
- Sanitisation libraries (DOMPurify) for cases where you must accept HTML
CSRF
Cross-Site Request Forgery: a malicious site causes the victim's browser to make a state-changing request to a site they're authenticated to. Defences:
- SameSite cookies (Lax or Strict) — modern browsers default to Lax
- CSRF tokens validated server-side on every state-changing request
- Custom request headers (X-Requested-With) for AJAX endpoints — browsers won't send custom headers cross-origin without CORS
API Security
OWASP also publishes an API Top 10. Key practices:
- Authenticate and authorize every endpoint, not just the gateway
- Validate input against a schema (OpenAPI / JSON Schema)
- Rate-limit per token and per IP
- Avoid BOLA (Broken Object-Level Authorization) — verify the caller owns the requested object on every read
- Return only the fields the caller needs (no over-exposure of internal data)
Secrets Management
Hardcoded API keys, database passwords, or signing keys in source code or container images are a perennial source of breaches. Treat secrets as a separate concern:
- Vault tools: HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, Doppler, 1Password
- Pre-commit scanners: gitleaks, trufflehog, GitHub secret scanning
- Short-lived credentials via OIDC federation (e.g., GitHub Actions to AWS without static keys)
- Rotate quickly after any suspected exposure
Securing the SDLC
Don't bolt security on at the end — integrate it at every stage. Tools by category:
| Tool | What it does | Examples |
|---|---|---|
| SAST (Static) | Analyses source code for vulnerabilities without running it | Semgrep, SonarQube, CodeQL, Checkmarx |
| DAST (Dynamic) | Tests a running app from the outside, like an attacker | OWASP ZAP, Burp Suite, Nuclei |
| SCA (Software Composition) | Identifies vulnerable open-source dependencies | Snyk, Dependabot, Renovate, Trivy |
| IAST (Interactive) | Instruments running app to find runtime issues | Contrast Security |
| IaC scanning | Catches misconfigured Terraform / CloudFormation / K8s manifests | Checkov, tfsec, KICS |
| Container scanning | Finds vulnerable packages in images | Trivy, Grype, Docker Scout |
Run them in CI on every pull request, fail the build on critical findings, and track time-to-remediation as a KPI.
Hardening Headers
A baseline set of security headers every web app should ship:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'; ...
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Test your site at securityheaders.com or observatory.mozilla.org.