SQL-инъекции: как распознать и защититься

Приложения и сайты сегодня зачастую используют базы данных на основе SQL-языка. С помощью него веб-приложение формирует запрос-обращение к необходимой странице и ищет в базе нужную информацию. Именно из-за распространенности SQL-языка SQL-инъекция или внедрение SQL-кода один из самых популярных способов атаки на сайты, программы и приложения, которые работают с базами данных. Внедрение такой уязвимости дает преступнику возможность вмешиваться в запросы программы к своим базам данных.
Руководитель группы защиты инфраструктурных ИТ компании «Газинформсервис»
SQL-инъекция – это атака, которая базируется на внедрении произвольного SQL-кода в легитимный запрос к базе данных. Например, у вас есть веб-приложение – интернет-магазин, где в базе данных хранится информация о товарах. Выбирая на сайте нужный товар, вы складываете его в корзину, а логика сайта подсчитывает общую стоимость. Если нарушить взаимодействие приложения с базой данных, изменяя запросы к этой базе, можно, например, изменить стоимость товаров в корзине.
Злоумышленник с помощью внедрение SQL-кода получает возможность вносить изменения, добавлять и удалять данные. Помимо этого, хакер может получить доступ к паролям и другой конфиденциальной информации, а также вносить изменения в команды ресурса, который атакует.
Типы инъекций
- SQL injection union — атака производится при помощи использования SQL-оператора Union для объединения нескольких таблиц и получения данных из смежных таблиц (напрямую туда доступа нет).
- SQL-injection boolean — называют слепой инъекцией. Здесь система управления базами данных не выдает как таковых ошибок, а запрос конструируется так, чтобы, например, наблюдать за задержкой выполнения вводимых команд. Информация извлекается исходя из реакции на условные выражения.
- Слепая SQL-injection на основе булевых операций (или инференциальная SQL-инъекцией) – в параметр HTTP-запроса передаются несколько допустимых утверждений, результатом которых является true и false. Сравнивая страницу ответа между обоими условиями, атакующий может сделать вывод о том, была ли инъекция успешной или нет.
- Временная слепая SQL-injection (иногда называют полной слепой SQL-injection) – в параметр HTTP-запроса передаются специальные SQL-запросы, которые заставляют базу данных приостановиться на определенный период времени. Сравнивая время отклика между обычными запросами и с различными временными интервалами, атакующий может определить, было ли выполнение SQL-запроса успешным.
- SQL-injection error based — атака, нацеленная на получение ошибки от баз данных, чтобы атакующий смог сформировать представление об их архитектуре.
У разных систем управления базами данных есть свои тонкости в реализации каждого вида атак. Эксперты отмечают, что типовые атаки обычно срабатывают, если есть уязвимости в самих веб-приложениях. Для того чтобы защититься от атаки или снизить ее эффективность, прежде всего необходимо понять, что произошла атака именно на основе SQL-инъекции.
Внедрение SQLi в веб-приложение имеет следующие признаки:
- несоответствие входных и выходных данных. Случаи, когда результаты, возвращаемые веб-приложением, не соответствуют ожидаемым результатам или содержат неправильные данные, могут свидетельствовать о внедрения вредоносного SQL-кода;
- некорректные данные. Если данные, введенные пользователем, не проходят проверку на сервере, то они могут быть использованы для внедрения вредоносного кода в запросы к базе данных;
- неожиданные сообщения об ошибках. Если веб-приложение возвращает неожиданные сообщения об ошибках, такие, как «ошибка SQL», «недостаточно прав доступа» или «неверный пароль», это также может быть признаком SQL-инъекции;
- необычные запросы: Необычные запросы, обнаруженные в логах веб-сервера, такие, как запросы на изменение пароля или получение конфиденциальных данных, могут указывать на SQL-инъекцию.
Кроме того, определить, что на ваш ресурс совершена SQL-атака можно по увеличению нагрузки на него. Перегрузка сервера в результате аномально большого количества запросов к базе данных может тоже говорить о том, что была сделана SQL-инъекция.
Как защититься от SQL-инъекций
Независимо от вида и способа атаки защититься от SQL-инъекции полностью нельзя Всегда есть риск попасть под такую атаку. Но больше всего рискуют те, у кого в системе уже есть уязвимости. С хорошей системой безопасности и придерживаясь определенных правил SQL-атаку можно предотвратить или затруднить ее проведение.
Специалист по тестированию на проникновение Awillix
Атаку на основе SQL-инъекции не предотвратить на 100%, это как Чеховское ружье. Если кто-то написал include , значит оно рано или поздно выстрелит, как и любой third-party продукт. Поэтому надо ревьюить патчи, делать taint-анализ потоков данных, закупать WAF и локальные анализаторы поведения хостов. И всё равно вам это не поможет, но вы хотя бы отловите 1-day скрипты, которые используют тупые методы форкования типа runtime.exec(«cmd.exe -c ‘net user Vasyan 1q2w3e4f /add'»).
Чтобы предотвратить атаку на основе SQL-инъекции или затруднить ее проведение, необходимо:
- на постоянной основе обновлять ПО, чтобы исключить наличие уязвимостей;
- использовать WAF – межсетевые экраны для защиты веб-приложений, а также системы обнаружения вторжений IDS/IPS;
- ограничить доступ к базам данных, предоставив его только тем пользователям и сервисам, которым он нужен для работы;
- проводить проверку на корректность и безопасность вводимых пользователем данных. Например, проверять то, что введенное значение является числом или строкой, исключая специальные символы, которые могут быть использованы в SQL-инъекциях;
- использовать параметризованные запросы. Это означает, что в запросах используются параметры вместо конкретных значений. Такой подход позволяет избежать внедрения вредоносного кода, поскольку параметры в запросе интерпретируются как данные, а не как код.
Основная идея предотвращения атаки на основе SQL-инъекции заключается в том, чтобы фильтровать то, что веб-приложение пересылается в систему управления базами данных. Запросы должны быть строго формализированы и грамотно ограничены. Плюс очень полезным оказывается использование специализированный межсетевых экранов уровня приложений – Web application firewall. Они как раз предназначены для защиты от различных типов атак на веб-приложения, в том числе и инъекции. Как правило, они уже содержат сигнатуры для типовых способов эксплуатации этой уязвимости.
Примеры из жизни
Так как большинство веб-приложений использует SQL-язык, количество громких примеров кибератак на основе SQL-инъекций достаточно. Часто этот вид атак используют в комплексе с другими. Так, в 2013-2014 годах Yahoo столкнулся с серией атак, включая SQL-инъекции, в результате чего были украдены личные данные более чем 3 миллиардов пользователей.
Ведущий инженер информационной безопасности R-Vision
Один из ярких примеров применения SQL-инъекций – атаки, нацеленные на сайты под управлением WordPress. В прошлом году самым громким случаем стало наличие SQL-уязвимости у более чем 500 тысяч сайтов на базе этой платформы, в этом году уязвимыми оказались уже 70 тысяч сайтов. Еще один из громких примеров – в 2017 году компания Equifax, крупнейший кредитный бюро в США, была подвергнута масштабной кибератаке, в результате которой были скомпрометированы данные более чем 140 миллионов человек. Один из методов атаки была SQL-инъекция, которая позволила хакерам получить доступ к базе данных компании.
В 2015 году TalkTalk, британский провайдер интернет-услуг, был атакован злоумышленниками, которые использовали SQL-инъекцию. В результате были украдены личные данные более чем 150 000 клиентов, включая данные банковских карт.
Специалист по тестированию на проникновение Awillix
SQL-инъекция интересна сама по себе в силу обширности своего применения. Сложно представить себе условия, в которых не было бы использования баз данных. Традиционно принято начинать поиск SQL-инъекций в веб-приложениях – есть огромное количество историй с названием «SQL to RCE». Даже будучи одной из самых изученных проблем, SQL-Injection до сих пор часто встречается в Security Bullet-in’ах серьезных продуктов. Одна из самых больших проблем это возможность адаптации атаки под конкретный продукт с целью обхода WAF.
В 2007 году произошел The Little Bobby Tables incident. Была опубликована статья в блоге xkcd, в которой описывалась SQL-инъекция, которая могла бы произойти, если бы у пользователя было имя «Robert»); DROP TABLE Students;—«. Этот пример стал очень популярным и даже получил свою собственную страницу в документации SQLite.
Заключение
Как и от любой другой сетевой атаки, защититься от нападения на основе SQL-инъекций на 100% невозможно. Никто не может гарантировать полную безопасность, но можно проследить за наличием уязвимостей в своей системе.
С помощью набора инструментов кибербезопасности, соблюдения необходимых правил и постоянного тестирования своей системы, проведения проверок на SQLi, можно добиться определенных результатов и свести вероятность SQL-инъекции к минимуму.
Профилактика SQL-инъекций

