SQL-инъекции остаются в топ-3 критических уязвимостей веб-приложений согласно OWASP 2024. Каждый день хакеры эксплуатируют эту уязвимость, получая доступ к базам данных тысяч сайтов. В этом руководстве разберем все современные методы защиты PHP-приложений от SQL-атак, от базовых prepared statements до продвинутых техник валидации. Вы получите готовые решения, которые можно применить в своих проектах уже сегодня.
Это вторая часть цикла статей о безопасности PHP. Первая часть посвящена защите от XSS-атак (https://forum.antichat.xyz/threads/561587/), рекомендую ознакомиться для комплексного понимания безопасности веб-приложений.
Содержание
Что такое SQL-инъекция: типы и механизм работы (https://forum.antichat.xyz/threads/561661/)
7 проверенных методов защиты от SQL-инъекций (https://forum.antichat.xyz/threads/561661/)
Примеры уязвимого и безопасного кода (https://forum.antichat.xyz/threads/561661/)
Реальные кейсы взлома и их предотвращение (https://forum.antichat.xyz/threads/561661/)
Инструменты для тестирования безопасности (https://forum.antichat.xyz/threads/561661/)
Чек-лист безопасности разработчика (https://forum.antichat.xyz/threads/561661/)
Частые вопросы (FAQ) (https://forum.antichat.xyz/threads/561661/)
Что такое SQL-инъекция: типы и механизм работы
SQL-инъекция это метод атаки, при котором злоумышленник внедряет вредоносный SQL-код через пользовательский ввод, получая несанкционированный доступ к базе данных. Эта уязвимость возникает, когда приложение напрямую конкатенирует пользовательские данные с SQL-запросами без должной валидации и экранирования.
Основные типы SQL-инъекций
1. Classic SQL Injection (Union-based)
Злоумышленник использует оператор UNION для объединения результатов своего запроса с легитимным:
PHP:
// Уязвимый код
$id
=
$_GET
[
'id'
]
;
$query
=
"SELECT * FROM users WHERE id = $id"
;
// Атака: ?id=1 UNION SELECT username, password FROM admin
https://forum.antichat.xyz/attachments/4794096/1.png
2. Blind SQL Injection (Boolean-based)
Атакующий извлекает данные побитово через true/false ответы:
PHP:
// Атака: ?id=1 AND SUBSTRING(password,1,1)='a'
// Если страница загружается нормально - первый символ пароля 'a'
3. Time-based Blind SQL Injection
Использует временные задержки для извлечения данных:
PHP:
// Атака: ?id=1 AND IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0)
// Если страница грузится 5 секунд - первый символ 'a'
4. Error-based SQL Injection
Эксплуатирует сообщения об ошибках базы данных:
PHP:
// Атака провоцирует ошибку с выводом структуры БД
// ?id=1 AND extractvalue(1, concat(0x7e, version()))
5. Second-Order SQL Injection
Вредоносные данные сохраняются в БД и выполняются позже:
PHP:
// Регистрация пользователя: admin'--
// При следующем запросе происходит инъекция
7 проверенных методов защиты от SQL-инъекций
1. Prepared Statements с PDO (рекомендуемый метод)
PDO (PHP Data Objects) предоставляет унифицированный интерфейс для работы с различными СУБД. Prepared statements полностью разделяют SQL-логику от данных:
PHP:
PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // Важно для настоящих prepared statements
];
try {
$pdo = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
// Логируем ошибку, не показываем пользователю
error_log($e->getMessage());
die('Ошибка подключения к базе данных');
}
// Пример безопасного запроса с именованными параметрами
function getUserById($pdo, $userId) {
$sql = "SELECT id, username, email, created_at
FROM users
WHERE id = :user_id AND status = :status";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':user_id' => $userId,
':status' => 'active'
]);
return $stmt->fetch();
}
// Пример с позиционными параметрами
function searchUsers($pdo, $search, $limit = 10) {
$sql = "SELECT * FROM users
WHERE username LIKE ?
ORDER BY created_at DESC
LIMIT ?";
$stmt = $pdo->prepare($sql);
$stmt->execute(["%$search%", $limit]);
return $stmt->fetchAll();
}
// Массовая вставка данных
function insertMultipleUsers($pdo, $users) {
$sql = "INSERT INTO users (username, email, password_hash)
VALUES (:username, :email, :password)";
$stmt = $pdo->prepare($sql);
foreach ($users as $user) {
$stmt->execute([
':username' => $user['username'],
':email' => $user['email'],
':password' => password_hash($user['password'], PASSWORD_DEFAULT)
]);
}
}
https://forum.antichat.xyz/attachments/4794096/6.png
2. Prepared Statements с MySQLi
https://forum.antichat.xyz/attachments/4794096/7.png
MySQLi также поддерживает prepared statements, но с другим синтаксисом:
PHP:
connect_error) {
error_log($mysqli->connect_error);
die('Ошибка подключения');
}
// Установка кодировки - критично для безопасности
$mysqli->set_charset('utf8mb4');
// Пример безопасного SELECT запроса
function getUserByEmail($mysqli, $email) {
$sql = "SELECT id, username, created_at FROM users WHERE email = ?";
if ($stmt = $mysqli->prepare($sql)) {
// 's' означает string, 'i' - integer, 'd' - double, 'b' - blob
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
$stmt->close();
return $user;
}
return null;
}
// Пример с множественными параметрами
function updateUserProfile($mysqli, $userId, $username, $bio) {
$sql = "UPDATE users SET username = ?, bio = ? WHERE id = ?";
if ($stmt = $mysqli->prepare($sql)) {
$stmt->bind_param("ssi", $username, $bio, $userId);
$success = $stmt->execute();
$stmt->close();
return $success;
}
return false;
}
// Пример безопасной процедуры с транзакцией
function transferMoney($mysqli, $fromId, $toId, $amount) {
$mysqli->autocommit(false);
try {
// Снимаем деньги
$sql1 = "UPDATE accounts SET balance = balance - ? WHERE user_id = ? AND balance >= ?";
$stmt1 = $mysqli->prepare($sql1);
$stmt1->bind_param("did", $amount, $fromId, $amount);
$stmt1->execute();
if ($stmt1->affected_rows === 0) {
throw new Exception("Недостаточно средств");
}
// Зачисляем деньги
$sql2 = "UPDATE accounts SET balance = balance + ? WHERE user_id = ?";
$stmt2 = $mysqli->prepare($sql2);
$stmt2->bind_param("di", $amount, $toId);
$stmt2->execute();
$mysqli->commit();
return true;
} catch (Exception $e) {
$mysqli->rollback();
error_log($e->getMessage());
return false;
}
}
3. Валидация и санитизация входных данных
Валидация должна происходить на нескольких уровнях:
PHP:
[]];
if ($min !== null) {
$options['options']['min_range'] = $min;
}
if ($max !== null) {
$options['options']['max_range'] = $max;
}
return filter_var($input, FILTER_VALIDATE_INT, $options);
}
// Валидация email
public static function validateEmail($email) {
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : false;
}
// Валидация строк с whitelist символов
public static function validateString($input, $pattern = '/^[a-zA-Z0-9_\-]+$/') {
$input = trim($input);
if (preg_match($pattern, $input)) {
return $input;
}
return false;
}
// Валидация UUID
public static function validateUUID($uuid) {
$pattern = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';
return preg_match($pattern, $uuid) ? $uuid : false;
}
// Валидация дат
public static function validateDate($date, $format = 'Y-m-d') {
$d = DateTime::createFromFormat($format, $date);
return $d && $d->format($format) === $date ? $date : false;
}
// Комплексная валидация для формы
public static function validateUserForm($data) {
$errors = [];
$clean = [];
// Username
if (empty($data['username'])) {
$errors['username'] = 'Username обязателен';
} else {
$username = self::validateString($data['username'], '/^[a-zA-Z0-9_]{3,20}$/');
if (!$username) {
$errors['username'] = 'Username может содержать только буквы, цифры и _ (3-20 символов)';
} else {
$clean['username'] = $username;
}
}
// Email
if (empty($data['email'])) {
$errors['email'] = 'Email обязателен';
} else {
$email = self::validateEmail($data['email']);
if (!$email) {
$errors['email'] = 'Некорректный email';
} else {
$clean['email'] = $email;
}
}
// Age
if (isset($data['age'])) {
$age = self::validateInt($data['age'], 13, 120);
if ($age === false) {
$errors['age'] = 'Возраст должен быть от 13 до 120 лет';
} else {
$clean['age'] = $age;
}
}
return [
'valid' => empty($errors),
'data' => $clean,
'errors' => $errors
];
}
}
// Использование валидатора
$formData = $_POST;
$validation = InputValidator::validateUserForm($formData);
if ($validation['valid']) {
// Используем очищенные данные
$userData = $validation['data'];
// Сохраняем в БД через prepared statements
} else {
// Показываем ошибки
foreach ($validation['errors'] as $field => $error) {
echo "Ошибка в поле $field: $error
";
}
}
https://forum.antichat.xyz/attachments/4794096/8.png
4. Использование ORM (Eloquent, Doctrine)
ORM автоматически защищают от SQL-инъекций:
PHP:
first();
$users = User::where('status', 'active')
->where('created_at', '>=', now()->subDays(30))
->orderBy('name')
->paginate(10);
// Query Builder тоже безопасен
$users = DB::table('users')
->where('votes', '>', 100)
->orWhere('name', 'like', '%john%')
->get();
// Raw запросы с параметрами
$results = DB::select('SELECT * FROM users WHERE id = ?', [$id]);
$affected = DB::update('UPDATE users SET votes = 100 WHERE name = ?', [$name]);
// Пример с Doctrine ORM
use Doctrine\ORM\EntityManager;
class UserRepository {
private $em;
public function __construct(EntityManager $em) {
$this->em = $em;
}
public function findActiveUsers($limit = 10) {
$qb = $this->em->createQueryBuilder();
return $qb->select('u')
->from('App\Entity\User', 'u')
->where('u.status = :status')
->setParameter('status', 'active')
->orderBy('u.createdAt', 'DESC')
->setMaxResults($limit)
->getQuery()
->getResult();
}
public function searchUsers($term) {
$query = $this->em->createQuery(
'SELECT u FROM App\Entity\User u
WHERE u.username LIKE :term OR u.email LIKE :term'
);
$query->setParameter('term', '%' . $term . '%');
return $query->getResult();
}
}
5. Хранимые процедуры
Хранимые процедуры добавляют дополнительный уровень защиты:
PHP:
prepare("CALL GetUserById(:userId)");
$stmt->bindParam(':userId', $userId, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch();
}
// Хранимая процедура с OUTPUT параметром
$createProcedure2 = "
CREATE PROCEDURE AuthenticateUser(
IN userEmail VARCHAR(255),
IN userPassword VARCHAR(255),
OUT userId INT,
OUT authResult BOOLEAN
)
BEGIN
DECLARE storedPassword VARCHAR(255);
SELECT id, password_hash INTO userId, storedPassword
FROM users
WHERE email = userEmail
LIMIT 1;
IF userId IS NOT NULL AND PASSWORD_VERIFY(userPassword, storedPassword) THEN
SET authResult = TRUE;
ELSE
SET authResult = FALSE;
SET userId = NULL;
END IF;
END;
";
// Использование процедуры с OUTPUT
function authenticateUser($mysqli, $email, $password) {
$stmt = $mysqli->prepare("CALL AuthenticateUser(?, ?, @userId, @authResult)");
$stmt->bind_param("ss", $email, $password);
$stmt->execute();
$select = $mysqli->query("SELECT @userId, @authResult");
$result = $select->fetch_assoc();
return [
'authenticated' => (bool)$result['@authResult'],
'user_id' => $result['@userId']
];
}
6. Принцип наименьших привилегий
Настройка правильных прав доступа к БД:
PHP:
PDO::ERRMODE_EXCEPTION]
);
}
return self::$connections['read'];
}
// Пользователь для записи
public static function getWriteConnection() {
if (!isset(self::$connections['write'])) {
self::$connections['write'] = new PDO(
'mysql:host=localhost;dbname=myapp;charset=utf8mb4 ',
'app_writer', // Пользователь с правами INSERT, UPDATE, DELETE
'write_password',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
return self::$connections['write'];
}
// Административный пользователь (используется редко)
public static function getAdminConnection() {
// Дополнительная проверка прав
if (!self::isAdminContext()) {
throw new Exception("Доступ запрещен");
}
if (!isset(self::$connections['admin'])) {
self::$connections['admin'] = new PDO(
'mysql:host=localhost;dbname=myapp;charset=utf8mb4 ',
'app_admin',
'admin_password',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
return self::$connections['admin'];
}
private static function isAdminContext() {
// Проверка, что код выполняется в административном контексте
return defined('ADMIN_CONTEXT') && ADMIN_CONTEXT === true;
}
}
// SQL для создания пользователей с ограниченными правами
/*
-- Пользователь для чтения
CREATE USER 'app_reader'@'localhost' IDENTIFIED BY 'read_password';
GRANT SELECT ON myapp.* TO 'app_reader'@'localhost';
-- Пользователь для записи
CREATE USER 'app_writer'@'localhost' IDENTIFIED BY 'write_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'app_writer'@'localhost';
-- Административный пользователь
CREATE USER 'app_admin'@'localhost' IDENTIFIED BY 'admin_password';
GRANT ALL PRIVILEGES ON myapp.* TO 'app_admin'@'localhost';
FLUSH PRIVILEGES;
*/
7. Web Application Firewall (WAF) и мониторинг
Реализация простого WAF на уровне приложения:
PHP:
/i',
'/javascript:/i',
'/onerror\s*=/i',
'/onload\s*=/i',
'/eval\s*\(/i',
'/base64_decode/i'
];
private $logger;
public function __construct($logger = null) {
$this->logger = $logger;
}
public function checkRequest($input) {
$threats = [];
// Проверяем все входящие данные
$checkData = is_array($input) ? json_encode($input) : (string)$input;
foreach ($this->suspiciousPatterns as $pattern) {
if (preg_match($pattern, $checkData)) {
$threats[] = $pattern;
}
}
if (!empty($threats)) {
$this->logThreat($threats, $input);
return false;
}
return true;
}
private function logThreat($threats, $input) {
$logData = [
'timestamp' => date('Y-m-d H:i:s'),
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'threats' => $threats,
'input' => $input,
'uri' => $_SERVER['REQUEST_URI'] ?? 'unknown'
];
// Логирование в файл или SIEM систему
if ($this->logger) {
$this->logger->warning('SQL Injection attempt detected', $logData);
} else {
error_log('SQL Injection attempt: ' . json_encode($logData));
}
// Можно добавить блокировку IP после N попыток
$this->checkForRepeatedAttempts($_SERVER['REMOTE_ADDR']);
}
private function checkForRepeatedAttempts($ip) {
// Реализация rate limiting и блокировки
$cacheKey = "sql_attempts_$ip";
$attempts = apcu_fetch($cacheKey) ?: 0;
$attempts++;
apcu_store($cacheKey, $attempts, 3600); // Хранить 1 час
if ($attempts > 5) {
// Блокировка IP или отправка алерта
$this->blockIp($ip);
}
}
private function blockIp($ip) {
// Добавление в blacklist
file_put_contents('/var/www/blacklist.txt', "$ip\n", FILE_APPEND | LOCK_EX);
// Или использование iptables
// exec("iptables -A INPUT -s $ip -j DROP");
}
}
// Использование
$firewall = new SimpleSQLFirewall();
// Проверка всех входящих данных
$allInput = array_merge($_GET, $_POST, $_COOKIE);
if (!$firewall->checkRequest($allInput)) {
http_response_code(403);
die('Подозрительная активность заблокирована');
}
Дополнительный уровень защиты: Помимо WAF, рекомендую настроить Content Security Policy (CSP) для предотвращения XSS-атак, которые часто используются в связке с SQL-инъекциями. Подробное руководство по настройке CSP доступно в статье для начинающих (https://forum.antichat.xyz/threads/583923/).
Примеры уязвимого и безопасного кода
https://forum.antichat.xyz/attachments/4794096/2.png
Пример 1: Аутентификация пользователя
PHP:
query($query);
if ($result->num_rows > 0) {
return $result->fetch_assoc();
}
return false;
}
// Атака: username = admin' --
// Результат: вход без пароля
// ✅ БЕЗОПАСНЫЙ КОД
function authenticateUserSecure($pdo, $username, $password) {
$sql = "SELECT id, username, password_hash FROM users WHERE username = :username LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([':username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
// Удаляем хэш пароля из результата
unset($user['password_hash']);
return $user;
}
return false;
}
Пример 2: Поиск по сайту
PHP:
query($query);
}
// Атака: search = %' UNION SELECT * FROM credit_cards --
// ✅ БЕЗОПАСНЫЙ КОД
function searchProductsSecure($pdo, $search, $category = null, $limit = 20) {
// Базовый запрос
$sql = "SELECT id, name, price, description, image_url
FROM products
WHERE (name LIKE :search OR description LIKE :search)";
$params = [':search' => "%$search%"];
// Динамическое построение запроса
if ($category) {
$sql .= " AND category_id = :category";
$params[':category'] = $category;
}
$sql .= " ORDER BY relevance DESC LIMIT :limit";
$stmt = $pdo->prepare($sql);
// Bind параметров с указанием типа
foreach ($params as $key => $value) {
if ($key === ':limit') {
$stmt->bindValue($key, $value, PDO::PARAM_INT);
} else {
$stmt->bindValue($key, $value);
}
}
$stmt->execute();
return $stmt->fetchAll();
}
Пример 3: Динамическая сортировка
PHP:
query($query);
}
// Атака: sort = (SELECT password FROM admin)
// ✅ БЕЗОПАСНЫЙ КОД
function getUsersSecure($pdo, $sort = 'created_at', $direction = 'DESC') {
// Whitelist разрешенных полей для сортировки
$allowedSorts = [
'created_at' => 'created_at',
'username' => 'username',
'email' => 'email',
'last_login' => 'last_login'
];
$allowedDirections = ['ASC', 'DESC'];
// Валидация параметров сортировки
$sortField = $allowedSorts[$sort] ?? 'created_at';
$sortDirection = in_array(strtoupper($direction), $allowedDirections)
? strtoupper($direction)
: 'DESC';
// Безопасный запрос
$sql = "SELECT id, username, email, created_at
FROM users
WHERE status = :status
ORDER BY {$sortField} {$sortDirection}";
$stmt = $pdo->prepare($sql);
$stmt->execute([':status' => 'active']);
return $stmt->fetchAll();
}
Реальные кейсы взлома и их предотвращение
Кейс 1: Взлом интернет-магазина через корзину
Сценарий атаки:
В 2023 году хакеры взломали популярный интернет-магазин через уязвимость в функции обновления корзины. Код выглядел так:
PHP:
// Уязвимый код магазина
$product_id
=
$_POST
[
'product_id'
]
;
$quantity
=
$_POST
[
'quantity'
]
;
$user_id
=
$_SESSION
[
'user_id'
]
;
$query
=
"UPDATE cart SET quantity = $quantity WHERE product_id = $product_id AND user_id = $user_id"
;
mysqli_query
(
$connection
,
$query
)
;
Эксплуатация:
Хакеры изменили запрос, установив отрицательную цену товара:
Код:
POST: quantity = 1, price = -1000 WHERE product_id = 1 OR 1=1 --
Решение:
PHP:
['min_range' => 0, 'max_range' => 99]
]);
if ($quantity === false) {
throw new InvalidArgumentException('Некорректное количество товара');
}
// Проверка существования товара
$checkSql = "SELECT id, price, stock FROM products WHERE id = :product_id";
$checkStmt = $this->pdo->prepare($checkSql);
$checkStmt->execute([':product_id' => $productId]);
$product = $checkStmt->fetch();
if (!$product) {
throw new Exception('Товар не найден');
}
if ($product['stock'] pdo->prepare($updateSql);
return $stmt->execute([
':user_id' => $userId,
':product_id' => $productId,
':quantity' => $quantity,
':price' => $product['price']
]);
}
}
Кейс 2: Утечка базы данных через API endpoint
Сценарий атаки:
API для мобильного приложения имел уязвимость в endpoint получения профиля:
PHP:
// Уязвимый API endpoint
Route
:
:
get
(
'/api/user/{id}'
,
function
(
$id
)
{
$user
=
DB
:
:
select
(
"SELECT * FROM users WHERE id = $id"
)
;
return
response
(
)
-
>
json
(
$user
)
;
}
)
;
Эксплуатация:
Код:
GET /api/user/1 UNION SELECT credit_card_number, cvv, expiry FROM payment_methods
Решение:
PHP:
json(['error' => 'Invalid user ID'], 400);
}
// Проверка авторизации
if (!$this->canAccessProfile($request->user(), $id)) {
return response()->json(['error' => 'Access denied'], 403);
}
// Безопасный запрос с ограниченными полями
$user = User::select(['id', 'username', 'email', 'created_at'])
->where('id', $id)
->where('status', 'active')
->first();
if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}
// Логирование доступа
$this->logAccess($request->user()->id, $id);
// Добавление rate limiting заголовков
return response()->json($user)
->header('X-RateLimit-Limit', 60)
->header('X-RateLimit-Remaining', $this->getRemainingRequests());
}
private function canAccessProfile($currentUser, $targetId) {
// Пользователь может видеть только свой профиль
// или если он админ
return $currentUser->id == $targetId || $currentUser->isAdmin();
}
private function logAccess($accessorId, $targetId) {
DB::table('api_access_logs')->insert([
'accessor_id' => $accessorId,
'target_id' => $targetId,
'endpoint' => 'user_profile',
'ip' => request()->ip(),
'created_at' => now()
]);
}
}
Инструменты для тестирования безопасности
1. SQLMap - автоматическое тестирование
https://forum.antichat.xyz/attachments/4794096/3.png
https://forum.antichat.xyz/attachments/4794096/3.png
Bash:
# Базовое сканирование
sqlmap -u
"http://example.com/page.php?id=1"
--batch
# Сканирование с cookie
sqlmap -u
"http://example.com/page.php?id=1"
--cookie
=
"PHPSESSID=abc123"
# Сканирование POST запросов
sqlmap -u
"http://example.com/login.php"
--data
=
"username=admin&password=test"
# Получение структуры БД
sqlmap -u
"http://example.com/page.php?id=1"
--tables
# Дамп конкретной таблицы
sqlmap -u
"http://example.com/page.php?id=1"
-D database -T
users
--dump
2. Тестирование вручную
PHP:
testPayloads as $payload) {
$testUrl = $url . "?$parameter=" . urlencode($payload);
$startTime = microtime(true);
$response = @file_get_contents($testUrl);
$responseTime = microtime(true) - $startTime;
$results[] = [
'payload' => $payload,
'response_time' => $responseTime,
'error_detected' => $this->checkForSQLErrors($response),
'status_code' => $http_response_header[0] ?? 'unknown'
];
}
return $results;
}
private function checkForSQLErrors($response) {
$errorPatterns = [
'/SQL syntax/',
'/mysql_fetch/',
'/Warning.*mysql/',
'/MySQLSyntaxErrorException/',
'/PostgreSQL/',
'/valid MySQL result/',
'/mssql_/',
'/SQLServer JDBC Driver/',
'/Oracle error/',
'/Oracle driver/'
];
foreach ($errorPatterns as $pattern) {
if (preg_match($pattern, $response)) {
return true;
}
}
return false;
}
}
3. Burp Suite настройка для PHP
Конфигурация Burp Suite для тестирования PHP приложений:
Scanner настройки:
Включить "SQL Injection" в активных проверках
Установить insertion points на все параметры
Использовать грамотные payloads для PHP/MySQL
Intruder настройки:
Payload type: Simple list
Добавить специфичные для PHP payloads
Настроить grep-match для PHP ошибок
Расширения для PHP:
SQLiPy Sqlmap Integration
PHP Object Injection Check
Autorize для проверки авторизации
Чек-лист безопасности разработчика
Обязательные проверки перед деплоем
Все SQL запросы используют prepared statements
PDO или MySQLi с параметризацией
Никаких прямых конкатенаций с пользовательским вводом
Валидация всех входных данных
Whitelist подход для разрешенных символов
Проверка типов данных
Ограничение длины строк
Правильная обработка ошибок
Отключен вывод ошибок в продакшене
Логирование ошибок в файлы
Общие сообщения для пользователей
Настроены права доступа к БД
Разные пользователи для чтения/записи
Минимальные необходимые привилегии
Отключен FILE privilege
Включены механизмы защиты
WAF или правила фильтрации
Rate limiting для API
Мониторинг подозрительной активности
Регулярные обновления
PHP версии 8.0+
Актуальные версии MySQL/PostgreSQL
Обновленные библиотеки и фреймворки
Тестирование безопасности
Автоматические тесты на SQL инъекции
Ручное тестирование критических endpoint'ов
Регулярный аудит кода
Конфигурация php.ini для безопасности
INI:
; Отключение опасных функций
disable_functions
= exec,passthru,shell_exec,system,proc_open,popen,cu rl_exec,curl_multi_exec,parse_ini_file,show_source
; Отключение отображения ошибок
display_errors
= Off
log_errors
= On
error_log
= /var/log/php/error.log
; Ограничения
max_execution_time
= 30
max_input_time
= 60
memory_limit
= 128M
post_max_size
= 8M
upload_max_filesize
= 2M
; Безопасность сессий
session.cookie_httponly
= 1
session.cookie_secure
= 1
session.use_only_cookies
= 1
session.use_strict_mode
= 1
; Другие настройки безопасности
expose_php
= Off
allow_url_fopen
= Off
allow_url_include
= Off
Частые вопросы (FAQ)
Что такое SQL инъекция простыми словами?
SQL инъекция это уязвимость, позволяющая злоумышленнику выполнять произвольные SQL команды в базе данных через веб-приложение. Представьте, что ваше приложение это охранник, который проверяет пропуска (SQL запросы) на входе в здание (база данных). SQL инъекция это когда злоумышленник подделывает пропуск так, что охранник не только пускает его, но и дает ключи от всех помещений.
Как проверить свой сайт на SQL инъекции?
Базовая проверка включает:
Добавление одинарной кавычки (') в параметры URL
Использование SQLMap для автоматического сканирования
Проверка форм с payloads типа
' OR '1'='1
Мониторинг логов на SQL ошибки
Использование онлайн сканеров безопасности
Достаточно ли использовать mysqli_real_escape_string?
Нет,
mysqli_real_escape_string()
не является достаточной защитой. Эта функция экранирует специальные символы, но не защищает от всех типов инъекций, особенно в числовых полях. Всегда используйте prepared statements как основной метод защиты.
Какая разница между PDO и MySQLi для защиты?
Оба расширения поддерживают prepared statements и при правильном использовании одинаково безопасны. PDO имеет преимущества: поддержка разных СУБД, именованные параметры, более удобный API. MySQLi работает только с MySQL, но может быть быстрее для специфичных MySQL операций.
Можно ли делать динамические запросы безопасно?
Да, но с ограничениями:
Используйте whitelist для имен таблиц и колонок
Параметризируйте все значения через prepared statements
Никогда не конкатенируйте пользовательский ввод напрямую
Валидируйте все входные данные
Защищают ли ORM от SQL инъекций автоматически?
Современные ORM (Eloquent, Doctrine) защищают от SQL инъекций при использовании их стандартных методов. Однако, raw queries и неправильное использование могут создать уязвимости. Всегда параметризируйте raw запросы и избегайте динамического построения SQL.
Как часто нужно проводить аудит безопасности?
Рекомендуемая периодичность:
Перед каждым major релизом
После значительных изменений в коде работы с БД
Минимум раз в квартал для критических систем
При обнаружении новых типов атак
Как защититься от комплексных атак (SQL + CSRF + XSS)?
Современные атаки часто используют комбинацию уязвимостей. Например, через SQL-инъекцию злоумышленник может внедрить XSS-код в базу данных, который затем будет выполнен у всех пользователей (Stored XSS). Для полной защиты необходимо:
Использовать prepared statements для всех SQL запросов
Экранировать вывод данных из БД
Внедрить CSRF-токены для защиты от межсайтовой подделки запросов
Для углубленного изучения CSRF-атак рекомендую ознакомиться с анализом современных web-уязвимостей и методов обхода защиты (https://forum.antichat.xyz/threads/578214/), где подробно разобраны техники эксплуатации и защиты.
Что делать при обнаружении SQL инъекции в продакшене?
План действий:
Немедленно изолировать уязвимый функционал
Проанализировать логи на предмет эксплуатации
Применить временный фикс (WAF правило)
Разработать и протестировать постоянное решение
Провести аудит на похожие уязвимости
Уведомить пользователей если были скомпрометированы данные
Заключение
Защита от SQL инъекций требует комплексного подхода: от правильного написания кода до настройки инфраструктуры. Используйте prepared statements как основу защиты, добавляйте валидацию данных, применяйте принцип наименьших привилегий и регулярно тестируйте безопасность. Помните, что безопасность это не разовая задача, а постоянный процесс.
Начните с внедрения prepared statements во всех новых проектах, постепенно рефакторьте старый код и обязательно настройте мониторинг подозрительной активности. Современные инструменты и фреймворки делают защиту от SQL инъекций проще, но требуют понимания принципов безопасности для правильного применения.
Полезные ресурсы для углубленного изучения
OWASP SQL Injection Prevention Cheat Sheet
PHP Security Guide
Серия статей SQL-Injection для начинающих
Мощный, полный мануал по эксплуатации
SQL обучение
Практика: root-me.org, alexbers.com/sql/, dvwa.co.uk
PDO Prepared Statements
Всё про PDO
Начало здесь: Безопасный PHP. Защита от XSS атак (https://forum.antichat.xyz/threads/561587/)
vBulletin® v3.8.14, Copyright ©2000-2026, vBulletin Solutions, Inc. Перевод: zCarot