Что такое SQL инъекция и как от неё защищаться
Черновик текста доклада «Окончательное решение проблемы SQL инъекций» c конференции DevConf 2013.
Для начала — небольшой дисклеймер, поскольку обманутые ожидания могут испортить впечатление от самого лучшего мероприятия. К примеру, как-то давно, в эпоху видеосалонов, я пошел на фильм, который считал боевиком, и который показался мне чудовищной халтурой и клюквой. И только на середине я догадался, что этот фильм не боевик, а пародия на боевики. И заржал в совершенно неподходящем месте.
Поэтому должен предупредить — никаких ошеломляющих открытий, зловещих инъекций и срывов покровов не будет. Моя задача — проанализировать существующие решения, выявить их сильные и слабые стороны, и предложить некоторое общее правило, которое будет работать не только на бумаге, но и в реальной жизни.
Я догадываюсь, что многие пришли настроенными довольно скептически. Мол, «ну сколько можно про инъекции? В детском саду их прошли и забыли». Проблема как раз в том, что в детском саду учатся еще не пониманием, а запоминанием.
И запоминают некоторый свод правил, который, к сожалению, работает не всегда.
Давайте для начала рассмотрим историю вопроса:
Слайд
============
Как происходит типичный диалог:
— Как защититься от инъекций?
— используй prepared statements
— а что делать если они не подходят
— нннууу. вот тебе воркараунд.
— а вот у меня другой случай, и твой воркараунд не подходит
— ннуууу. вот тебе другой воркараунд
— а вот.
===============
Как-то это выглядит не слишком заманчиво.
Методологически ничем принципиально не отличается от «искейпь входящие данные». А для всех остальных случаев — воркараунд.
Но самое ужасное, что наличие этих воракраундов — это повторение ситуации «кто в лес, кто по дрова», и в результате программист пишет воркараунды сообразуясь со своим пониманием вопроса. А понимание там часто — аховое. И этот сон разума порой рождает таких чудовищ, что хочетcя плакать от ужаса. Пример я приведу ниже.
Поэтому нам нужен более последовательный и логичный механизм.
И вот я предлагаю сначала выработать теоретические основы этого механизма, а потом подумать над тем, как его практически реализовать.
Также прошу не делать стойку на то, что я буду вначале уделять много внимания форматированию. О подготовленных выражениях, которые на данный момент считаются вершиной технической мысли в защите от инъекций, мы обязательно поговорим — но позже.
Доклад будет будет состоять из трех частей, характеризуемых извечными русскими вопросами — “кто виноват”, “что делать” и “как нам теперь со всей этой фигнёй взлететь”.
Да, и «разное». Конечно же — «разное».
Несмотря на всю провокационность этого вопроса, ответ — НЕТ.
Это очень легко доказать.
Я думаю,всем знаком этот малолетний, эээ. долботряс с заковыристой фамилией? И комикс, из которого каждый разработчик уяснил себе, что пользовательский ввод надо обязательно дезинфицировать, делать безопасным?
$name = «Bobby’;DROP TABLE users; — «;
$query = «SELECT * FROM users WHERE name=’$name'»;
Ну ок, уяснили. Но что если у нас решил зарегистрироваться не малолетний хакир, а честный шевалье Д’Артаньян? Уж этот-то защитник сирот и утешитель вдов никогда не позволит себе сделать какую-нибудь пакость типа инъекции. Защищаться, вроде бы, не от чего. Но зарегистрироваться у него все равно не получится.
Ещё пример. Допустим, у нас нет никакого пользователя вообще, а есть похапист. назовем его Расмус. Который вообще ничего не добавляет в запрос динамически, а тупо пишет его от руки.
слайд
————————-
$sql = “SELECT group FROM table «;
Снова FAIL.
————————
Но у него все равно ошибка. Почему? Да потому что он, как и все остальные, не отформатировал свой запрос правильно.
Три картинки — Бобби, Дартаньян и Расмус — что у них общего
Фишка в том, что элементы запроса мы должны форматировать в любом случае.
От пользователя ли они пришли, или из нашей базы, или их вообще админ написал, или робот сгенерил —
Слайд
————
ИСТОЧНИК данных не имеет вообще никакого отношения к форматированию запросов.
————
Это очень важная вещь для понимания дальнейшего материала. И одновременно одно из самых больших заблуждений разработчиков — что “защищать” надо только пользовательский ввод. Та же мамаша нашего Бобби Фейсом-об-тейбл говорит про “инпут”. Но на примере Расмуса мы видим, что форматировать надо всякий элемент запроса, а не только “пользовательский ввод”.
Просто потому что
Нормальная программа, такая же как наш РНР скрипт. А всякая программа — я думаю, никто не будет с этим спорить — будет работать только если в ней всегда соблюдается корректный синтаксис.
И вот соблюдение корректного синтаксиса и должно являться нашей целью. А защита будет обеспечена просто в виде побочного эффекта.
Самое главное что нужно понимать — это то, что мы формируем эту программу динамически.
Страшно представить себе, что из себя представляли бы скрипты, если бы средний пользователь РНР собирал их динамически, так же, как запросы. Впрочем, даже и в статике некоторые пользователи умудряются-таки накосячить. Пример из моей богатой практики:
Слайд
——————
$s = fread(«$f»,7);
——————
— это написал один юзер и долго удивлялся, почему у него ничего не работает.
Это очень показательный, на самом деле, пример. Не секрет, что новички обожают обращаться к переменным, заключая их в кавычки. ПХП же, так же как и мускуль, прощает, до поры-до времени, вольное обращение с форматированием. Но когда когда не прощают — бьют прямо в темечко.
Слайд
————-
«хорошо зафиксированный пациент в анестезии не нуждается»
————-
Итак. Надеюсь, что смог убедить вас в том, что запросы надо не защищать, а корректно форматировать. Перефразируя известное медицинское изречение — Корректно отформатированный запрос в защите не нуждается.
Сформулируем 4 правила корректного составления запросов.
1. Форматирование должно быть ПОЛНЫМ.
потому что если оно не будет полным, то полным будет пушистый зверек, который к нам придет.
Типичные примеры неполного форматирования
$id = $mysqli->real_escape_string($id)
$sql = «SELECT * FROM $table = $id»
$sql = «SELECT * FROM `$table`»
———————————
при этом первый пример на самом деле простителен
поскольку на протяжении многих поколений в пхп разработчиков вбивали мысль, что «mysql_real_escape_string защищает от инъекций». Вот они и защищаются. Хотя на самом деле делают дыру у себя в приложении размером с пробоину Титаника. А все потому, что
НЕ ИМЕЕТ ни малейшего отношения
— К SQL инъекциям
— К защите от чего бы то ни было
Имеет отношение
— к Форматированию строк
— для этого форматирования недостаточна
—————————————
Что же на самом деле делает эта функция?
Разумеется, никого ни от кого она не защищает. А всего лишь только форматирует данные для использования их в строковом литерале SQL. Из чего мы можем сделать два вывода.
1. применение этой функции должно быть ВСЕГДА сопряжено с заключением обработанного текста в кавычки.
2. Без кавычек результат этой функции не имеет ни малейшего смысла, и, как следствие, никакой защиты от инъекции не даст.
В этом смысле идеальным примером может являться функция PDO::quote(), которая, единственная из всех известных мне функций АПИ, делает полное форматирование — И квотинг И искейпинг.
Но у нее все равно есть один принципиальный недостаток, о котором мы поговорим ниже.
2. Форматирование должно быть адекватным.
слайд
————————————————————
Типичнейший пример неадекватного форматирования
$table = $mysqli->real_escape_string($table);
$sql = «SELECT * FROM `$table`»
(картинка фейспалм)
—————————————————————
Здесь к идентификатору применяется форматирование от строк. Результат немного предсказуем.
ещё один пример чудовищно неадекватного форматирования я обнаружил совсем недавно:
В комментариях к функции PDO::quote() в мануале почти год висела вот такая дичь, набрав аж 8 положительных оценок.
Слайд
—————-
While rewriting some application to PDO, remember that PDO->quote is adding the quotes around the whole value! Compared to mysql_real_escape_string which is quoting only the dangerous characters.
$value = «hello’s world» ;
echo mysql_real_escape_string ( $value );
// hello»s world
echo $dbh -> quote ( $value );
// ‘hello»s world’
// This second quoting will break your old SQL statements which includes the quotes already. Workaround is to remove first and last character
echo substr ( $dbh -> quote ( $value ), 1 , — 1 );
// hello»s world
?>
———————-
После того, как я его обнаружил, этот коммент потерли, но это очень хорошая иллюстрация того, к чему приводит самодеятельность пхп программистов в деле защиты от инъекций.
поэтому надо четко знать — в каких случаях какое форматирование применять
вот небольшой список того, как должны форматироваться различные элементы запроса:
Слайд
————————
1. Строки
* могут быть добавлены через native prepared statement (работает не всегда)
или
* должны быть заключены в кавычки
* спецсимволы (грубо говоря — те же самые кавычки) должны быть экранированы
* плюс должна быть установлена правильная кодировка клиентской библиотеки.
2. Числа
* могут быть добавлены через native prepared statement (работает не всегда)
или
* должны быть отформатированы так, чтобы содержать только цифры, знак и — для соответствующего типа — точку
3. идентификаторы
* должны быть заключены в обратные кавычки
* спецсимволы (грубо говоря — те же самые обратные кавычки) должны быть экранированы
4. Операторы и ключевые слова
* тут нет специальных правил форматирования, за исключением того, что они должны быть законными операторами и ключевыми словами
——————-
Как видим, правил не одно и даже не два. Одних типов форматирования 4 штуки (на самом деле, их ещё больше, но об этом — позже). И весь этот список необходимо постоянно держать в голове и не перепутать. Если мы не найдем подходящего решения.
Но — перейдем к следующему пункту
3. Форматирование должно быть ОБЯЗАТЕЛЬНЫМ
С одной стороны, важность этого пункта объяснять не приходится. С другой — постоянно можно слышать от ленивых (в плохом смысле слова) разработчиков — да зачем это значение форматировать — мы его и так знаем! И вот здесь надо вести понятие динамических данных.
Если для константы, для элемента запроса, прописанного в скрипте, форматирование может быть опциональным, то для динамических, ПЕРЕМЕННЫХ частей запроса, оно должно быть ОБЯЗАТЕЛЬНЫМ. Мы НЕ МОЖЕМ знать, что лежит в переменной. Программы всякие бывают. В программах бывают ошибки.
Которые могут привести к непредсказуемому значению переменной.
поэтому.
Хотя константную часть, разумеется,подвергать строгому форматированию не обязательно, переменные части запроса должны форматироваться в обязательном порядке.
4-е и последнее (по счету, но не по важности!) правило:
4. Форматирование должно производиться КАК МОЖНО БЛИЖЕ к выполнению запроса.
Это тоже на самом деле очень важный момент, если над ним задуматься.
Понимание важности этого правила начало потихоньку приходить к сообществу, но очень фрагментарно и толком не сформулированное.
Самое большое количество инъекций происходит оттого, что обработка данных для запроса и помещение их в запрос оказываются разнесены по коду. Что сразу создает почву для нарушения предыдущих трех правил.
В первую голову приходят, конечно, волшебные кавычки. Которые пытались форматировать данные заранее, при этом неизвестно было — ни пойдут ли они в запрос вообще, ни — в каком формате — налицо нарушение правил 1 и 2: волшебные кавычки делают неполное форматирование и вдобавок мы не можем знать, какого типа данные прйдут в запрос.
Но даже и без волшебных кавычек форматирование данных и исполнение запроса часто оказываются довольно-таки сильно разнесены в коде. И здесь мы можем придти к нарушению третьего правила
Приведу пример.
В одной компании использовался великолепный движок, в котором была могучая фильтрация входящих параметров.
Если по логике приложения от клиента приходить должна цифра — то ты мог быть уверен, что это будет цифра. Валидировал вообще ВСЁ что движется, в соответствиями требований SEO любое расхождение с ожидаемыми параметрами вызывало 404.
Все привыкли и никто по поводу инъекций не парился
Но потом пришлось написать небольшой код, который должен был запускаться до всех валидаций, и инитить одну переменную из реквеста.
а потом, сильно ниже по коду, эту переменную задействовали в запросе
и никакой валидации не сделали — привыкли же.
В системе, которую делают несколько разработчиков, риск повышается — у семи нянек дитя без глазу.
Давайте рассмотрим два примера кода
Слайд
———————————-
$sql = ‘SELECT * FROM users WHERE email=’.PDO::quote($email);
$res = $pdo->query($sql);
$sql = ‘SELECT * FROM users WHERE email=?’;
$res = $pdo->prepare($sql);
$res->execute(array($email));
———————————-
При настройках по умолчанию, эти два куска кода отправят на сервер два ИДЕНТИЧНЫХ запроса.
Но при этом первый практически не пропагандируется, а про второй вам трендят на каждом углу.
Зачем же платить больше?
А вот именно затем: плейсхолдер обеспечивает и форматирование гарантированно, и как можно ближе к исполнению.
[namw=three]Часть третья. Как нам со всей этой фигней взлететь, или типизованные плесхолдеры.[/name]
А теперь посмотрим, какие есть средства этого добиться.
Подготовленные выражения же!
НО! Совсем не то жалкое подобие левой руки на два типа данных, которое нам втюхивают производители API. А идея подготовленного выражения в целом.
Здесь я хочу разграничить понятия:
Слайд
———-
— “Родные” подготовленные выражения, поддерживаемые на уровне сервера. Данные отправляются на сервер отдельно от запроса
— сама идея подготовленного выражения в целом, когда занные представлены в запросе неким представителем, плейсхолдером, который при окончательной обработке заменяется на актуальные данные
—————
Вообще, я должен признать, что идея родных подготовленных сама по себе отличная — поскольку она реализует фундаментальный принцип разделения кода и данных. Код (запрос) и данные передаются на сервер раздельно, и данные не имеют возможности никаким образом повлиять на код.
Но. чрезвычайная ограниченность сводит все преимущества на нет.
Еще один минус родных (т.е. — поддерживаемых безой) подготовленных выражений — то, что они работают не всегда.
Слайд
——————-
Prepared statements не поддерживаются
— для идентификаторов
— для массив для оператора IN().
— для массив для INSERT/UPDATE.
——————
И, самое главное — они не поддерживают все типы данных, которые мы можем отправить в запрос.
Почему нам нужны плейсхолдеры для ин и для сет?
А потому что такие штуки все равно добавляются в запрос, но делается это руками.
То есть, мы вернулись к тому, от чего ушли — ручная сборка запроса и функции!
здесь надо остановиться поподробнее. Про подготовленные выражения рассказывают много ерунды, но почти никто при этом не упоминает ни их ограниченности, ни по-настоящему сильных сторон.
И тут мы упираемся в чудовищную ограниченность родных подготовленных выражений. НА САМОМ ДЕЛЕ нам нужно гораздо больше плейсхолдеров, чем предлагают родные prepared
Если посмотреть на наши запросы не предвзято, то мы увидим, что помимо банальных скаляров, которые, в сущности, практически всегда можно прослешить и окавычить, в запрос могут попадать такие элементы, как
Не будем закрывать глаза и прятать голову в песок, воображая, что добавлять идентификатор нам никогда не понадобится. В реальной жизни — понадобится.
поэтому надо просто иметь плейсхолдер для добавления идентификаторов. Вот и всё.
Дальше интереснее. Если смотреть с точки зрения приложения(!) в запрос могут опадать и комплексные типы! Например:
Вы, конечно, по привычке скажете, что это не массив, а динамический набор переменных, для которых можно динамически сформировать запрос. Вам самим не смешно? 🙂 За что боролись — на то и напоролись. Хотели уйти от ручного формирования запросов, и к нему же и пришли! Не говоря уже о том, что некоторые АПИ превращают такую банальную операцию в АД.
Поднимите руки, кто пробовал забиндить в mysqli IN с переменным числом параметров.
Кто не пробовал — рекомендую. Увлекательнейшее занятие по производству отборного говнокода.
И опять же — с точки зрения приложения, массив, который мы передаём в IN — это законченный блок данных. Почему мы строку можем передать через плейсхолдер, делая наш код ЧИСТЫМ, свободным от ручного форматирования, а массив — нет? зачем портить свой собственный код? Если надо добавить в запрос массив — значит, должен быть плейсхолдер, поддержэивающий такое добавление. Точка.
— массив для INSERT/UPDATE. Да-да. Это тоже вполне себе массив, ассоциативный. В ключах — имена полей, в значениях — сответственно — значения.
Передавать массивом на вставку значительно удобнее, чем собирать запрос вручную.
При этом инкапсулировать добавление этих массивов куда-нибудь подальше не получится — в АПИ нет такого понятия, как «содержимое оператора IN».
У ПДО с этим получше, сильно получше. вплоть до того, что ПДО со скрипом и оговорками можно назвать DALом. Всего-то строк 5 лишнего кода.
Вы скажете — а у нас есть прекрасная функция-хелпер.
Ну. давайте посмотрим
Слайд
—————————-
Новенькая епонская лесопилка
$db->insert($table, $data);
$db->update($table,$data,$where);
$db->delete($where);
—————————
Insert INGORE
INSERT .. ON DUPLICATE UPDATE
DELETE FROM .. JOIN
———————————
И иного-много других страшных слов.
И вот наша прекрасная епонская лесопилка сделала “Хррр. ”
что делаем мы? Вариантов немного — либо писать запрос руками, либо — писать к хелперу хелперы. Чувствуете? Попахивает? Это он, родимый. Говнокод!
Но — я хочу это подчеркнуть — всё равно запрос составляется по-старинке руками. Это никуда не годится. Это сразу отбрасывает нас назад, в каменный век ручного форматирования — со всеми его опасностями:
— запрос формируется вручную. что может привести к тому, что либо данные окажутся испорчены, или не отформатированы.
— стремясь защитить скалярные данные, при этом почти никто не думает об индентификаторах, которых тут вагон и маленькая тележка.
И родные подготовленные выражения нам совсем ни с одним из этих типов данных не помогут.
Давайте теперь обратимся к более широкому толкованию этого понятия.
И мы увидим, что подготовленные выражения как раз идеально подходят (при соблюдении некоторых условий) для тех правил, которые мы вывели во второй части:
1. при соответствующей обработке входящего значения форматирование будет всегда полным, причем вне зависимости от желания, состояния или способностей программиста.
2. при привязке значения можно указать тип.
3. форматирование будет проведено в обязательном порядке
4. и в приложении будет невозможно написать код, который будет стоять между помещением знчения в запрос и его обработкой — значение уже уехало в драйвер и больше мы его не увидим.
Плюс два второстепенных, но весьма приятных бонуса:
— prepared statement форматирует только значение, которое попадает в запрос, а не исходную переменную, которую потом можно использовать, неиспорченную, где-то ещё — вывести на экран, например.
— prepared statement может сделать наш код фантастически коротким, выполняя все операции по форматированию внутри.
Слайд
Что знает обычный человек про прерареды?
— что они «быстрее»
— что они безопасные [подразумевается — в отличие от искейпинга].
Вот это реальные преимущества ПЭ. То, что вам говорят в рекламе — по большей части туфта.
— скорость. Как выясняется, в нормальном веб-приложении повторяющиеся запросы — это, скорее признак профнепригодности программиста. Только в исключительно редких, маргинальных случаях, какого-нибудь консольного скрипта, делающего тыщи инсертов — может быть и будет какой-то прирост. А может и не будет — для инсерта план запроса примитивный, а парсинг текста на современны компьютерах происходит очень быстро.
— безопасность (подразумевается — в отличие от ). Нормально отформатированный запрос ничуть не опаснее запроса с ПЭ. Они взаимозаменяемы. БД, которая не может обеспечить безопасность стандартных запросов — это бессмыслица. Разумеется, она ее и обеспечивает. Поэтому можно использовать и тот и другой способ с одинаковой безопасностью.
Единственная проблема — как привязавать.
Ни один из предлагаемых стандартными API способов нам не подходит
Слайд
——————
— PDO “lzay” binding: $db->execute(array($var1,$var2,$var3,$var4));
— mysqli: $db->bind_param(“issi”,$var1,$var2,$var3,$var4);
— PDO standard:
$sth->bindParam(1, $var1, PDO::PARAM_INT);
$sth->bindParam(2, $var2, PDO::PARAM_STR);
$sth->bindParam(3, $var3, PDO::PARAM_STR);
$sth->bindParam(4, $var4, PDO::PARAM_INT);
—————————————-
Смотрите:
— “Ленивая” привязка ПДО не подходит — она не учитывает тип, и валит все в строки. Нам это категорически не подходит, у нас типов гораздо больше.
— mysqli. уже поумнее, но если у нас массив с неизвестным числом параметров, то на горизонте маячит call user_func — и это уже становится похоже на фарс
— Стандартная привязка в столбик. СЛИШКОМ много кода
И тут нам на помощь приходит решение, которое древнее, чем … эпоха юникс!
Функция printf, которая появилась еще в языке ALGOL68, и в которой плейсхолдер САМ говорит обработчику, как его форматировать — поистине гениальная. она решает всепроблемы и убивает одним выстрелом целую стаю зайцев. Нужно всего лишь использовать тот же самый принцип
Берем обычный плейсхолдер, и приписываем к нему тип.
Парсер читает запрос, вырезает плейсхолдеры, смотрит тип, и соответсрвующим образом форматирует входящую переменную.
А ведь в запрос можно передать просто массив, чтобы он был корректно подставлен на место плейсхолдера. Всё. Гору говнокода как корова языком слизала.
Получается, что подддержка расширенного набора плейсхолдеров — насушная необходимость.
Поэтой причине нам надо изобретать свой АПИ. Без вариантов.
Если бы не правило номер три — в принципе, можно было бы и писать горы говногода с ручным форматированием.
Но жазнь любого проекта складывается так, что постепенно код форматирования может отнести от кода исполнения запроса на значительное расстояние. Особенно стараниями программистов, которые хотят что-то улучшить.
И здесь можно пойти двумя путями.
Первый всем вам известен — это написать SQL на PHP, получив на выходе QueryBuilder. Задача почтенная, но трудновыполнимая.
Второй — сохранить SQL как есть, но дополнить его плейсхолдерами для действительно нужных в запросе типов данных.
Первый вам всем известен, поэтому посмотрим пока на второй (но потом все равно вернемся немного попинать первый 😉
Варианта реализации плейсхолдеров у нас опять же, два
Первый и сейчас активно применяется — это указывать тип передаваемых данных в операторе биндинга.
Но. Это снова превращает наш код в говнокод. Толпы привязок — это то,от чего мы хотели уйти! И вот здесь у нас получается Пятое правило, не менее важное, чем все остальные:
1. Каждый элемент запроса, помещаемыый в него динамически, должен быть корректно отформатирован.
2. Правила форматированя кардинально различаются для различных элементов запроса
3. Данные должны форматироваться настролько близко к исполнению запроса, насколько возможно.
4. По этой причине любые динамические данные должны попадать в запрос только через плейсхолдер.
5. Плесхолдер должен быть типизованным.
Эта очень простая мысль, которая прозвучала, скажем, у Котерова ещё черт знает когда — но так и не полчила распространения.
Я думаю это потому, что не была надлежащим образом сформулирована. Потому что типизованные плейсхолдеры нассматривались Котеровым как заплатки, частный случай. А не как принуипиальное ядро всей системы.
И вот здесь мы впервые подойдем к вопросу, который можно назвать защитой от SQL инъекций.
Но инъекция это не того рода, как обычно. ООт обычных мы закрыты цкликом и полностью.
При инъекции, которую я имею в виду, целостность запроса не нарушается.
Но у встроенных есть ещё и недостаток — очень ограниченная область применения.
not somewhere else, so, our safety won’t rely on such unreliable sources like
— some PHP ‘magic’ feature which rather spoils the data than make it safe.
— good will of one (or several) programmers, who can decide to format (or not to format) our variable somewhere in the program flow. That’s the point of great importance.
При этом. если статические литералы можно форматировать по потребности, то динамические — безусловно!
Вы скажете — ну зачем ещё раз форматировать, если валидатор и так все уже отвалидировал?
А вот потому. У семи нянек — дитя без глазу.
В одном проекте, с которым мне пришлось разбираться, была практически идеальная система валидации, не требовававшая никакого
ручного форматирования, для чисел. Все всегда знали, что если переменная пришла со стороны,
и она должна быть числовго типа — она и будет числового. Но тут пришлось дописать код, который бы запускался до всей валидации.
но привычка-то осталась!
Давайте попробую объяснить.
SQL запрос — это программа. Нормальная программа, такая же как наш РНР скрипт.
Который состоит из элементов *множества* разных типов. А не тупо делится на «запрос» и «данные».
Фишка в том, что мы формируем эту программу динамически.
Страшно представить себе,
под конец котелось бы немного попинать конкурентов.
Каждый, наверное, писал функции нсерт делет, селект. а потом обо орастало и обрастало
Не. ну правда — если вам нужно только доставать по первичному ключу — не нужна вам РСУБД, используйте носк.
Но если используете скл — не загоняйте в прокрустово ложе
Тут от порядлка таблиц в джойне план запроса зависит, а вы хотите отдать его на откуп железяке.
Страшилки дядюшки Шифлетта.
Но при этом надо понимать, что способов сделать инъекцию — ровно ОДИН: нарушить целостность запроса.
А все вышеперечисленное — только следствия, способы эксплутировать тем или иным образом имеющуюся уязвимость.
Так вот — не надо заниматься глупостями, и защищаться от этих способов. Надо закрывать уязвимость в запросе. И тогда все эти многочисленные хитроумные способы превратятся в тыкву.
— Stored procedures (с целью защиты от инъекций)
— Заведение разных пользователей на чтение и запись
— Фильтрация разных «опасных» символов или их сочетаний
——————————————
Вы уже, думаю. понимаете, что это полная ерунда
во-первых, не работает в реальной жизни
во-вторых,
Защита сайта от sql инъекций
Перед тем как мы поговорим о том, как выполняется защита сайта от sql инъекций, нам нужно сначала разобраться что это такое и почему этот тип атак настолько опасен. Большинство современных сайтов уже делают все, чтобы на их страницах не было таких мест, через которые злоумышленник мог бы выполнить эту атаку, но все же иногда такие проблемы случаются даже на популярных ресурсах или в часто используемых движках.
Сегодня мы разберем что такое sql инъекция, как она работает, а также как с ней бороться, как на уровне языка программирования, так и с помощью создания различных помех для злоумышленника с помощью веб-сервера. Это тоже достаточно эффективно.
Что такое SQL инъекция?
Как вы знаете, большинство сайтов интернета используют базу данных для хранения информации. Не удивительно, что во время формирования страницы выполняются запросы к базе данных. И тут вроде ничего такого нет, если не считать то, что при формировании запроса могут использоваться данные, введенные пользователем. Например, при создании комментария, поиске или даже переходе на другую страницу.

И тут появляется возможность для SQL инъекции. Если пользователь введет в поле определенный запрос, то он сможет создать свой запрос к базе данных. Это позволит ему делать практически все, что угодно, украсть ваши данные, стереть базу, получить доступ к паролям пользователей, добавить новых пользователей и все что ему будет угодно.
Например, вот так выглядит SQL запрос запроса id статьи при поиске:
SELECT id,title,content FROM posts WHERE title LIKE ‘%запрос_пользователя%’
А теперь представьте, что пользователь вместо ключевых слов из статьи введет такую комбинацию:
1%’; DROP TABLE posts LIKE ‘%;
И в результате получится такой себе полностью рабочий запрос, которого вы совсем не ожидали:
SELECT id,title,content FROM posts WHERE title LIKE ‘%1%’; DROP TABLE posts LIKE ‘%%’
Это сработает с любым запросом, в котором есть ввод данных пользователей, если программист не позаботился о безопасности. Следовательно, чтобы решить эту проблему нужно просто экранировать все кавычки в запросах от пользователя. А теперь перейдем к способам защиты.
Защита сайта от sql инъекций на уровне PHP
Защита от sql атак может выполняться различными способами. Первое, на что стоит обратить внимание и что очень важно — это чтобы программист уже во время написания кода занимался экранированием кавычек с помощью таких функций, как mysql_real_escape_string или mysqli_real_escape_string. Если каждая переменная, которая используется в запросах к базе будет профильтрована ими, программистом или на уровне CMS, то никаких проблем не возникнет.
Но почему же на протяжении последних 14 лет все еще случаются атаки на SQL? Все просто. Программисты ленивы, а делать небезопасные запросы к базе так просто, в то время как безопасные — более сложны. Во всяком случае, сложнее, чем небезопасные.
Защита на уровне веб-сервера
Не всегда есть возможность исправить все недоработки в коде. Например, популярный движок Drupal имеет более 20 000 строк кода, WordPress — 60 000, а Joomla — 180 000. Было бы нецелесообразно все это переписывать. Но можно поступить по-другому. Сначала мы отфильтруем все значения из переменной REQUEST в самом начале скрипта. Вставьте этот код после подключения базы данных:
Для PHP 7 вам нужно будет использовать функцию mysqli_real_escape_string, поскольку расширение mysql было удалено из этой версии языка. Для экранирования кавычек используется лишь функция clean и все что ниже нее. То что выше применяется для совместимости с версиями PHP ниже 5.4. В них была настройка Magic Quotes, которая при включении экранировала все кавычки. Чтобы наш скрипт все не портил мы сначала все убираем экранирование если она включена.
Теперь у вас есть дополнительная защита на уровне PHP. Осталось еще позаботиться про защиту на уровне веб-сервера. Если вы используете Nginx, то можно добавить такие правила в вашу секцию server:
set $block_sql_injections 0;
if ($query_string ~ «union.*select.*\(«) set $block_sql_injections 1;
>
if ($query_string ~ «union.*all.*select.*») set $block_sql_injections 1;
>
if ($query_string ~ «concat.*\(«) set $block_sql_injections 1;
>
if ($block_sql_injections = 1) return 403;
>
Здесь мы отфильтровываем все запросы, которые содержат слова select, concat вместе с кавычками. Это явный признак, что пользователь пытается выполнить SQL инъекцию, а значит его запрос нужно заблокировать.
Также вы можете блокировать подозрительные адреса на уровне веб-сервера Apache, например, выбрать самые часто употребляемые ключевые слова SQL. Правда, это опасно, поскольку могут быть заблокированы и запросы обычных пользователей. Добавьте в вашу секцию VitualHost такие строки:
RewriteCond % [^a-z](declare¦char¦set¦cast¦convert¦delete¦drop¦exec¦insert¦meta¦script¦select¦truncate¦update)[^a-z] [NC]
RewriteRule (.*) — [F]
Но это еще не полное решение, здесь можно пойти дальше. Эта блокировка не защищает от SQL инъекций, выполняемых с помощью POST или RESTful запросов. Еще можно активировать модуль mod_security:
sudo a2enmod mod_security
Здесь тоже есть несколько правил, которые защищают от инъекций. Но желательно использовать еще и более комплексный подход.
Разделение базы данных
Чтобы сделать вашу базу данных более безопасной, вы можете разделить ее на несколько частей. В области информационной безопасности существует такое понятие, как принцип минимальных привилегий. Суть принципа состоит в том, что программа или пользователь должны иметь доступ только к тому, что им нужно и нечему больше. Например, будет разумно хранить данные о кредитных картах пользователя и форумы в разных базах данных. Особенно, если формы используют устаревшую версию phpBB. Это своеобразная дополнительная защита sql injection.
Анализ запросов перед приложением
Еще один вариант — это использования более сложных систем защиты. Это может быть аппаратное решение, которое работает поверх iptables или ipfw или же система обнаружения вторжений на сервере HIDS, такая, как OSSEC. Но такое решение намного сложнее, чем нужно и не предназначено для решения нашей задачи. Можно использовать специальные брандмауэры веб-приложений, с помощью которых, в том числе, выполняется защита от SQL инъекций. Это такие свободные решения, как ModSecurity или IronBee.
Выводы
Нет идеального решения или волшебной палочки, с помощью которой бы получалась стопроцентная защита сайта от sql инъекций, хотя PHP стремится быть все более защищенным. Начиная с версии 7.0 была удалена поддержка расширения MySQL. Теперь необходимо переходить на MySqli или PDO. И это хорошо, потому что эти расширения делают проще использование данных с подготовленными операторами. Хотя для этого все еще требуется написать несколько строк.
Существует множество способов выполнения SQL атак, но до тех пор, пока разработчики не будут писать правильный код, а на веб-серверах не будут на максимум использоваться средства защиты, эти атаки не исчезнут из списка ТОП 10 OSWAP. Настройте вашу систему так, чтобы защитить свои данные и базы данных.
SQL-инъекции и межсайтовый скриптинг XSS: как себя защитить
![]()
Разработчики программного обеспечения должны держать много информации у себя в голове. Существует множество вопросов, которые нужно задать, когда речь заходит о создании веб-сайта или приложения: Какие технологии использовать? Как будет настроена структура? Какой функционал нам нужен? Как будет выглядеть пользовательский интерфейс? Особенно на рынке программного обеспечения, где производство приложений больше похоже на гонку за репутацией, чем на хорошо обдуманный процесс, один из важнейших вопросов, который часто остается на дне “Списка важных”: Как наш продукт будет защищен?

Если вы используете надежный, открытый фреймворк для создания своего продукта (и, если он доступен и пригоден, почему бы и нет?), тогда базовые проблемы безопасности, как атаки CSFR и кодирование пароля, могут быть уже решены за вас.
Тем не менее, быстро развивающимся разработчикам будет полезно освежить свои знания о стандартных угрозах, дабы избежать ошибок новичка. Обычно самое слабое место в безопасности вашего программного обеспечения — это вы.
А кто может заниматься взломом?. Есть этичный хакер – это тот, кто ищет возможные слабости в безопасности и приватно рассказывает их создателям проекта.
А чёрный хакер, которого так же зовут “Взломщик (cracker)” – это тот, кто использует эти слабости для вымогательства или собственного блага.
Эти два вида хакеров могут использовать одинаковый набор инструментов и, в общем, пытаются попасть в такие места, куда обычный пользователь не может попасть. Но белые хакеры делают это с разрешением, и в интересах усиления защиты, а не уничтожения её. Черные хакеры – плохие ребята.
Вот некоторые примеры наиболее распространённых атаках, которые используют слабости в защите: Внедрение SQL-кода и межсайтовый скриптинг XXS.
SQL атаки
SQL-инъекция (SQLi) — это тип инъекционной атаки, которая позволяет выполнять вредоносные SQL команды, для получения данных или вывода из строя приложения. По сути, злоумышленники могут отправлять команды SQL, которые влияют на ваше приложение, через некоторые входные данные на вашем сайте, например, поле поиска, которое извлекает результаты из вашей базы данных. Сайты, закодированные на PHP, могут быть особенно восприимчивы к ним, и успешная SQL-атака может быть разрушительной для программного обеспечения, которое полагается на базу данных (например, ваша таблица пользователей теперь представляет собой пустое место).
Вы можете проверить свой собственный сайт, чтобы увидеть, насколько он восприимчив к такого рода атакам. (Пожалуйста, тестируйте только те сайты, которыми вы владеете, так как запуск SQL-кодов там, где у вас нет разрешения на это, может быть незаконным в вашем регионе; и определенно, не очень смешно.) Следующие полезные нагрузки могут использоваться для тестов:
- ‘ OR 1=’1 оценивается как константа true, и в случае успеха возвращает все строки в таблице
- ‘ AND 0=’1 оценивается как константа false, и в случае успеха не возвращает строк.
К счастью, есть способы ослабить атаки SQL-кода, и все они сводятся к одной основной концепции: не доверяйте вводимым пользователем данным.
Смягчение последствий SQL-кодов.
Чтобы эффективно сдержать атаки, разработчики должны запретить пользователям успешно отправлять необработанные SQL-команды в любую часть сайта.
Некоторые фреймворки сделают большую часть тяжелой работы за вас. Например, Django реализует концепцию объектно-реляционного отображения, или ORM с использованием наборов запросов. Мы будем рассматривать их в качестве функций-оболочек, которые помогают вашему приложению запрашивать базу данных с помощью предопределенных методов, избегая использование необработанного SQL.
Однако возможность использовать фреймворк никогда не является гарантией. Когда мы имеем дело непосредственно с базой данных, существуют и другие методы, которые мы можем использовать, чтобы безопасно абстрагировать наши SQL-запросы от пользовательского ввода, хотя они различаются по эффективности. Они представлены по порядку от более к менее важному:
- Подготовленные операторы с переменной привязкой (или параметризованные запросы)
- Хранимые процедуры
- Белый список или экранирование пользовательского ввода
Если вы хотите реализовать вышеприведенные методы, то эти шпаргалки — отличная отправная точка для более глубокого изучения. Достаточно сказать, что использование этих методов для получения данных вместо использования необработанных SQL-запросов помогает свести к минимуму вероятность того, что SQL будет обрабатываться любой частью вашего приложения, которая принимает входные данные от пользователей, тем самым смягчая атаки SQL-кодов.
Межсайтовые скриптовые атаки (XSS)
Если вы являетесь хакером, то JavaScript — это в значительной степени ваш лучший друг. Правильные команды будут делать все, что может сделать обычный пользователь (и даже некоторые вещи, которые он не должен делать) на веб-странице, иногда без какого-либо взаимодействия со стороны реального пользователя.
Межсайтовые скриптовые атаки, или XSS, происходят, когда код JavaScript вводится на веб-страницу и изменяет ее поведение. Его последствия могут варьироваться от появления неприятных шуток до более серьезных обходов аутентификации или кражи учетных данных.
XSS может происходить на сервере или на стороне клиента и, как правило, поставляется в трех вариантах: DOM (Document Object Model — объектная модель документа) на основе хранимых и отображаемых XSS. Различия сводятся к тому, где полезная нагрузка атаки вводится в приложение.
XSS на основе DOM
XSS на основе DOM возникает, когда полезная нагрузка JavaScript влияет на структуру, поведение или содержимое веб-страницы, загруженной пользователем в свой браузер. Они чаще всего выполняются через измененные URL-адреса, например, в фишинговых письмах.
Чтобы увидеть, насколько легко было бы для введенного JavaScript манипулировать страницей, мы можем создать рабочий пример с веб-страницей HTML. Попробуйте создать файл в локальной системе под названием xss-test.html (или любым другим) со следующим кодом HTML и JavaScript:
My XSS Example var name = new URLSearchParams(document.location.search).get('name'); if (name !== 'null')
На этой веб-странице будет отображаться заголовок «Hello!” если только он не получает параметр URL из строки запроса со значением name . Чтобы увидеть работу скрипта, откройте страницу в браузере с добавленным параметром URL, например:
file:///path/to/file/xss-test.html?name=Victoria
Наша небезопасная страница принимает значение параметра URL для имени и отображает его в DOM. Страница ожидает, что значение будет хорошей дружественной строкой, но что, если мы изменим его на что-то другое? Поскольку страница принадлежит нам и существует только в нашей локальной системе, мы можем тестировать ее сколько угодно. Что произойдет, если мы изменим параметр name , скажем, на:

Это всего лишь один пример, который демонстрирует, как может быть выполнена атака XSS. Смешные всплывающие оповещения могут быть забавными, но JavaScript может принести много вреда, в том числе помогая злоумышленникам украсть пароли и личную информацию.
Хранимые и отраженные XSS
Хранимые (stored) XSS возникают, когда полезная нагрузка атаки хранится на сервере, например, в базе данных. Атака влияет на жертву всякий раз, когда эти сохраненные данные извлекаются и отображаются в браузере.
Например, вместо того чтобы использовать строку URL-запроса, злоумышленник может обновить свою страницу профиля на социальном сайте, чтобы внедрить скрытый сценарий, скажем, в раздел “Обо мне”. Сценарий, неправильно сохраненный на сервере сайта, будет успешно выполняться всё время, пока другой пользователь просматривает профиль злоумышленника.
Одним из самых известных примеров этого является червь Samy, который практически захватил MySpace в 2005 году. Он распространялся путем отправки HTTP-запросов, которые копировали его на страницу профиля жертвы всякий раз, когда просматривался зараженный профиль. Всего за 20 часов он распространился на более чем миллион пользователей.
Отраженные (reflected) XSS аналогично возникают, когда введенные данные перемещаются на сервер, однако вредоносный код не сохраняется в базе данных. Вместо этого он немедленно возвращается в браузер веб-приложением.
Подобная атака может быть осуществлена путем заманивания жертвы для перехода по вредоносной ссылке, которая отправляет запрос на сервер уязвимого веб-сайта. Затем сервер отправит ответ злоумышленнику, а также жертве, что может привести к тому, что злоумышленник сможет получить пароли или совершить действия, которые якобы исходят от жертвы.
Ослабление XSS
Во всех этих случаях XSS могут быть сдержаны с помощью двух ключевых стратегий: проверка полей формы и предотвращение прямого ввода данных пользователем на веб-странице.
Проверка полей формы
Фреймворки снова могут нам помочь, когда речь заходит о том, чтобы убедиться, что представленные пользователем формы находятся в актуальном состоянии. Один из примеров — встроенные классы полей Django, которые предоставляют поля, проверяющие некоторые часто используемые типы, а также задают нормальные значения по умолчанию.
Например, поле электронной почты Django использует набор правил, чтобы определить, является ли предоставленный ввод действительным письмом. Если отправленная строка содержит символы, которые обычно не присутствуют в адресах электронной почты, или если она не имитирует общий формат адреса электронной почты, то Django не будет считать это поле допустимым и форма не будет отправлена.
Если вы не можете полагаться на фреймворк, можете реализовать вашу собственную проверку входных данных. Это можно сделать с помощью нескольких различных методов, включая преобразование типа, например, гарантируя, что число имеет тип int() ; проверка минимальных и максимальных значений диапазона для чисел и длин строк; использование заранее определенного массива вариантов, который позволяет избежать произвольного ввода, например, месяцев года; и проверка данных на соответствие строгим регулярным формулировкам.
К счастью, нам не нужно начинать все с нуля. Помогут доступные ресурсы с открытым исходным кодом, такой как валидация репозитория регулярных выражений OWASP, который предоставляет шаблоны для сопоставления их с некоторыми распространенными формами данных. Многие языки программирования предлагают библиотеки проверки, специфичные для их синтаксиса, и мы можем найти множество таких библиотек на GitHub.
Хотя это и может показаться утомительным, правильно реализованная проверка ввода может защитить наше приложение от восприимчивости к XSS.
Предотвращение прямого ввода данных
Элементы приложения, которые непосредственно возвращают пользовательский ввод в браузер, при обычной проверке могут быть неочевидны. Мы можем определить области приложения, которые могут быть подвержены риску, изучив несколько вопросов:
- Как происходит поток данных через приложение?
- Что ожидает пользователь, когда он взаимодействует с этими входными данными?
- Где на нашей странице появляются данные? Становятся ли они встроенными в строку или атрибут?
Вот некоторые примеры полезных нагрузок, с которыми мы можем поиграть, чтобы проверить входные данные на нашем сайте (опять же, только на нашем собственном сайте!). Успешное выполнение любого из этих образцов может указывать на возможную уязвимость к XSS из-за прямого ввода данных.
- «>
test
- ‘+alert(1)+’
- «onmouserover onmouseover article-item»>
SQL-инъекция
При работе с информацией мы часто используем различные веб-приложения. К сожалению, многие из них содержат уязвимости, которые могут быть использованы злоумышленниками с целью получения несанкционированного доступа к этой информации. Одной из актуальных уязвимостей можно назвать атаку на приложение с помощью SQL-инъекции, в результате которой киберпреступник может украсть сведения для авторизации, конфиденциальную информацию, открыть доступ к базе данных. Рассмотрим детально подобные атаки, а также способы защиты от них.
Что такое SQL-инъекция?
Речь идет об уязвимости приложения, которая позволяет внедрить в него фрагмент кода и тем самым добраться до базы данных (БД). Эти атаки относятся к числу наиболее распространенных и легко осуществимых за счет того, что наибольшее число баз данных созданы на SQL. С их помощью злоумышленники могут произвести замену цифрового образа, модифицировать данные, добраться до закрытой и конфиденциальной информации. Для проведения SQL-инъекции они внедряют неотфильтрованные и неправильным образом экранированные символы в SQL-операторы и функции при вводе данных. Как следствие, становится возможным ввод данных, направленных на получение несанкционированного доступа к информации.
Как работает SQL-инъекция?
Подобные атаки построены на использовании потенциала SQL-языка, который широко распространен при работе с базами данных. При внедрении SQL-инъекции происходит исполнение запроса, направленного в сторону базы данных, который связан с выполнением функции или операции. Иными словами, происходит передача информации со стороны пользователя на получение доступа.
Формы авторизации в приложениях сконфигурированы таким образом, чтобы получать определенные группы данных, например, пароль и логин пользователя. После ввода данных происходит их проверка на соответствие и выносится решение о получении или блокировании доступа.
Главная проблема безопасности при вводе данных в приложениях — отсутствие защитных механизмов у форм ввода на добавление дополнительной информации, что чревато передачей зловредных запросов со стороны киберпреступников в адрес базы и открывает возможности для проведения направленных манипуляций с данными для достижения корыстных целей.
SQL-инъекции в приложениях
Подобные атаки на приложения чаще всего совершаются в следующих направлениях:
- Проверка параметров БД. SQL-запрос нацелен на сбор сведений по базе данных, о ее размерах и компонентах для дальнейшей коррекции вектора атаки и выбора приоритетных целей.
- Сбор скрытых данных. Направлен на получение закрытой информации из приложения путем модификации запроса, позволяющего увидеть данные, изначально недоступные в самом приложении.
- Атака через оператора UNION. Посредством этого оператора можно объединять несколько запросов в один. В этом случае киберпреступник легко извлекает нужную информацию из закрытых разделов базы данных. Этим способом похищают сведения для входа в учетную запись.
- Атака через логику приложения. Связана с использованием техники обхода пароля, получения прав администратора приложения. Открывает множество возможностей для использования приложения в самых разных преступных целях.
- Слепые атаки. Проводятся путем инициации внешнего сетевого взаимодействия с приложением и отправки множественных запросов.
Как защититься от SQL-инъекций?
- Постоянный контроль вводных данных. Любая вводимая пользователем информация в запросе связана с возможными рисками. До подтверждения ее безопасности нужно рассматривать данные как полученные извне и валидировать их. Все учетные записи, подключенные к БД, должны изначально иметь привилегии минимального класса. Дополнительный контроль ввода может включать проверку по разрешенным словам при вводе данных.
- Всегда очищать все входные данные. Использовать входные данные напрямую небезопасно, даже если они подтверждены аутентификацией. Необходимо позаботиться об очистке ввода, исключить любые элементы, которые могут быть потенциально связаны с вредоносным кодом.
- Проводить параметризацию запросов с помощью специально подготовленных для этого операторов. В ходе этого процесса происходит формирование запросов с известными метками, к которым прикрепляют пользовательские параметры ввода данных.
- Осуществлять регулярную проверку используемых приложений на безопасность. Для этого можно применять онлайн-сервисы или специальные инструменты анализа, которые находят уязвимости приложений, закладки, проверяют ПО. Использование безопасных приложений – залог отсутствия дополнительных рисков и угроз.
- Выполнять регулярное обновление используемого ПО. Развитие угроз не стоит на месте, они становятся все сложнее и разнообразнее. Использование старых версий ПО повышает вероятность взлома и эксплуатации уязвимостей в коде приложений. Регулярные обновления – это мера, которая позволяет повысить уровень защиты приложений.
Примеры SQL-инъекций многочисленны, и они остаются одной из очевидных угроз ПО. Для поддержания уровня безопасности важно выполнять регулярный мониторинг используемых приложений. В этом может помочь сканер уязвимостей, который автоматически выполняет анализ приложений по группе параметров и выявляет проблемы безопасности. Solar appScreener – пример подобного анализатора уязвимостей в ПО. С его помощью легко проверить код приложений, выявить недекларированные возможности и ошибки. Инструмент не требует от пользователей специальных знаний и предоставляет подробные рекомендации по устранению обнаруженных проблем.