Предположим, имеется скрипт, отображающий список пользователей из данного города, принимающий в качестве GET-параметра id города. Обращение к скрипту будет происходить с помощью HTTP по адресу/users.php?cityid=20
<?php // подключение к базе данных $sql = "SELECT username, realname FROM users WHERE cityid=" . $_GET['cityid']; $result = mysql_query($sql) or die(mysql_error()); // обработка результата и отображение списка пользователей ?>
В скрипте выше разработчик вставляет GET-параметр в SQL-запрос, подразумевая, что в GET-параметре всегда будет число. Злоумышленник может передать в качестве параметра строку и тем самым повредить запрос. Например, он обратится к скрипту как /users.php?cityid=20; DELETE * FROM users SQL-запрос получится таким:
SELECT username, realname FROM users WHERE cityid=20; DELETE * FROM users
Получается, что сервер MySQL получит не один запрос, а уже два, второй из которых нежелателен. К счастью, от этого существует защита: не допускается передавать два запроса одним mysql_query(). Поэтому злоумышленник должен встраивать свой кусок хитрее. Например, так: /users.php?cityid=20 UNION SELECT username, password AS realname FROM users Запрос к БД будет иметь вид:
SELECT username, realname FROM users WHERE cityid=20 UNION SELECT username, password AS realname FROM users
Запрос выполнится, и скрипт выдаст не только пользователей из заданного города, но и список всех пользователей, у которых вместо реального имени отобразится пароль.
Как защититься?
Давайте заключим пользователькую информацию в одинарные кавычки. Поможет ли это?
$sql = "SELECT username, realname FROM users WHERE cityid='" . $_GET['cityid'] . "'";
Оказывается, что такая мера не помогает. Злоумышленник сможет передать параметр cityid, содержащий одинарные кавычки, нейтрализующие эффект от защитных кавычек. В качестве параметра cityid он передаст 20' UNION SELECT username, password AS realname FROM users WHERE '1, что приведет с формированию следующего запроса:
SELECT username, realname FROM users WHERE cityid='20' UNION SELECT username, password AS realname FROM users WHERE '1'
Из примера выше видно, что заключить в одиночные кавычки недостаточно. Необходимо также экранировать все кавычки, содержащиеся в строке. Для этого в PHP предусмотрена функция mysql_real_escape_string(), которая добавляет обратный слеш перед каждой кавычкой, обратной кавычкой и некоторыми другим спецсимволами. Рассмотрим код:
$sql = "SELECT username, realname FROM users WHERE cityid='" .mysql_real_escape_string($_GET['cityid']) . "'";
В случае использования mysql_real_escape_string() действия злоумышленника приведут к формированию запроса, который не является опасным, так как весь текст теперь внутри кавычек:
SELECT username, realname FROM users WHERE cityid='20\' UNION SELECT username, password AS realname FROM users WHERE \'1'
Итак, чтобы защититься от SQL-инъекций, все внешние параметры, которые могут содержать текст, должны быть перед включением в SQL-запрос обработаны с помощью mysql_real_escape_string() и заключены в одиночные кавычки.
Если известно, что параметр должен принимать числовое значение числовым, его можно привести к числовому виду явно с помощью функции intval() или floatval(). В данном примере мы могли бы использовать:
$sql = "SELECT username, realname FROM users WHERE cityid='" . intval($_GET['cityid']) . "'";