Как стать автором
Обновить

Выводы по SQL injection

Время на прочтение4 мин
Количество просмотров11K


Я знаю, что тема SQL инъекций уже всем набила оскомину.

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

О том, как не допустить инъекций была уже масса статей — повторять не буду — сводится все к нескольким банальнейшим пунктам практики:

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

    Обязательно проверяйте тип и приводите к тому, что Вы ожидаете.
    Если Вы ждали целочисленное число, а пришли буквы и(или) точка — сообщаем об ошибке куда-нибудь, а клиенту выдаем 500 / 404 / или нужный Вам код в ответ.

    Мой пример ф-ии для unsigned integer:

    function getAsUint( &$var ) {
    	return ( preg_match( "~^\\s*(\\d+)\\s*$~i", $val, $t ) ) ? $t[1] : null;
    }
    


    На вход она получает исследуемую переменную, по выходу она возвращает корректное значение, или null, если значение некорректное или пустое.
    Это лишь пример, но суть понятна — Вы можете сделать свою супер функцию с блекджеком и феями нетяжелого поведения.

    В PHP можно воспользоваться функциями фильтрации, тут про них есть.

    Надо помнить и учитывать возможность пробелов во входных параметрах форм. Например — эта функция корректно отбросит пробелы вернув лишь число.
    Таким образом, «123» и " 123 " для этой функции — корректные значения, но вернет она всегда «123»
  2. В SQL запросе, который вы пишите, квотироваться должно ВСЕ, даже то, что вроде как и не нужно (например числовые значения)

    Т.е. надо расставлять правильные кавычки, как для названий полей, таблиц и др, так и для любых значений.

    Во-первых, это убережет Вас от ненужных пауз за размышлением: “ставить или нет кавычку”, а во вторых, оставит меньше шанс написать потенциально уязвимый запрос типа

    "select * from `table` where `id`={$id}"

    (я думаю у многих, когда дело касается id написано именно так).

  3. Все данные (а это не только то, что пришло извне но и то, что сформировалось внутри функции движка) должны проходить экранирование.

    Можно использовать mysql_real_escape_string, можно свое.

    Вот моя функция:
    function prepareStr( $str ) {
    	return "'". str_replace(
    		array( '\\', "\0", "\n", "\r", "'", '"', "\x1a" ),
    		array( '\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z' ),
    		$str ) . "'";
    }

    используется например так:
    "select * from `table` where `id`=" . prepareStr( $tag )

    Кавычки внутри параметров для нее не нужны — она их подставляет сама — это убережет от того, что можно случайно забыть про п2 :)

    Данные по замене взяты из мануала mysql отсюда

    Теперь немного про набившую ужу оскомину mysql_real_escape_string — один из параметров которой — connection id.
    Мне, например, это не очень удобно, ибо не при всех использованиях у меня он есть
    Но ведь он зачем-то нужен этой функции? Писали что без нее она работает некорректно :)
    Если заглянуть в исходники mysql, то эта ф-я лежит в mysys/charset.c и использует идентификатор чтобы по нему получить CHARSET_INFO, а его в свою очередь чтобы определить лишь мультибайтная кодировка или нет.
    И сколько длина одного символа. Во всяком случае, я это так понял из листинга. Быть может тут есть гуру C? Поправьте меня если я не прав.

    Поскольку я работаю с UTF при коннекте с базой или же в самом худшем случае с WIN1251, то можно вполне обходиться prepareStr не заморачиваясь с кодировкой, вот если она у Вас действительно экзотическая — тогда лучше использовать mysql_real_escape_string.

    У функции prepareStr как и у mysql_real_escape_string есть одна уязвимость — дело в том, что они обе никак не экранируют "%" и "_" которые используются в конструкции типа LIKE.
    Внутри LIKE работает экранирование вида "\%" и "\_" а вот вне — нет — вернет два символа. Почему? — неизвестно, но это так.
    По этому если Вы хотите избавится от этой неоднозначности я вижу два пути — либо использовать в подстановках для LIKE другой вариант функции, либо же использовать одну функцию, но для универсальности придется заменять все символы “% “и “_” на нечто вида

    "CONCAT( 'строкаДо%или_', '\%или\_', 'строкаПосле%или_ ')"

    Возможно есть и лучшее решение, но я о нем не знаю.

  4. Использовать placeholders / prepared statements (или интегрирующие их PDO / ActiveRecord / ORM и т.д.)

    Это, кстати, абсолютно разные вещи.
    Кратко так — placeholders — это помощник, формирующий обычный текстовый запрос к БД, но эскейпящий все параметры сам, т.е. он избавляет от необходимости "...". mysql_real_escape_string( $param). "...".
    А prepared statements — это способ попросить подготовить запрос базой данных, например для многократного вызова.

    С моей точки зрения использовать для обычного сайта и обычного CRUD prepared statement — это как по воробъям из базуки стрелять. У них есть много подводных камней с кэшированием и т.д.
    Если Вы их знаете или знаете что это Вам необходимо – ну тогда дело уже другое.

  5. Ну и самое главное, что надо усвоить про SQL инъекции — серебрянной пули нет.

    При написании запросов нужно использовать МОЗГ и ЖОПУ. МОЗГ — потому что он должен понимать, что он пишет, а ЖОПУ – потому, что она чует что что-то не так ;)

    А если без юмора — мне смешно, когда люди, использующие PDO или prepared statement, считают что все, SQL инъекции в их проектах невозможны — это не так, ибо все это — лишь ИНСТРУМЕНТЫ, снижающие количество мест, где нужно подумать, но не убирающие их.
    И если человек сделал уязвимый запрос, то все равно, через что он его закинет в базу — он будет уязвимым.



Пример, встретившийся в одном из проектов, что я разбирал (типа такого):
"select * from `{$table}` where `col` LIKE ?"

И система все правильно эскейпила и подставляла, только вот в переменной $table наш кодер был очень уверен — ибо бралась она из файла конфигурации плагина для движка.
А лежал этот файл в директории, куда был разрешен upload…
И вот так, из-за одного уязвимого плагина слили плохие дяди всю базу…

Ну вот как то так.
Надеюсь, я кому-то помог c кашей в голове после кучи статей и чтений ну как же защитится, и уверенных в своем коде людей стало больше.
Жду Ваших комментариев и дополнений и спасибо Вам что дочитали до конца.

P.S. Про ошибки, если найдете – пожалуйста в личку.

Интересное из комментариев
  • Как справедливо заметил zapimir для использования mysql_real_escape_string ПРАВИЛЬНО, требуется установка локали через mysql_set_charset. SET NAMES не влияет на нее (будет браться по умолчанию)
Теги:
Хабы:
-4
Комментарии57

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн