PS Product SecurityKnowledge Base

๐Ÿ˜ PHP Vulnerability Examples and Fixes

Use this page when: reviewing PHP services, old MVC applications, upload paths, or utility endpoints that mix user input with shell, filesystem, or SQL operations.

How to read these examples

  • Vulnerable snippet shows the unsafe habit.
  • Safer pattern shows the direction you want in production code.
  • Why it matters ties the defect to attacker value and business impact.
  • Review cue is phrased so it can become a pull-request comment or checklist item.

Example 1 โ€” SQL injection through string concatenation

Vulnerable snippet

$email = $_GET['email'];
$sql = "SELECT id, role FROM users WHERE email = '$email'";
$result = $pdo->query($sql);
$user = $result->fetch();

Safer pattern

$stmt = $pdo->prepare("SELECT id, role FROM users WHERE email = :email");
$stmt->execute(['email' => $_GET['email']]);
$user = $stmt->fetch();

Why it matters

  • String-built queries let attacker-controlled input change the meaning of the SQL statement.

Business impact

  • Account enumeration, full data exposure, privilege escalation, or destructive queries against production data.

Review cue

  • Do not concatenate request data into SQL. Require prepared statements or a safe query builder for all value binding.

Example 2 โ€” Reflected or stored XSS in output rendering

Vulnerable snippet

$comment = $_POST['comment'];
echo "<div class='comment'>$comment</div>";

Safer pattern

$comment = $_POST['comment'] ?? '';
echo "<div class='comment'>" . htmlspecialchars($comment, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</div>";

Why it matters

  • Raw HTML output turns user-controlled content into executable script in the victim browser.

Business impact

  • Session theft, account takeover, admin-action forgery, phishing overlays, and supply-chain-like browser compromise.

Review cue

  • For every rendering context, demand context-appropriate output encoding or safe templating helpers instead of raw echo.

Example 3 โ€” Unsafe file upload that trusts file name and extension

Vulnerable snippet

$name = $_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], __DIR__ . '/public/uploads/' . $name);
echo 'uploaded';

Safer pattern

$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);

if (!isset($allowed[$mime])) {
    http_response_code(400);
    exit('unsupported file type');
}

$random = bin2hex(random_bytes(16)) . '.' . $allowed[$mime];
$target = '/srv/app-uploads/' . $random; // outside web root
move_uploaded_file($_FILES['file']['tmp_name'], $target);

Why it matters

  • Original names, fake extensions, and web-root storage allow web shells, polyglots, overwrite attacks, and unsafe direct retrieval.

Business impact

  • Remote code execution, malware distribution, storage abuse, or public leakage of sensitive files.

Review cue

  • Require type allowlists, generated names, size limits, storage outside web root, and separate retrieval controls.

Example 4 โ€” Broken object-level authorization (IDOR)

Vulnerable snippet

$invoiceId = (int) $_GET['invoice_id'];
$stmt = $pdo->prepare("SELECT * FROM invoices WHERE id = :id");
$stmt->execute(['id' => $invoiceId]);
$invoice = $stmt->fetch();

Safer pattern

$invoiceId = (int) $_GET['invoice_id'];
$userId = $_SESSION['user_id'];

$stmt = $pdo->prepare(
    "SELECT * FROM invoices WHERE id = :id AND owner_user_id = :user_id"
);
$stmt->execute(['id' => $invoiceId, 'user_id' => $userId]);
$invoice = $stmt->fetch();

if (!$invoice) {
    http_response_code(404);
    exit('not found');
}

Why it matters

  • Looking up objects by ID without ownership or role scope means any authenticated user can pivot into another userโ€™s records.

Business impact

  • Cross-tenant data disclosure, unauthorized financial actions, privacy incidents, and support/admin trust breakdown.

Review cue

  • Every record fetch for sensitive data should prove scope: owner, tenant, team, or explicit delegated permission.

Example 5 โ€” Command injection through shell_exec

Vulnerable snippet

$term = $_GET['term'];
$output = shell_exec("grep -R $term /srv/docs");
echo nl2br($output);

Safer pattern

$term = $_GET['term'] ?? '';
if (!preg_match('/^[a-zA-Z0-9._ -]{1,40}$/', $term)) {
    http_response_code(400);
    exit('bad search term');
}

$matches = [];
foreach (glob('/srv/docs/*.txt') as $file) {
    foreach (file($file, FILE_IGNORE_NEW_LINES) as $line) {
        if (str_contains($line, $term)) {
            $matches[] = $line;
        }
    }
}

echo nl2br(htmlspecialchars(implode("
", $matches), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));

Why it matters

  • Shell interpolation makes metacharacters part of the command, not just part of the search term.

Business impact

  • Remote code execution, data exfiltration, lateral movement, and compromise of the host or its secrets.

Review cue

  • If user input reaches a shell, stop. Prefer native language functions, strict validation, and no shell expansion.

Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.