The OWASP Top 10 was designed as a general awareness document for web application security. It covers the most critical risks across all web applications. But legacy applications — systems built before 2018, on older frameworks, with accumulated technical debt — face these risks differently than modern applications do.
Some OWASP categories are catastrophically common in legacy systems. Others are less relevant because legacy apps predate the patterns that cause them. This guide prioritizes the list for legacy systems specifically, so you know where to focus your limited remediation time.
The priority order for legacy systems
Priority 1: A03 — Injection
Why it's #1 for legacy systems: Modern frameworks make injection nearly impossible through parameterized queries and ORMs. Legacy applications, especially PHP and classic ASP, frequently construct SQL queries through string concatenation — the exact pattern that enables injection.
What to look for:
// This pattern is an injection vulnerability
$query = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";
// This pattern is safe
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute([':email' => $_POST['email']]);
How to find it: Static analysis tools like PHPStan, Semgrep, or even grep for mysql_query, $_GET, $_POST near SQL strings. In a mature audit, we run every database interaction through a taint analysis to ensure user input never reaches SQL execution without parameterization.
Effort to fix: Medium. Each injection point requires rewriting the query to use prepared statements. In large codebases, this can be hundreds of queries — systematic, tedious, but straightforward work.
Priority 2: A07 — Identification and Authentication Failures
Why legacy systems are vulnerable: Legacy applications often implement authentication from scratch rather than using a framework's auth system. Common problems:
- Passwords stored in MD5 or SHA-1 — both are computationally trivial to crack with modern hardware.
- No account lockout — brute-force attacks can run indefinitely.
- Session fixation — the session ID doesn't change after login, allowing attackers to set it in advance.
- Session tokens in URLs — the session ID leaks through referrer headers, browser history, and access logs.
How to fix it:
// Password hashing: don't roll your own
// Old (insecure)
$hash = md5($password);
// New (secure, adaptive, salted automatically)
$hash = password_hash($password, PASSWORD_ARGON2ID);
$valid = password_verify($input, $hash);
Effort to fix: High. Authentication touches every part of the application. Budget 1–2 weeks for a thorough overhaul.
Priority 3: A06 — Vulnerable and Outdated Components
Why it's critical for legacy systems: A modern application with Composer, npm, or pip can run audit and see every known CVE in its dependency chain. Legacy applications often have dependencies vendored manually — downloaded as zip files, modified, and dropped into a lib/ directory with no version tracking.
You can't patch what you can't identify.
How to audit it:
- Inventory every third-party library in the codebase (look in
lib/,vendor/,includes/,third_party/). - Identify the version — check for version constants, README files, or changelogs.
- Cross-reference against the National Vulnerability Database or Snyk's vulnerability database.
Effort to fix: Variable. Some libraries have modern replacements available through Composer or npm. Others require finding an alternative altogether.
Priority 4: A01 — Broken Access Control
Why legacy systems fail here: Modern frameworks enforce authorization through middleware, decorators, or policy classes. Legacy applications often check permissions inline — if ($user->role === 'admin') scattered throughout the codebase. This approach is fragile: miss one check and you've created a privilege escalation bug.
Common patterns in legacy code:
- Horizontal privilege escalation: User A can access User B's data by changing an ID in the URL (
/profile?id=1234). - Vertical privilege escalation: Regular users can access admin endpoints because the authorization check is missing.
- Insecure direct object references: Database IDs exposed in URLs without ownership verification.
How to fix it: Centralize authorization in a middleware or service layer. Every request should pass through an authorization check before reaching business logic — never after.
Priority 5: A05 — Security Misconfiguration
Why it compounds in legacy systems: Legacy applications accumulate configuration drift. Debug mode left on in production. Default credentials that were never changed. Directory listings enabled on the web server. Error messages that expose stack traces, file paths, and database schemas.
Quick wins:
# php.ini in production
display_errors = Off
expose_php = Off
allow_url_include = Off
# .htaccess or nginx config
# Disable directory listing
Options -Indexes
# Remove server signature
ServerSignature Off
Effort to fix: Low. Security misconfiguration is often the highest-ROI remediation work — a few hours of configuration changes can close significant attack surface.
Priority 6: A02 — Cryptographic Failures
What to look for in legacy systems:
- HTTP instead of HTTPS — especially for login forms and API endpoints.
- Weak hashing algorithms — MD5, SHA-1 for passwords or sensitive data.
- Hardcoded encryption keys — in source code rather than environment variables or a secrets manager.
- Missing encryption at rest — sensitive data stored in plaintext in the database.
Effort to fix: Medium. Moving to HTTPS is operationally straightforward. Migrating password hashes requires a staged approach (re-hash on next login).
Lower priority for legacy systems
The remaining OWASP categories — A04: Insecure Design, A08: Software and Data Integrity Failures, A09: Security Logging and Monitoring Failures, A10: Server-Side Request Forgery — are important but typically less critical in legacy systems because:
- Insecure Design is about architectural flaws in new software. Legacy systems' design problems are better addressed through the migration/refactoring lens.
- SSRF is more common in modern cloud-native applications that make server-side HTTP requests.
- Logging failures are real but don't create a direct attack vector.
The 80/20 remediation plan
If you have limited time and budget, prioritize in this order:
- Fix SQL injection — every instance, no exceptions. This is the attack vector that leads to data breaches.
- Upgrade password hashing — move from MD5/SHA-1 to Argon2id or bcrypt. Do this on next login for each user.
- Audit dependencies — identify everything your application imports, check for CVEs, update what you can.
- Lock down configuration — disable error display, remove default credentials, enable security headers.
- Centralize authorization — build a middleware layer that checks every request before it reaches business logic.
This sequence addresses the highest-risk, most-exploitable vulnerabilities first. You can do the first four items in under two weeks for most legacy applications.
If your application hasn't had a security review in the past 12 months, it's overdue. A security hardening engagement systematically works through your threat surface — starting with the OWASP Top 10 and going deeper into your specific architecture and compliance requirements.