Skip to content

PHP Tutorial PHP Security Exercise

Practice: Fix the Vulnerable Code

Lesson 303 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

Each snippet below has a real vulnerability. Find it, explain it, then fix it. Try before you open the solution.

Exercise 1: the login query

index.php
PHP
<?php
$email = $_POST["email"];
$password = $_POST["password"];

$sql = "SELECT * FROM users WHERE email = '$email' AND password = '" . md5($password) . "'";
$user = $pdo->query($sql)->fetch();

What are the two problems?

Show the answer
  1. SQL injection: $email is joined into the SQL.
  2. Weak password storage: md5() is a fast, unsalted hash.

A safer version:

index.php
PHP
<?php
$pdo = require __DIR__ . "/db.php";

$pdo->prepare("UPDATE users SET password_hash = ? WHERE email = 'ana@example.com'")
    ->execute([password_hash("a long passphrase", PASSWORD_DEFAULT)]);

$_POST = ["email" => "ana@example.com", "password" => "a long passphrase"];

$statement = $pdo->prepare("SELECT id, password_hash FROM users WHERE email = :email");
$statement->execute(["email" => strtolower(trim((string) $_POST["email"]))]);
$user = $statement->fetch();

$loggedIn = $user !== false && password_verify((string) $_POST["password"], $user["password_hash"]);

var_dump($loggedIn);
Output
OUTPUT
bool(true)

Exercise 2: the greeting page

index.php
PHP
<?php
echo "<h1>Welcome, " . $_GET["name"] . "</h1>";
echo '<a href="profile.php?user=' . $_GET["name"] . '">Your profile</a>';
Show the answer

Both lines are XSS holes. The first needs HTML escaping. The second also needs the value URL-encoded, and the whole attribute HTML-escaped.

index.php
PHP
<?php
$_GET["name"] = '"><script>alert(1)</script>';

$name = (string) ($_GET["name"] ?? "");

echo "<h1>Welcome, " . htmlspecialchars($name, ENT_QUOTES, "UTF-8") . "</h1>\n";
echo '<a href="profile.php?' . htmlspecialchars(http_build_query(["user" => $name]), ENT_QUOTES, "UTF-8") . '">Your profile</a>';
Output
OUTPUT
<h1>Welcome, &quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;</h1>
<a href="profile.php?user=%22%3E%3Cscript%3Ealert%281%29%3C%2Fscript%3E">Your profile</a>
index.php
PHP
<?php
// delete.php?id=5
$pdo->exec("DELETE FROM posts WHERE id = " . $_GET["id"]);

How many problems can you find?

Show the answer
  1. SQL injection ($_GET["id"] goes into the SQL).
  2. CSRF and unsafe method: a GET link deletes data, so it can be triggered by an image tag or a crawler.
  3. No login or ownership check: anyone can delete any post.
  4. No validation that id is a number.
index.php
PHP
<?php
session_start();

if ($_SERVER["REQUEST_METHOD"] !== "POST") { http_response_code(405); exit; }
if (!csrfValid($_SESSION, $_POST["csrf_token"] ?? null)) { http_response_code(403); exit; }

$userId = requireLogin();
$id = filter_var($_POST["id"] ?? null, FILTER_VALIDATE_INT);

if ($id === false) { http_response_code(400); exit; }

$statement = $pdo->prepare("DELETE FROM posts WHERE id = :id AND user_id = :user");
$statement->execute(["id" => $id, "user" => $userId]);

Exercise 4: the page loader

index.php
PHP
<?php
include $_GET["page"] . ".php";
Show the answer

A local file inclusion hole. A visitor can request ?page=../config/database or similar and run or expose files. Use an allow-list.

index.php
PHP
<?php
$pages = ["home" => "pages/home.php", "about" => "pages/about.php"];

$requested = "../../etc/passwd";

echo $pages[$requested] ?? $pages["home"];
Output
OUTPUT
pages/home.php

Exercise 5: the avatar upload

index.php
PHP
<?php
move_uploaded_file($_FILES["avatar"]["tmp_name"], "uploads/" . $_FILES["avatar"]["name"]);
Show the answer

The visitor controls the file name and content, and the file lands inside the public folder. An attacker can upload shell.php. Fix: check the real type with finfo, allow only specific image types, use a random name with an extension you choose, and store the file outside the web root. See Secure File Uploads.

index.php
PHP
<?php
setcookie("user_id", (string) $user["id"], time() + 86400 * 30);
// later: if (isset($_COOKIE["user_id"])) { $_SESSION["user_id"] = $_COOKIE["user_id"]; }
Show the answer

Anyone can set user_id=1 in their browser and become user 1 (or an admin). The cookie must hold a random token that is checked against a hash stored in the database. See Remember Me.