MySQL / 18. Обработка ввода через Web с помощью MySQL

Использование ввода через Web для формирования запросов

Задача
Вводу, полученному через Web, нельзя доверять и использовать его в запросе без принятия соответствующих мер.

Решение
Проведите «санитарную обработку» значений данных, используя заполнители или функцию заключения в кавычки.

Обсуждение
После того как значения входных параметров извлечены и проверены на корректность, их можно использовать для построения запросов. Это самаяпростая часть процедуры, хотя и требуется принять некоторые меры предосторожности, чтобы не сделать ошибку, о которой придется сожалеть. Сначала давайте подумаем о том, что плохого может случиться, а затем посмотрим, как предотвратить такие проблемы.

Предположим, что у вас есть форма поиска, содержащая поле для ключевого слова, которое действует как внешний интерфейс для простой поисковой машины. Когда пользователь вводит ключевое слово, вы планируете использовать его для поиска соответствующих записей в таблице, формируя запрос так:

SELECT * FROM mytbl WHERE keyword = 'ключевое_слово'

Здесь ключевое_слово представляет значение, введенное пользователем. Если введено, например, значение eggplant, то результирующий запрос будет таким:

SELECT * FROM mytbl WHERE keyword = 'eggplant'

Запрос возвращает все соответствующие записи, вероятно, формируя небольшое результирующее множество. Но предположим, что мы имеем дело с коварным пользователем, который пытается испортить ваш сценарий, введя такое значение:

eggplant' OR 'x'='x

В данном случае запрос выглядит так:

SELECT * FROM mytbl WHERE keyword = 'eggplant' OR 'x'='x'

Этому запросу соответствуют все записи таблицы! Если таблица достаточно большая, то ввод можно рассматривать как атаку типа «отказ в обслуживании» на сценарий, так как он может привести к тому, что система будет тратить все ресурсы, предназначенные для обработки корректных запросов, на бесполезную работу. Возможны такие результаты:

• Чрезмерная нагрузка на сервер MySQL.

• Проблемы нехватки памяти сценария при попытке обработать результирующее множество, полученное от MySQL.

• Чрезмерное потребление пропускной способности сети при отправке результатов клиенту.

Если сценарий формирует предложение DELETE, то последствия такой подрывной деятельности могут быть еще печальнее – сценарий может выдать запрос, полностью опустошающий таблицу, при том, что вы планировали удалить всего одну запись.

Мораль в том, что предоставление веб-интерфеса к базе данных делает вас открытым для атак определенного рода. Однако проблемы можно предотвратить, всегда следуя одному простому правилу: «Никогда не помещать значения данных в строки запроса буквально». Используйте заполнители или функцию кодирования. Например, в Perl можно обработать входной параметр при помощи заполнителей так:

$keyword = param ("keyword");
$sth = $dbh->prepare ("SELECT * FROM mytbl WHERE keyword = ?");
$sth->execute ($keyword);
# ... извлечение результирующего множества ...

Или при помощи quote():
$keyword = param ("keyword");
$keyword = $dbh->quote ($keyword);
$sth = $dbh->prepare ("SELECT * FROM mytbl WHERE keyword = $keyword");
$sth->execute ();
# ... извлечение результирующего множества ...

В любом случае, если пользователь вводит некорректное значение, запрос будет таким:

SELECT * FROM mytbl WHERE keyword = 'eggplant\' OR \'x\'=\'x'

Ввод становится безобидным, запрос не находит ни одной соответствующей условиям записи (вместо всех записей) – это определенно более удачный ответ тому, кто пытается вам навредить.

Применение заполнителей и способы кодирования для PHP, Python и Java аналогичны, они обсуждались в рецептах 2.6 и 2.7. Что касается страниц JSP, созданных при помощи библиотеки тегов JSTL, вы можете заключать значения входных параметров в кавычки, используя заполнители и тег <sql:param> (рецепт 16.3). Например, чтобы использовать значение параметра keyword формы в предложении SELECT, выполните:

<sql:query var="rs" dataSource="${conn}">
SELECT * FROM mytbl WHERE keyword = ?
<sql:param value="${param['keyword']}" />
</sql:query>

Заполнители и функции кодирования применяются только к значениям данных SQL. Они не занимаются обработкой Web-ввода, используемого для других типов элементов запроса, таких как имена баз данных, таблиц и столбцов. Если вы планируете вставлять такие значения в запрос, необходимо делать это буквально, поэтому предварительно следует проверить их. Например, если вы создаете такой запрос, необходимо проверить, разумное ли значение содержит $tbl_name:

SELECT * FROM $tbl_name;

Но что значит «разумное»? Если у вас нет таблиц, имена которых содержат странные символы, достаточно просто убедиться в том, что $tbl_name включает только буквенно-цифровые символы и символы подчеркивания. Или можно выполнить запрос SHOW TABLES, чтобы проверить, есть ли таблица с таким именем в базе данных. Это более надежно, но требуется дополнительный запрос.

Кроме того, методы использования заполнителей не ставят перед собой вопрос об интерпретации: если поле формы является необязательным, что следует сохранить в базе данных, если пользователь оставит его пустым? Вероятно, значение представляет пустую строку или должно восприниматьсякак NULL. Для решения вопроса можно обратиться к метаданным столбца.

Если столбец допускает использование NULL, то пустые значения интерпретируются как NULL. В противном случае пустое поле означает пустую строку.

Статьи по MySQL на эту тему:

Ведение журнала Apache с помощью MySQL
Выполнение поиска и получение результатов
Журнал доступа к веб-странице
Обработка загружаемых файлов
Сортировка результатов запроса по произвольному столбцу