RefoundRefound
All articles
securityphplegacy systems

5 Security Risks in Abandoned PHP Applications (And How to Fix Them)

Refound

Most PHP applications written before 2018 were never designed to be secure — they were designed to ship. The frameworks were looser, the threat landscape was different, and the developer who built it left three jobs ago.

If your business is running on a PHP application that hasn't been actively maintained, you're likely carrying at least three of the five risks below. Here's what they are, how to find them, and what fixing them actually looks like.


1. SQL Injection via Unparameterised Queries

SQL injection has been the number one web vulnerability for over a decade. It's still endemic in legacy PHP codebases because older PHP tutorials taught mysql_query() — a function that was deprecated in PHP 5.5 and removed entirely in PHP 7.

What it looks like:

// Dangerous — user input concatenated directly into SQL
$username = $_POST['username'];
$result = mysql_query("SELECT * FROM users WHERE username = '$username'");

A single quote in the username breaks out of the string context. From there, an attacker can extract every row from every table, bypass authentication, or — on poorly-configured servers — execute OS commands.

How to fix it:

Replace raw queries with PDO prepared statements:

// Safe — input is parameterised, never interpreted as SQL
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([':username' => $_POST['username']]);
$user = $stmt->fetch();

If the codebase is large, remediation is a systematic audit, not a one-day sprint. Every database interaction that touches user-controlled input needs to be reviewed. A static analysis tool like PHPStan with the right ruleset will surface most of them quickly.

Severity: Critical. Automated scanners target this constantly.


2. Composer-Less Codebases Running EOL Dependencies

Before Composer became standard (roughly 2013–2015), PHP applications bundled third-party libraries manually — downloaded zip files dropped into a lib/ or vendor/ folder, often renamed and modified, with no version tracking and no upgrade path.

The result: your application is almost certainly running libraries with known CVEs that will never be patched, because there's no mechanism to update them.

How to audit it:

# If there's no composer.json, that's the first problem
ls composer.json || echo "No Composer — manual dependency audit required"

# For apps that do have Composer
composer audit

# Check PHP version — EOL versions receive no security patches
php -v

PHP 8.0 reached end-of-life in November 2023. PHP 7.x is ancient. If your application is running on either, you're running without a safety net.

What fixing it requires:

  1. Inventory all third-party code in lib/, vendor/, includes/ — anything not written in-house
  2. Identify versions against CVE databases (NVD, Snyk, etc.)
  3. Migrate to Composer-managed equivalents or modern replacements
  4. Establish a process for ongoing dependency audits

This is rarely glamorous work. It's also among the highest-ROI security work you can do.

Severity: High. Known CVEs in unpatched libraries are trivially exploitable.


3. Insecure Session Handling

PHP's default session configuration was designed for convenience, not security. In a production application that hasn't been explicitly hardened, sessions are likely stored on the filesystem, transmitted over HTTP, and vulnerable to fixation attacks.

Common misconfigurations:

// Missing: session regeneration after authentication
session_start();
if ($valid_login) {
    // No session_regenerate_id() — vulnerable to session fixation
    $_SESSION['user_id'] = $user_id;
}
; php.ini defaults that are insecure
session.cookie_httponly = 0  ; JavaScript can read session cookies
session.cookie_secure   = 0  ; Session cookie transmitted over HTTP
session.use_strict_mode = 0  ; Server accepts externally set session IDs

The secure configuration:

// Always call before session_start()
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);    // Requires HTTPS
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'Lax');

session_start();

// Regenerate session ID on privilege escalation
if ($just_logged_in) {
    session_regenerate_id(true);
}

If sessions are stored in a shared /tmp directory on a shared hosting environment — a common legacy pattern — any process on that server can read every active session.

Severity: High. Session hijacking allows complete account takeover without knowing the user's password.


4. Local File Inclusion (LFI) and Remote File Inclusion (RFI)

Older PHP applications frequently use include() or require() with path segments taken directly from request parameters. This was a common pattern for "templating" before MVC frameworks existed.

// Extremely dangerous — path controlled by user input
$page = $_GET['page'];
include("templates/$page.php");

An attacker passes page=../../../../etc/passwd%00 (null byte injection in older PHP versions, or direct path traversal in misconfigured setups) and reads arbitrary files from the server. With allow_url_include = On, they can include a PHP file hosted on a remote server and execute arbitrary code.

What the audit looks like:

# Find every include/require that might touch user input
grep -rn "include\s*(\$_" .
grep -rn "require\s*(\$_" .
grep -rn "include\s*(.*\$_GET" .
grep -rn "include\s*(.*\$_POST" .

The fix:

Never construct file paths from user input. Use a whitelist:

// Safe — user input selects from a known-good list only
$allowed_pages = ['home', 'about', 'services', 'contact'];
$page = $_GET['page'] ?? 'home';

if (!in_array($page, $allowed_pages, true)) {
    $page = 'home';
}

include("templates/{$page}.php");

Also verify allow_url_include and allow_url_fopen are disabled in php.ini:

allow_url_include = Off
allow_url_fopen   = Off

Severity: Critical. LFI is a direct path to remote code execution.


5. Verbose Error Output in Production

This one is easy to overlook because it doesn't feel like a security issue — it feels like a debugging convenience. But a stack trace served to an unauthenticated user contains the full file path, the PHP version, the database schema, and sometimes credentials passed as function arguments.

What an attacker learns from a single PHP error:

Fatal error: Uncaught PDOException: SQLSTATE[42000]:
Syntax error or access violation in
/var/www/html/sites/client-name/app/Models/User.php:47
Stack trace:
#0 /var/www/html/sites/client-name/app/Models/User.php(47):
PDO->prepare('SELECT * FROM u...')

From this single error: the server path structure, the framework layout, the database driver, the table name, and the fact that the query is unparameterised.

Fix it in two lines:

// In production: disable display, enable logging
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php/app-errors.log');
error_reporting(E_ALL);

Or in php.ini:

display_errors  = Off
log_errors      = On
error_log       = /var/log/php/errors.log

A proper application-level error handler should catch exceptions before they bubble to the PHP engine and return a controlled error response:

set_exception_handler(function (Throwable $e) {
    // Log the full trace internally
    error_log($e->getMessage() . "\n" . $e->getTraceAsString());

    // Return nothing useful to the client
    http_response_code(500);
    echo json_encode(['error' => 'An unexpected error occurred.']);
    exit;
});

Severity: Medium (standalone) but amplifies every other vulnerability — it hands attackers the map.


What This Looks Like in Practice

These five risks don't exist in isolation. A typical abandoned PHP application has SQL injection in the authentication layer, an unpatched third-party library with a known CVE, and verbose error output that reveals the database schema. Together, they represent a path from unauthenticated HTTP request to full database extraction in under ten minutes.

The good news: none of these require a rewrite. A structured security audit followed by targeted remediation can harden a legacy PHP application in a matter of weeks — not months. The work is methodical, not heroic.

If your application hasn't had a security review in the past 12 months, it's overdue.