SQL-инъекции (также известные как «Нарушение в целостности структуры SQL-запроса») являются одними из самых распространённых и наиболее опасных уязвимостей в вопросе безопасности. SQL-инъекции очень опасны, потому что они открывают двери хакерам в вашу систему через веб-интерфейс, и позволяют получить неограниченный доступ: например удалять таблицы, изменять базу данных, и даже получить доступ к внутренней корпоративной сети. SQL-инъекции это чисто программная ошибка, и не имеет ничего общего с хост-провайдером. Итак, вы занимались поисками безопасного JSP хостинга, PHP хостинга, или любого другого, вы должны знать, что за профилактику SQL-инъекций несут ответственность только разработчики, а не хост провайдер.
Почему же происходят SQL-инъекции
SQL-инъекции это очень распространённая проблема, но по иронии судьбы, их также легко предотвратить. SQL-инъекции так распространены, поскольку очень много мест, где может присутствовать уязвимость, и в случае успешной инъекции, хакер может получить хорошую награду (например полный доступ к данным в базе).
Риск SQL-инъекций возникает всякий раз, когда программист создает динамический запрос к базе, содержащий введённые пользователем данные. Это значит способов предотвращения SQL-инъекций два:
• Не использовать динамических запросов к базе.
• Не использовать пользовательских данных в запросах.
Все вроде бы просто, но это теории, на практике же отказаться от динамических запросов невозможно, как и исключить пользовательский ввод данных. Но это не значит, что избежать инъекций невозможно. Есть некоторые приемы и технические возможности языков программирования, которые помогут предотвратить SQL-инъекции.
Что можно сделать для предотвращения SQL-инъекций
Хотя решение во многом зависит от конкретного языка программирования, все же общие принципы предотвращения SQL-инъекций схожи. Вот несколько примеров как это можно сделать:
• Использовать динамические запросы только в случае крайней необходимости.
Динамический запрос почти всегда можно заменить подготовленными выражениями (prepared statements), параметризованными запросами, или хранимыми процедурами. Например, вместо динамического SQL, в Java вы можете использовать PreparedStatement() с привязанными параметрами, в .NET вы можете использовать параметризованные запросы, такие как SqlCommand() или OleDbCommand()с привязанными параметрами, а в PHP вы можете использовать PDO со строгой типизацией параметризованных запросов (используя bindParam()).
В дополнение подготовленным выражениям (prepared statements), вы можете использовать хранимые процедуры. В отличие от подготовленных выражений (prepared statements), хранимые процедуры хранятся в базе, но в обоих случаях вначале определяется SQL-запрос, и в него передаются параметры.
• Проверка введенных данных в запросах.
Проверка ввода данных менее эффективна, чем параметризованные запросы и хранимые процедуры, но если нет возможности использовать параметризованные запросы и хранимые процедуры, то уж лучше все же проверять введенные данные – это лучше, чем ничего. Точный синтаксис использования проверки введенных данных сильно зависит об базы данных, читайте доки на вашу конкретную базу данных.
• Не надеяться на волшебные Кавычки (Magic Quotes).
Включение параметра magic_quotes_gpc может предотвратить некоторые (но не все) SQL-инъекции. Magic quotes никак не последняя защита, и что еще хуже, иногда они выключены и вы не знаете об этом, или не имеете возможности его включить. Именно поэтому необходимо использовать код, который будет экранировать кавычки. Здесь кусок кода, предложенный Джоном Ли:
$username = $_POST[‘username’];
$password = $_POST[‘password’];
if (!get_magic_quotes_gpc()) $username = addslashes($username);
$password = addslashes($password);
>
• Регулярная и своевременная установка исправлений.
Даже когда ваш код не имеет уязвимостей, есть сервер баз данных, операционная система сервера, или утилиты разработчиков, которые могут иметь уязвимости. Именно поэтому всегда устанавливайте исправления сразу после их появления, особенно если это исправление SQL-инъекций.
• Удаляйте весь функционал, который вы не используете.
Сервер баз данных это сложное создание и имеет намного больше функционала, чем вам требуется. А то, что касается безопасности, тут принцип «чем больше – тем лучше» не работает. Например, расширенная системная процедура xp_cmdshell в MS SQL дает доступ к операционной системе, а это просто мечта для хакера. Именно поэтому эту функцию нужно отключать, как и любые другие, позволяющие легко злоупотреблять функционалом.
• Использование автоматизированных средств нахождения SQL-инъекций.
Даже если разработчики следовали всем вышеописанным правилам, чтобы избежать динамических запросов с подстановкой непроверенных пользовательских данных, вы все равно должны подтвердить это тестами и проверками. Существуют автоматизированные средства тестирования, для выявления SQL-инъекций, и нет оправдания тем, кто не пользуется этими средствами для проверки процедур и запросов.
Один из простых инструментов (и один из более-менее надежных) для выявления SQL-инъекций это расширение для Firefox`а именуемое SQL Inject ME. После установки этого расширения, инструмент доступен по правому клику в контекстном меню, или из меню Tools → Options. Рабочая область SQL Inject ME показана на следующем скриншоте, и вы можете видеть как много видов тестов вы можете провести:


Вы можете выбирать, какой тест запускать, и с какими параметрами. По окончанию проверки вы увидите отчет о результатах тестирования.
Множество параметров которые вы можете устанавливать для расширения SQL Inject ME, которые показаны на следующих двух скриншотах:


Как вы видите, есть много решений (и прежде всего все простые) которые вы можете предпринять для очистки кода от потенциальных уязвимостей SQL-инъекциям. Не пренебрегайте этими простыми вещам, т.к. вы ставите под угрозу не только свою безопасность, но и все сайты, которые размещаются на вашем хост-провайдере.
- информационная безопасность
- хакеры
- уязвимости
- инъекции
Защита от SQL-инъекций
Внедрение SQL-кода (SQL инъекция) — один из распространённых способов взлома сайтов, работающих с базами данных. Способ основан на внедрении в запрос произвольного SQL-кода. Внедрение SQL позволяет хакеру выполнить произвольный запрос к базе данных (прочитать содержимое любых таблиц, удалить, изменить или добавить данные).
Атака этого типа возможна, когда недостаточно фильтруются входные данные при использовании в SQL-запросах.
Принцип атаки внедрения SQL
Допустим, на нашем сайте есть страница показа истории погодных наблюдений для одного города. Идентификатор этого города передаётся в ссылке в параметре запроса: /weather.php?city_id= , где ID — это первичный ключ города. В PHP-сценарии используем этот параметр для подстановки в SQL запрос:
$city_id = $_GET['city_id']; $res = mysqli_query($link, "SELECT * FROM weather_log WHERE city_id text language-text">SELECT * FROM weather_log WHERE city_id = 10
Но если злоумышленник передаст в качестве параметра id строку -1 OR 1=1 , то выполнится запрос:
SELECT * FROM weather_log WHERE city_id = -1 OR 1=1
Добавление во входные параметры конструкций языка SQL (вместо простых значений) изменяет логику выполнения всего SQL запроса. В этом примере вместо показа данных по одному городу, будут получены данные по всем городам, потому что выражение 1 = 1 всегда истинно. Вместо выражения SELECT . могло быть выражение на обновление данных, и тогда последствия были бы ещё серьезнее.
Отсутствие должной обработки параметров SQL-запроса — это одна из самых серьёзных уязвимостей. Никогда не вставляйте данные от пользователя в SQL запросы «как есть»!
Приведение к целочисленному типу
В SQL-запросы часто подставляются целочисленные значения, полученные от пользователя. В примерах выше использовался идентификатор города, полученный из параметров запроса. Этот идентификатор можно принудительно привести к числу. Так мы исключим появление в нём опасных выражений. Если хакер передаст в этом параметре вместо числа SQL код, то результатом приведения будет ноль, и логика всего SQL-запроса не изменится.
PHP умеет присваивать переменной новый тип. Этот код принудительно назначит переменной целочисленный тип:
$city_id = $_GET['city_id']; settype($city_id, 'integer');
После преобразования переменную $city_id можно без опаски использовать в SQL-запросах.
Экранирование значений
Что делать, если в SQL запрос требуется подставить строковое значение? Например, на сайте есть возможность поиска города по его названию. Форма поиска передаст поисковый запрос в GET-параметр, а мы используем его в SQL-запросе:
$city_name = $_GET['search']; $sql = "SELECT * FROM cities WHERE name LIKE('%$city_name%')";
Но если в параметре city_name будет символ кавычки, то смысл запроса можно кардинально изменить. Передав в search_text значение ')+and+(id<>'0 , мы выполним запрос, что выведет список всех городов:
SELECT * FROM cities WHERE name LIKE('%') AND (id<>'0%'))
Смысл запроса поменялся, потому что кавычка из параметра запроса считается управляющим символом: MySQL определяет окончание значение по символу кавычки после него, поэтому сами значения кавычки содержать не должны. Очевидно, приведение к числовому типу не подходит для строковых значений. Поэтому, чтобы обезопасить строковое значение, используют операцию экранирования.
Экранирование добавляет в строке перед кавычками (и другими спецсимволами) знак обратного слэша \ . Такая обработка лишает кавычки их статуса — они больше не определяют конец значения и не могут повлиять на логику SQL-выражения.
За экранирование значений отвечает функция mysqli_real_escape_string() . Этот код обработает значение из параметра, сделав его безопасным для использования в запросе:
$city_name = mysqli_real_escape_string($link, $_GET['search']); $sql = "SELECT * FROM cities WHERE name LIKE('%$city_name%')";
Подготовленные выражения
Вид атак типа «SQL-инъекция» возможен, потому что значения (данные) для SQL-запроса передаются вместе с самим запросом. Так как данные не отделены от SQL-кода, они могут влиять на логику всего выражения. К счастью, MySQL предлагает способ передачи данных отдельно от кода. Такой способ называется подготовленными запросами.
Выполнение подготовленных запросов состоит из двух этапов: вначале формируется шаблон запроса — обычное SQL-выражение, но без действительных значений, а затем, отдельно, в MySQL передаются значения для этого шаблона.
Первый этап называется подготовкой, а второй — выражением. Подготовленный запрос можно выполнять несколько раз, передавая туда разные значения.
Этап подготовки. На этапе подготовки формируется SQL-запрос, где на месте значений будут находиться знаки вопроса — плейсхолдеры. Эти плейсхолдеры в дальнейшем будут заменены на реальные значения. Шаблон запроса отправляется на сервер MySQL для анализа и синтаксической проверки. Пример:
$sql = "SELECT * FROM cities WHERE name = ?"; $stmt = mysqli_prepare($link, $sql);
Этот код сформирует подготовленное выражение для выполнения вашего запроса.
За подготовкой идёт выполнение. Во время запуска запроса PHP привязывает к плейсхолдерам реальные значения и посылает их на сервер. За передачу значений в подготовленный запрос отвечает функция mysqli_stmt_bind_param() . Она принимает тип и сами переменные:
mysqli_stmt_bind_param($stmt, 's', $_GET['search']);
После выполнения запроса получить его результат в формате mysqli_result можно функцией mysqli_stmt_get_result() :
$res = mysqli_stmt_get_result($stmt); // чтение данных while ($row = mysqli_fetch_assoc($res)) < // ассоциативный массив с очередной записью из результата var_dump($row); >
Значения привязанных к запросу переменных сервер экранирует автоматически. Привязанные переменные отправляются на сервер отдельно от запроса и не могут влиять на него. Сервер использует эти значения непосредственно в момент выполнения, уже после того, как был обработан шаблон выражения. Привязанные параметры не нуждаются в экранировании, так как они никогда не подставляются непосредственно в строку запроса.
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.
Защита от SQL-инъекций
Быстрые рекомендации.
Для предотвращения SQL инъекций следует соблюдать два простых правила:
1. Не помещать в БД данные без обработки.
Это можно сделать либо с помощью подготовленных выражений, либо обрабатывая параметры вручную.
Если запрос оставляется вручную, то
- все числовые параметры должны быть приведены к нужному типу
- все остальные параметры должны быть обработаны функцией mysql_real_escape_string() и заключены в кавычки.
2. Не помещать в запрос управляющие структуры и идентификаторы, введенные пользователем.
А заранее прописывать в скрипте список возможных вариантов, и выбирать только из них.
Два важных дополнения:
1. Используя изложенную в этой статье информацию, я написал Класс для безопасной и удобной работы с MySQL, который делает запросы безопасными и сокращает код в несколько раз.
2. Если вы не любите сторонние библиотеки, то пользуйтесь хотя бы PDO. Как работать с PDO? Полное руководство.
Правила составления запросов MySQL
Для начала - немного о том, почему вообще нужны эти слеши.
Если мы подставляем в запрос какие-либо данные, то, чтобы отличить эти данные от команд SQL, их надо брать в кавычки.
К примеру, если написать
SELECT * FROM table WHERE name = Bill
то база решит, что Bill - это имя другого поля, не найдёт его, и выдаст ошибку. Поэтому подставляемые данные (в данном случае имя Bill) надо заключать в кавычки - тогда база сочтет его строкой, значение которой надо присвоить полю name:
SELECT * FROM table WHERE name = 'Bill'
Однако, и в самих данных могут встречаться кавычки тоже. К примеру,
SELECT * FROM table WHERE name = 'Д'Артаньян'
Здесь база данных решит, что 'Д' - это данные, а Артаньян - команда, которую она не знает, и тоже выдаст ошибку. Поэтому и надо прослешивать все данные, чтобы объяснить базе, что встречающиеся в них кавычки (и некоторые другие спецсимволы) относятся к данным.
В результате мы получим правильный запрос, который ошибок не вызовет:
SELECT * FROM table WHERE name = 'Д\'Артаньян'
Таким образом, мы выяснили, что при подстановке строковых данных в запрос, следует придерживаться двух правил:
- все вставляемые строковые данные должны быть заключены в кавычки (одинарные или двойные, но удобнее и чаще используются одинарные).
- в них должны быть экранированы слешами спецсимволы.
Следует специально отметить: добавленные слеши НЕ идут в базу. Они нужны только в запросе. При попадании в базу слеши отбрасываются. Соответственно, распространенной ошибкой является применение stripslashes при получении данных из базы.
Всё вышесказанное относится к данным строкового типа и датам. Числа можно вставлять не прослешивая и не окружaя кавычками. Если вы так делаете, то ОБЯЗАТЕЛЬНО! насильно приводите данные к нужному типу перед вставкой в запрос, например:
$id=intval($id);
Однако для простоты (и надёжности) можно и с числами работать, как со строками (проскольку mysql всё равно преобразует их к нужному типу). Соответственно, мы будем любые данные, вставляемые в запрос, прослешивать и заключать в кавычки.
Так же, есть ещё одно правило - необязательное, но его следует придерживаться во избежание появления ошибок:
Имена полей и таблиц следует заключать в обратные одинарные кавычки - "`" (клавиша с этим символом находится на стандартной клавиатуре слева от клавиши "1") Ведь имя поля может совпадать с ключевыми словами mysql, но если мы используем обратную кавычку, то MySQL поймёт всё правильно:
SELECT * FROM `table` WHERE `date` = '2006-04-04'
Следует различать эти кавычки и не путать одни с другими. Следует также помнить, что обратные кавычки слешами не экранируются.
Динамическое составление запросов
Если SQL запрос в скрипте написан целиком, и никак не меняется, например
SELECT * FROM `table`
то никаких проблем с ним и не будет.
Но вся сила наших скриптов именно в динамическом составлении запросов!
Вместо того, чтобы писать готовые запросы на все случаи жизни, мы составляем их на основании поступающих в скрипт данных.
И вот здесь нас подстерегает опасность.
Допустим, мы составляем запрос с использованием переменной:
SELECT * FROM table WHERE name = '$name'
Вроде бы - всё нормально?
А если $name у нас будет Д'Артаньян? Запрос выдаст ошибку!
То есть, переменную перед подстановкой в запрос надо прослешить.
Это можно сделать несколькими путями.
Самый простой (и неправильный) - положиться на волшебные кавычки. Как вы уже догадались, именно для этого случая они и были придуманы. Ради того, чтобы уберечь SQL запросы забывчивых программистов от ошибок, ВСЕ поступающие в скрипт данные прослешиваются без разбору.
Если вы используете чужой код, то лучше воспользоваться волшебными кавычками. Это может создавать некоторые неудобства и не гарантирует вас от ошибок или взлома (поскольку прослешиванием правила составления запросов не исчерпываются) но хотя бы снижает риск. Поэтому, при использовании чужого кода, обязательно убедитесь, что волшебные кавычки включены.
Если же вы пишете весь код самостоятельно, то следует научиться правильному составлению запросов.
Правильная работа со спецсимволами при составлении запросов
Итак. Как мы уже узнали выше, чтобы правильно составить запрос, надо заключать данные в кавычки и прослешивать их.
С первым всё понятно. При составлении динамических запросов мы никогда не забываем все данные заключить в кавычки:
$query="INSERT INTO `table` VALUES(NULL,'$name','$date','$price')";
Если переменная $price должна быть типа int и мы приведём её к этому типу, то можно её не заключать в кавычки. Однако, если заключим, то беды особой не будет, но зато можно будет сделать работу с данными единообразной.
Второе же - прослешивание - и является тем, ради чего, собственно, по большей части, и написан весь этот текст. Поскольку вызывает больше всего вопросов и затруднений.
Сначала отключим волшебные кавычки. Так, как это описано в самом начале.
Почему это следует сделать?
По многим причинам. Самая очевидная - логическая. "Волшебные кавычки" добавляют слеши не там, где они нужны - при составлении запроса, а еще до попадания в скрипт! Но ведь данные совсем не обязательно после этого будут вставляться в запрос. Может быть, их придётся выводить пользователю, и слеши будут только мешать. Плюс к тому, добавленные слеши помешают, к примеру, правильно проверить длину введённой строки. К тому же, прослешивать нам надо не только пришедшие от пользователя данные, а вообще любые, вставляемые в запрос - многим этот очевидный факт даже не приходил в голову! Список можно продолжать, но вывод один: добавлять слеши надо не автоматом, без разбору, до начала выполнения скрипта, а только там, где действительно надо – при составлении запроса.
Есть и ещё одна причина: при использовании кодировки Unicode, которая приобретает всё большую популярность, а со временем займёт доминирующее положение в веб, волшебные кавычки могут попросту испортить текст, приняв часть мультибайтной строки за спецсимвол.
Теперь займёмся добавлением слешей самостоятельно.
Во-первых, для прослешивания мы воспользуемся функцией mysql_real_escape_string()
Следует помнить, что применять её можно только после установления соединения с базой.
Эта функция делает гораздо больше, чем устаревшие addslashes и mysql_escape_string. Во-первых, она облегчает ведение и чтение логов mysql, заменяя, например, символ перевода строки на "\n" и некоторые другие символы на escape-последовательности. Во-вторых, и самое главное - она корректно работает с многобайтными кодировками, принимая во внимание текущую кодировку MySQL и не портит, таким образом, тексты в кодировке Unicode.
Во-вторых, не забываем, что прослешить надо все те данные, которые мы заключили в запросе в кавычки:
$name=mysql_real_escape_string($name);
$age=mysql_real_escape_string($age);
$query="INSERT INTO table (name,age,class) VALUES ('$name','$age',11)";
или:
$query="SELECT * FROM table WHERE name LIKE '".mysql_real_escape_string($_GET['name'])."%'";
Видно, что код получается довольно громоздким. Для облегчения составления запросов можно пуститься на разные ухищнения - сделать функцию для составления запросов из массива (очень удобно для запросов типа INSERT и UPDATE), прослешивать массив данных в цикле, и так далее.
Вы можете написать и свою библиотеку или функцию для составления запросов.
Главное - помнить, что только неукоснительное соблюдение правил составления запросов гарантирует вас от взлома БД, а так же сознавать, что применение "волшебных кавычек", при видимой лёгкости составления запросов, не даёт такой гарантии, а только мешает нормальной работе приложения.
Подготовленные выражения
Есть еще один способ отправлять запросы в БД, называемый "подготовленными выражениями" (prepared statements).
Суть его заключается в том, что подготавливается шаблон запроса, со специальными маркерами, на место которых будут подставлены динамические компоненты. Пример такого шаблона:
SELECT * FROM table WHERE name=?
Знак вопроса здесь - это тот самый маркер. По-другому он называетсй плейсхолдером (placeholder). Весь секрет в том, что данные на его место подставляет специальная функция, которая "привязывает" переменную к запросу.
Вот как выглядит код в таком случае:
$stmt = $mysqli->prepare("SELECT District FROM City WHERE Name=?");
$stmt->bind_param("s", $city);
$stmt->execute();
В первой строчке мы подготавливаем шаблон запроса.
Во второй - привязываем к маркеру значение переменной $city.
В третьей строчке выполняем подготовленный таким образом запрос.
При этом запрос и данные идут в базу не вместе, а по отдельности, исключая возможность какой-либо ошибки или злонамеренной манипуляции.
Понятно, что возникает много вопросов. Но ни объём, ни тематика данной статьи не позволяют остановиться на них более подробно. Рекомендую обратиться к документации по библиотекам mysqli и PDO , реализующим данный принцип.
Так же, можно использовать библиотеку DbSimple Дмитрия Котерова или PEAR::DB. Основное отличие этих двух состоит в том, что они реализуют механизм подготовленных выражений только внешне. А внутри работают по-старинке - составляя запрос и отправляя его в базу, беря на себя работу по обработке переменных. А PDO и mysqli работают, как было описано выше - то есть, шаблон запроса и данные уходят в базу по отдельности.
Итак, мы научились правильно подставлять в запрос данные.
НО! Динамическое составление запросов не исчерпывается подстановкой данных. Часто нам приходится подставлять в запрос команды SQL и имена полей. И здесь мы уже переходим к теме безопасности:
SQL Injection - это способ хакерской атаки, когда передаваемые скрипту данные модифицируются таким образом, что запрос, формируемый в этом скрипте, начинает выполнять совсем не то, для чего он предназначался.
Правила защиты от таких атак можно разделить на два пункта:
1. Работа с данными.
2. Работа с управляющими элементами запроса.
Первый пункт мы подробно рассматривали выше. Он, можно сказать, и не является, собственно, защитой. Соблюдение правил добавления занных в запрос продиктовано, в первую очередь, требованиями СИНТАКСИСА SQL. А как побочный эффект мы имеем и защиту от взлома.
Второй пункт гораздо сложнее, поскольку не существует такого же единого универсального правила, как для данных - обратная кавычка никак не защитит имя поля от модификации хакером. Невозможно кавычками защитить имя таблицы, операторы SQL, параметры команды LIMIT, и другие операторы.
Поэтому основное правило при подстановке управляющих элементов в запрос такое:
Если требуется динамически подставлять в запрос операторы SQL или имена полей, баз данных, таблиц, то ни под каким видом не вставлять их в запрос напрямую.
Все варианты таких добавлений должны быть ЗАРАНЕЕ прописаны в вашем скрипте и выбираться на основании того, что ввёл пользователь.
К примеру, если надо передать имя поля в оператор order by, то ни в коем случае нельзя подставлять его напрямую. Надо сначала проверить его. К примеру, сделать массив допустимых значений, и подставлять в запрос только если переданный параметр в этом массиве присутствует:
$orders=array("name","price","qty");
$key=array_search($_GET['sort'],$orders));
$orderby=$orders[$key];
$query="SELECT * FROM `table` ORDER BY $orderby";
Мы ищем в массиве заранее описанных вариантов введённое пользователем слово, и, если находим, то выбираем соответствующий элемент массива. Если совпадения не будет найдено, то будет выбран первый элемент массива.
Таким образом, в запрос подставляется не то, что ввёл пользователь, а то, что было прописано у нас в скрипте.
Точно так же надо поступать и во всех остальных случаях
К примеру, если динамически формируется оператор WHERE:
if (!empty($_GET['price'])) $where.="price='".mysql_real_escape_string($_GET['price'])."'";
$query="SELECT * FROM `table` WHERE $where";
Мне сложно представить себе случай, когда имя таблицы может подставляться в запрос динамически, но если такое случится, то имя тоже надо вставлять только из заранее прописанного в скрипте набора.
Параметры оператора LIMIT следует принудительно приводить к целочисленному типу с помощью арифметических операций или функции intval ().
Не следует думать, что перечисленными здесь примерами исчерпываются все варианты динамического составления запросов. Нужно просто понять принцип, и применять его во всех подобных случаях.
Особенности работы с оператором LIKE
При работе с оператором LIKE существуют некоторые нюансы.
Во-первых, следует обратить внимание на то, что у этого оператора есть два своих спецсимвола - _ и %. Если вы не хотите, чтобы они использовались, как маски, а хотите искать буквальное совпадение с символами % и _, то их надо прослешить. Это можно сделать командой
$data = addCslashes($data, '%_');
Внимание - это не addslashes! В имени этой функции есть дополнительная буква "c".
Во-вторых, в силу некоторых причин в подставляемых в LIKE (и REGEXP) даных, надо удваивать слеши.
Следовательно, перед тем, как подставлять некую переменную в like, её надо отдельно обработать:
либо прослешить только бэкслеш, если у нас уже стоят символы поиска по маске и мы хотим их использовать по назначению,
$data = addCslashes($data, '\\');
либо прослешить и символы поиска тоже, если мы хотим их добавить вручную,
$data = addCslashes($data, '\%_');
В итоге, код подготовки переменной для подстановки в LIKE может выглядеть так:
$data = '%'.addCslashes($data, '\%_').'%';
и полученное таким образом значение мы уже дальше можем подставлять в запрос, используя либо прослешивание, либо подстановку.
О слешах. Как от них избавиться
Слеш, или бэкслеш, от английского back slash - обратная косая черта ("\"), которая непонятным образом вдруг сама собой появляется в ваших переменных. Добавляется он к некоторым спецсимволам, но в основном его замечают из-за кавычек.
Происходит это из-за специальных настроек PHP, обычно включённых на хостинге по умолчанию. Теоретически, эти настройки могут повысить безопасность скриптов, работающих с БД. Практически же, от автоматического добавления слешей часто получается путаница и неудобство, как при работе с БД, так и при её отсутствии.
Ниже мы подробно разберём оба этих случая.
За автоматическое добавление слешей отвечают директивы php.ini, которые носят общее название "волшебные кавычки":
magic_quotes_gpc и magic_quotes_runtime
Если включена первая, то PHP автоматически добавляет слеши к данным, пришедшим от пользователя - из POST, GET запросов и кук (а так же - к логину и паролю, полученным через HTTP Authorisation).
Если вторая, то слеши добавляются к данным, полученым во время исполнения скрипта - например, из файла или базы данных.
Если вы работаете без базы данных, или же работаете с БД правильно (о чём будет написано ниже), лишние слеши вам только мешают, и от них надо избавляться. Проще и правильнее всего отключить автоматическое добавление, в настройках PHP.
Это можно сделать либо поправив соответствующие директивы в php.ini, если у вас есть к нему доступ, либо создав в коневом каталоге сайта файл .htaccess , и добавив в него строчки
php_flag magic_quotes_gpc 0
php_flag magic_quotes_runtime 0
Если отключить таким образом не получается, то придётся писать код разной степени сложности, чтобы очистить от слешей входящие данные. (Впрочем, если вы хотите написать переносимое приложение, не зависящее от настроек PHP, то написать его всё равно придётся. И включать отдельным блоком в начале ваших скриптов).
- Среди причин, по которым не стоит полагаться на "волшебные кавычки", есть ещё одна. Весьма маловероятная, но всё же. К "волшебным кавычкам" относится на самом деле не две директивы, а три. Третья - magic_quotes_sybase . Мало того, что она вместо слеша добавляет кавычку - так она ещё и отменяет действие magic_quotes_gpc. Если каким-то чудом обе эти директивы имеют статус 'on', то последняя не сработает! То есть, полагаясь на "волшебные кавычки", мы в этом случае получим все прелести неправильно составленных запросов. Вообще, чисто теоретически, надо учитывать наличие этой директивы, поскольку она преподносит ещё и такой сюрприз, как. изменение поведения функций addslashes и stripslashes! Если magic_quotes_sybase = on , то эти функции начинают вместо слеша добавлять и удалять одинарную кавычку соответственно.
- Все приведенные примеры касаются только БД Mysql. Конкретные правила составления запросов могут отличаться для других СУБД, но общий принцип остается прежним:
- если API для работы с БД или сторонняя библиотека предоставляет специальные функции для составления запросов, и есть возможность их использования, то пользоваться в первую очередь надо ими.
- если таких функций нет, то следует искать в документации функции экранирования спецсимволов для этой СУБД.
Примечание: формы
При выводе value в тегах input форм, слеши не помогают.
Чтобы текст в таком поле выводился целиком, value надо заключать в кавычки, а к выводимым данным применять функцию htmlspecialchars()
Пример:
">Необходимо так же отметить (хоть это уже совсем не имеет отношения к кавычкам и слешам), что функцию htmlspecialchars следует применять при выводе в браузер вообще ко всем данным, которые получены от непроверенного пользователя. Почему это следует делать, можно почитать в гугле по запросу что такое XSS уязвимость