Статьи Борьба с Sql-инъекциями

Discussion in 'Статьи' started by MINDFLY, 7 Sep 2006.

  1. MINDFLY

    MINDFLY Banned

    Joined:
    18 Jun 2006
    Messages:
    19
    Likes Received:
    3
    Reputations:
    -15
    Борьба с SQL-инъекциями

    Каждый день на сайте xakep.ru появляются откровения людей, взломавших тот или иной сайт. Львиная доля этих взломов основывается на SQL-инъекциях. Неужели SQL-инъекции - это вечная проблема, и нет универсального средства, которое бы гарантированно раз и навсегда избавило бы web-разработчиков от этого зла?

    Как бы нескромным ни показалось вам мое заявление, но я в этой статье о таком средстве расскажу.

    Источник описываемой проблемы в том, что при использовании в web-приложениях баз данных MySql приходится формировать запросы, в текстах которых присутствуют данные, вставляемые в таблицу, или критерии выборки. Болшинство тех, кто это делает, формируют запросы "вручную", т. е., например, так (PHP):

    $sql = "SELECT name, val FROM tbl WHERE id = $id;";

    Далее этот запрос передается в функцию mysql_query. При этом переменная $id берется из параметра, передаваемого скрипту через урл.

    Те, кто знает, что такое Sql-инъекции, фильтруют данные, используя громоздкие регулярные выражения, каждый раз думая, какие символы разрешать использовать в том или ином параметре, а какие нет. При этом нетрудно ошибиться или, добавив новый параметр, вовсе забыть про фильтрацию.

    Альтернатива такой фильтрации - параметризованные запросы. Они есть в стандарте SQL и поддерживаются уже очень многими СУБД, ондако среди этих многих нет MySql (на момент написания статьи последняя версия 5.1.9). Будем надеяться, что разработчики добавят эту возможность в будущем. Их следует применять, если есть такая возможность. Дело не только в безопасности, но еще и в том, что при их использовании запрос компилируется только один раз, после чего он многократно выполняется с разными параметрами, что ускоряет работу СУБД.

    При использовании этой техники в запросе вместо изменяющихся значений ставятся знаки вопроса, а значения, которые нужно подставить вместо них передаются через отдельные параметры функции, вызывающей выполнение запроса. Тоесть данные отделены от запроса, что полностью исключает возможность sql-инъекции.

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

    Как уже было сказано, обычно для каждого параметра пишется фильтр. Между тем, вполне возможно при разработке скриптов допускать использование любых символов в параметрах, но так, чтобы приложение было не уязвимым, а просто прозрачным ко всем 256-и символам ASCII, включая оба типа кавычек и нулевой байт. Речь в данном случае идет, конечно, только о строковых парметрах. В документации вы можете найти описание функции mysql_escape_string, которая вставляет слэши перед кавычками, слэшами и нулевыми байтами. При этом нужно еще не забыть заключить параметр в кавычки. Этого данная функция не делает.

    Числовые параметры достаточно обработать функцией intval. Она преобразует строку в число, а если это невозвожно, возвращает ноль. При подстановке числа в запрос оно прелбразуется обратно в строку. Таким образом, два этих преобразования обеспечивают необходиную фильтрацию, защищающую числовой параметр от SQL-инъекций.

    С учетом выше изложенного, типичный запрос будет строиться так:

    $sql = "SELECT id, str_val, int_val FROM tbl1 WHERE str_val = '" . mysql_escape_string($v1) . "' AND int_val = " . intval($v2) . ";";

    Теперь о том, как на основе выше описанных приемов соорудить собственную замену для отсутствующей в MySql возможности посылать запросы с параметрами.

    Мы напишем параметризатор запросов, тоесть такой компонент, который будет подставлять данные, должным образом отфильтрованные, вместо знаков вопроса.

    Нужно отметить, что ни один подобный параметризатор не ускорит многократное выполнение запросов из одного шаблона, потому что на сервер всё равно будут посылаться запросы с уже подставленными данными, и сервер будет каждый раз заново парсить запрос. Так что я надеюсь, что разработчики MySql всё же примут решение реализовать параметризованные запросы в одной из следующих версий своего продукта. Мы же преследуем цель обезопасить разрабатываемые нами сайты от SQL-инъекций, что возможно и при существующей версии движка.

    Прежде всего, надо решить, как определять, рассматривать параметр как строку или как число. Можно рассматривать как числа все параметры, которые так выглядят. Тогда преобразование параметра к фрагменту sql-запроса запишется так:

    if (!is_numeric($param) $param = '"' . mysql_escape_string($param) . '"';

    Тоесть, если перед нами не число, то обрабатываем параметр как строковый, в противном случае оставляет строку без изменения. Это может привести к тому, что строковые параметры, состоящие только из цифр, будут рассматриваться как числа. Это не смертельно, потому, что MySql преобразовывает типы данных по мере необходимости. В таком случае, необязательно вообще делать различия между типами. Пусть MySql сам разбирается, что как обрабатывать. Можно рассматривать все параметры как строки. С другой стороны, можно, наоборот, расширить обработку параметров, добавив, например, возможность вставки в запрос ключевого слова NULL в случае, если параметра нет.

    Приступим к кодированию. Кроме того, чтобы преобразовать все параметры, необходимо еще определить, куда их вставить. Для этого нужно найти в SQL-запросе все знаки вопроса, не являющиеся частью строковых констант. Если бы не эта последняя оговорка, можно было бы просто использовать функцию explode и затем вставить параметры между подстроками из массива, который вернет эта функция. Но обнаружение констант требует правильной обработки кавычек и слэшей. Это можно реализовать, но получится громоздкий код, при написании которого можно сто раз ошибиться. А если нет уверенности в правильноси написанного кода, то он не оправдывает своей цели (гарантия безопасности) и, следовательно, весь наш труд пропадет впустую.

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

    function sql_process_param($p) {
    if (is_numeric($p)) return $p;
    if (is_string($p)) return '"' . mysql_escape_string($p) . '"';
    return 'NULL';
    }

    function sql_subst_params($sql, $params) {
    $f = explode('?', $sql);
    $r = reset($f);
    $p = reset($params);

    while (($chunk = next($f)) !== FALSE) {
    if (substr($r, -1) != '') {
    $r .= sql_process_param($p);
    $p = next($params);
    } else $r .= '?';
    $r .= $chunk;
    }
    return $r;
    }

    Пример использования:

    $sql = sql_subst_params('SELECT id FROM tbl WHERE a=? AND b=? AND c=?;', Array($_GET['a'], $_GET['b'], $_GET['c']));

    В завершение добавлю, что кроме sql-инъекций существуют и другие типы уязвимостей, поэтому применив описанный мной подход, расслабляться еще рано. Например, для защиты от XSS-атак все строки, вставляемые в выводимую страницу, следует пропускать через функцию htmlspecialchars(). Кроме того прозрачность для любых строк не всегда хороша. Например, логины юзеров обычно должны состоять только их букв и цифр, мыла и линки должны быть валидными, но это уже выходит за рамки данной статьи.

    Статья взята с сайта http://www.mrblack.pp.ru