The audit we run on an inherited PHP codebase almost never starts with the code. It starts with php -v and a look at composer.lock, because a platform running PHP 5.6 or 7.1 (both years past EOL) has a ceiling on how secure it can possibly be, no matter how clean the application code is on top of it — there's no vendor patching the runtime itself anymore.
Rule of thumb we use: if the PHP version is more than three years past its own EOL date, or dependencies haven't been touched in two-plus years, treat the platform as actively vulnerable until proven otherwise. The audit exists to find out how, not whether.
The code patterns that actually show up in five-plus-year-old PHP
Scanners find some of this. The rest is grep and judgment. What we look for, in the order it tends to matter:
SQL built by concatenation instead of prepared statements — the classic "SELECT * FROM users WHERE id = " . $_GET['id'] pattern still shows up regularly in code written before PDO/mysqli conventions were standard practice. It's the highest-priority finding on almost every audit, because it's usually also the easiest for an automated scanner to find and exploit.
Unescaped output — user input echoed straight into HTML without htmlspecialchars(), most often in search result pages, comment sections, and admin panels that were considered "internal only" and therefore never hardened.
Missing CSRF protection — forms that change state (password reset, account settings, admin actions) with no token check, frequently paired with session cookies missing the SameSite attribute.
Upload handlers that trust the client — validating file type by checking the Content-Type header or file extension rather than actual file content, which lets a .php file renamed to .jpg (or with a null-byte trick on older PHP versions) land in a web-accessible directory with execute permissions still on.
Weak or reversible password storage — md5() or unsalted hashes from a signup flow built before bcrypt/argon2 became the default in frameworks. If this shows up, treat the entire user table as already compromised the moment the codebase itself is exposed, even absent evidence of a breach.
Server and access-control findings that get missed
Fixing the code above doesn't help if the server around it is misconfigured. Two findings recur often enough to call out specifically: configuration files (holding database credentials) left world-readable at 644 instead of 600, and .git or .env directories left inside the web root and directly fetchable over HTTP — we've pulled full database credentials this way from platforms that had otherwise reasonably clean application code.
On the access-control side, the finding we flag most is privilege boundaries that exist in the UI but not in the backend — an admin-only page hidden from the nav for regular users, but the underlying endpoint has no server-side role check, so anyone who guesses or finds the URL gets full access.
Turning findings into a remediation order
Once the audit is done, everything gets triaged by exploitability and blast radius, not by how easy each fix is:
| Priority | Issue Category | Timeline |
|---|---|---|
| Critical | Unparameterized SQL, exposed credentials, unrestricted upload execution | This week |
| High | XSS, missing CSRF tokens, weak password hashing, EOL runtime | Within 2 weeks |
| Medium | Outdated dependencies, missing security headers, backend-only access control gaps | Within 1 month |
| Low | File permission hygiene, logging depth, monitoring coverage | Within 3 months |
Patch it, or replace it?
Once the critical and high findings are fixed, there's a real decision to make about the platform's future. Patching in place preserves what already works and gets you secure faster, but you're maintaining a codebase built on assumptions (framework version, PHP version, architecture) that will keep aging under you — every future dependency update gets harder, not easier. Rebuilding on a current framework costs more upfront but resets that clock. We generally recommend patch-and-maintain when the critical/high list is short and the platform's architecture is otherwise sound, and rebuild when the audit itself takes longer than expected because there's simply too much surface area to secure piecemeal.
Not sure what's actually exposed in your codebase?
We run a code-level audit — not just an automated scan — and hand back a prioritized list you can act on immediately.
Request Security Audit