Безопасный код в Друпале: Работа с базой данных

Безопасный код в Друпале: Работа с базой данных

Друпал предоставляет свои собственные средства для доступа к базе данных.

Во-первых, это позволяет не зависеть от используемого типа СУБД. К слову, на сегодняшний момент, полностью функционирует прослойка для MySQL и PostgreeSQL. В седьмом Друпале этот список будет расширен Ораклом и SQLite.

Во-вторых же, прослойка БД позволяет защититься от SQL инъекций. Самая первая функция, о которой следует узнать при работе с базой — db_query().

Начну, пожалуй, с примера, в стиле которого пишут почти все начинающие друпаллеры:

/** * Пример 1 - небезопасный * Пример должен отобразить список заголовков нод типа $type (например, поступающего из поля формы) */ $result = db_query( "SELECT nid, title FROM node WHERE type = '$type'" );

$items = array(); while ($row = db_fetch_object($result)) title, "node/nid>" ); > return theme( 'item_list' , $items);

В этом примере сразу несколько вещей в корне неправильны.

Псевдонимы названий таблиц

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

Что нам это даст? Это обеспечит простоту обработки таблиц с префиксами. То есть, если у вас все таблицы в базе называются «pr_node», «pr_users» и т.д., Друпал автоматически будет подставлять корректные префиксы к таблицам, заключенным в скобки. Указание псевдонимов при этом избавит от надобности использовать фигурные скобки больше одного раза.

Фильтрация аргументов

Отсутствует фильтрация аргументов запроса. Это прямой путь к SQL инъекции. Если в $type окажется значение story' UNION SELECT s.sid, s.sid FROM s WHERE s.uid = 1/* , то весь запрос будет уже таким:

что позволит мошеннику завладеть айдишниками сессий, и в свою очередь, при создании корректной куки сессии, получить прямой админский доступ к сайту.

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

  • %d — для целых чисел (integers)
  • %f — для чисел с плавающей запятой, т.е. дробных (floats)
  • %s — для строк (однако, обратите внимание, что в запросе, вокруг строки выставляются кавычки)
  • %b — двоичные данные (не нужно оборачивать в кавычки)
  • %% — заменяется на % (например, для LIKE %monkey% )

Для конструкций IN (. , . , . ) , используйте функцию db_placeholders(), которая создаст нужную последовательность заменителей, по заданному массиву параметров, например:

Теперь, наш запрос будет выглядеть так:

Ранжирование результатов запроса

Наш пример на большом сайте выведет большущий список нодов. Что если нам можно ограничиться всего первым десятком? Первым позывом будет использовать SQL конструкцию LIMIT, например

и вроде бы все хорошо, но на Postgree SQL этот код приведет к ошибке, так как с этим сервером управления, вам нужно использовать конструкцию OFFSET 0 LIMIT 10 . А еще на каком-нибудь Оракле, синтаксис опять другой. Что же делать?

Ответ — использовать db_query_range() для лимитирования количества результатов запроса. Его использование аналогично db_query, за исключением того, что в после всех аргументов, вам нужно указать два параметра — номер первой строки, и количество результатов. Наш запрос преобразится в следующее:

И на последок, если вам ко всему еще нужен постраничный вывод, используйте функцию pager_query(). Она отличается от db_query_range() наличием всего одного необязательного параметра, о котором вы можете почитать на странице документации. С этой функцией вывод листалки страниц прост как дважды два:

/** * Пример 2 - безопасный, с листалкой */ // изменяем сам запрос $result = pager_query( "SELECT n.nid, n.title FROM n WHERE n.type = '%s'" , $type, 0, 10);

$items = array(); while ($row = db_fetch_object($result)) title, "node/nid>" ); >

$output = theme( 'item_list' , $items);

// добавляем листалку $output .= theme( 'pager' );

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

Возможность изменения запроса модулями

Довольно часто имеет смысл предоставить другим модулям возможность повлиять на ваш запрос. В Друпале это реализуется связкой функции db_rewrite_sql(), и реализациями хука hook_db_rewrite_sql() в модулях. Наш запрос будет выглядеть так:

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

Возвращенные из хука 'join' элементы, будут прикреплены к нашему запросу, 'where' — добавлены к списку условий, и наш запрос после обработки будет таким:

После этого, он, собственно, поступит в pager_query() и будет обработан как обычно.

Финальный код примера

/** * Пример 3 - безопасный, с листалкой и возможностью перезаписи запроса */ // добавляем db_rewrite_sql $result = pager_query(db_rewrite_sql( "SELECT n.nid, n.title FROM n WHERE n.type = '%s'" , 'n' , 'nid' ), $type, 0, 10);

$items = array(); while ($row = db_fetch_object($result)) title, "node/nid>" ); >

$output = theme( 'item_list' , $items);

$output .= theme( 'pager' );

* This source code was highlighted with Source Code Highlighter .

📎📎📎📎📎📎📎📎📎📎