Pull to refresh

Comments 95

Использовать нормальный ORM и не знать гембеля… ^_^
Использовать placeholders.
UFO just landed and posted this here
Касательно web, в стандартом мануале php есть пример простейшей защиты от сабжа. Собственно больше ничего не надо, все остально надстойки.
UFO just landed and posted this here
что еще за placeholders?
По-моему вы что-то напутали.
В разных языках это название одного и того же :)
UFO just landed and posted this here
насчет экранирования. А разве обычный addslashes можно пробить?
UFO just landed and posted this here
А mysql_real_escape_string?
UFO just landed and posted this here
использовать шустрый мозг
Плагин нашёл на главной странице Гугла 16 ошибок
Magic Quotes в рекомендованных способах защиты? Мда

Главное и пожалуй единственное правило — не верить ничему, что приходит от пользователя. Это должно быть не вторым пунктом «Проверка введенных данных в запросах», а первым!

Как вы это будете делать — вопрос второй. Лучше всего подготовленные запросы использовать.
Тут имеется ввиду «Именно поэтому необходимо использовать код, который будет экранировать кавычки.», т.е. не надеяться на волшебные ковычки
Поправил текст чтобы не было двоякого понимания.
Проверка ввода данных менее эффективна, чем параметризованные запросы и хранимые процедуры

Проясните — как хранимые процедуры могут противостоять инъекции?
В параметрах процедуры присутствует строгая типизация, т.е. вы явно указываете тип данных этого параметра, т.е. в IN pId BIGINT окромя числа ничего не вставишь, отсюда отсутствует возможность подбора инъекции
То есть что-то вроде этого не сработает?
1); DROP TABLE Users; /*
Нет, база выдаст сообщение о несоответствии типов, даже более того, если тип параметра допустим IN comment VARCHAR(255), то все равно это не сработает… он будет работать с «DROP TABLE Users; /* » как со строкой, не обращая внимания на инъекцию
Не подумайте, что я придираюсь. Хочется просто разобраться.
Допустим, есть такой запрос в базу:
"SELECT * FROM FUNC1( $var );"

$var — переменная, которую вводит пользователь на форме.
Введя указанную выше строку, мы получим
"SELECT * FROM FUNC1( 1 ); DROP TABLE Users; /* );"

Мне кажется, что drop table будет выполнен. Или я не прав?
Вы привели как раз пример динамического запроса.
После прочтения статьи, нашу функцию FUNC1 мы будем вызывать примерно так (смотря как и где будем делать bind): SELECT FUNC1(?pVar) FROM DUAL;
А данные к параметру pVar (тип TEXT) будем привязывать (bind) уже отдельно (как именно — тут от конкретного языка зависит, например для PHP можем использовать prepare), в переменную pVar запишем «DROP TABLE Users; /*» и мы ничем не рискуем.
если мы используем бинды, то при чём тут хранимые процедуры?
Не все языки имеют строгую типизацию, чтобы максимально избежать ввода некорректных данных, лучше использовать дополнительную защиту со стороны базы данных, это во-первых, во-вторых логику, раскрывающую подробности вашей базы и структуры хранения, лучше спрятать в процедуре\функции, например, зачем светить чем вы делаете подсаливание паролей в php скрипте, или для увеличения безопасности особо «опасные» запросы нужно разрешить запускать только от root`ого пользователя, такое безопаснее всего сделать через базу. Плюс процедуры вас отучат от динамических запросов в принципе (если вы не будите делать такие же prepare в процедуре разумеется), согласитесь, большое искушение в php не писать два запроса с небольшим отличием, а использовать один с динамической подстановкой данных, которые могут быть опасны. Ну и плюс процедуры создают некий уровень абстракции. Намного проще и быстрее понять и правит что делает код «CALL listAllUser(typeFilter INT)», чем «SELECT id, name, lastName, middleName FROM Users user INNER JOIN userStatus status ON user.statusId = status.id WHERE status.value = ». $status. " and deleted = 0"
да-да, и будут у нас тысячи процедур вида listUsersByBirthDayOverAndSexEqualOrderByAgeWithLimitAndOffset x)
А как такой вариант #1:
listAllUser(filterId INT) которая внутри фильтрует данные путем join`а к таблице фильтра,
таблица filter имеет тип Memory
заполняем перед вызовом процедуры listAllUser наш фильтр процедурой AddFilter(typeId int, valueId int)
… как то вот так.

Второй вариант:
Вытаскиваем какие-то данные в контроллер (класс работающий со списком User`ов) и в нем реализуем эти самые фильтры (не на стороне базы).

Все зависит от поставленных задач, если мы в динамике должны постоянно менять набор фильтров, то (опять таки в зависимости от количества данных, ширины канала, объема оперативки) выбираем приемлемый вариант реализации: либо в базе это делать, либо на клиенте. Ну и также зависит от конкретной реализации схемы базы.
В общем это тема отдельной статьи)
Мсье проектировал, реализовывал (один и в команде) сложные ERP, CRM системы, некоторые из которых (по лени и не соблюдению вышеописанных правил на некоторых участках кода) подвергались SQL-инъекциям) И код этих участков рефакторился, из-за порчи данных в важных таблицах (например кто-то умудрился испортить хеши всех паролей у пользователей и чуть не сорвал рабочий день огромной компании в трех странах, благо дальше этого ничего сделать не смог\не захотел и мы быстренько восстановили из бэкапа и задумались над проблемой безопасности всерьез)
да-да, богатый опыт по выдиранию гланд через анус прямо-таки бросается в глаза х)
Как говорится: сколько программистов, столько мнений
нет, говорится обычно по другому: есть только два мнения — моё и неправильное х)
Мсье действительно извращенец.
В данном случае хранимые процедуры БД не дают никакой прибавки к защищенности кода.
Поясню. Перед тем, как создвать динамический запрос нам так или иначе необходимо контролировать тип динамических данных. Иными словами ни на одном участке кода, где используются или каким-то чудесным образом могут появиться генерируемые пользователем данные (например, где-то какой-то функцией возвращаемые), необходимо контролировать тип данных, с которыми мы работаем и использовать принудительное приведение типов. Обычный intval() отсечет половину проблем в местах, где мы ждём id/флаг, а злоумышленник может подставить строку. Для строковых/текстовых параметров прекрасно работает mysql_real_escape_string().
И, да, первое, что я делаю, начиная писать что-то новое — выношу все функции по работе с БД в отдельный класс, в котором присутствуют все необходимые проверки перед выполнением запроса, начиная с самых простых и заканчивая самыми параноидальными.
Плюс к тому, подобный подход позволяет намного быстрее рефакторить код и в случае возникновения ошибки — править его только в одном месте и не искать по всему коду подобные уязвимые куски.
Ну начнем с того что десктопные приложения тоже работают с базами данных, и не все используют Perl, PHP и тд. И если запрос к базе можно перехватить, то его можно и поправить, в случае в процедурами мы не раскрываем схемы базы, логики запросов и тд. А вот в случае запроса мы позволяем менять запрос как угодно.

Как проще вытащить все поля, в том числе и пароли пользователей: если у нас процедура getUserList (которая вытаскивает id, name), или запрос, который выглядит как SELECT id, name FROM users. Я думаю правка «id, name» на "*" проще чем подобрать имя таблицы. Инъекция может быть на любой стадии, от формирования запроса, до передачи SQL серверу.

А дополнительная защита когда пользователь входит в базу под своим системным пользователем, у которого права определяются в самой процедуре. Т.е. даже имея имя, пароль пользователя (не рута) и имея имя процедуры ничего секретного вытащить не сможем
Повторюсь, все зависит от задачи и степени важности данных, если это форум, разумеется не стоит впадать в пароною, а вот если мы имеем дело с финансами, то тут стоит подумать ото всех степенях защиты, в том числе и правки исполняемого приложения в памяти, но это уже тема отдельной статьи)
Не думаю, что кого-то удивлю, если скажу, что финансовые системы тоже могут работать на Perl и PHP.
Действительно, если существует возможность перехвата и модификации запроса на уровне приложение->БД, то логично будет использовать хранимые процедуры. Но это уже, скорее, больше относится к типу атак MitM, чем к SQL-Injection.
Другое дело — мне тяжело представить такой случай. Возможно потому, что прикладным программированием я не занимался уже несколько лет, всё больше web да интранет.

P.S. Почему-то вспомнилась эпическая байка про программистов PЖД, которые гоняют всю базу целиком между клиентом и сервером ^^
А так-же выключайте вывод логов ошибок клиенту, из которых можно получить представление о структуре базы и используйте небанальные названия таблиц: например придумайте некий префикс и добаляйте ко всем осмысленным названиям: someprefix-user, someprefix-profile,…
Особой пользы от использования «небанальных названий таблиц нет», ибо на большинстве БД можно читать служебные базы данных, которые содержат информацию о структуре, и редкий админ сейчас закрывает к ним доступ.

А вообще тема сисек не раскрыта. Уязвимыми могут быть не только «данные введенные пользователем», а так же куки, юзер агенты и прочие данные передаваемые в http headers, и даже, как бы странно это не звучало, параметры сессии.
я бы ещё добавил «храните пароли в БД в зашифрованном виде с солью»
Могу сказать и другое, что magic_qoutes не всегда правильно. Использую модуль для отключения его на любом хостинге:

if (! get_magic_quotes_gpc() ) return 1;
while ( list($k,$v)=each($_POST) ) $_POST[$k]=stripslashes($v);
return 1;

Потому что если в коде уже жестко зашита поддержка addslashes или mysql_escpace_string, то под соусом magic_qoutes получается полная каша.
Только stripslashes надо делать рекурсивно к массивам, а то данные из <input type=«radio» name=«part[]» value=«77» /> будут испорчены
И ещё, stripslashes следует делать не только значений, но и ключей!
Проблема SQL-инъекций решается элементарно. Если вы юзаете ORM, написанный не-индусами — там скорее всего проблемы нет. Если же вы хотите писать запросами, быстренько пишем библиотечку для обработки параметров, которая сама будет все экранировать, типа такой:

$data = getAll('SELECT * FROM ?t WHERE id =? LIMIT 10', 'table1', $unsafe_id);

Подобная есть например у dklab (вроде называется DBSimple), хотя конечно лучше написать свою.

А все эти PDO, дотнеты — это муть.
>Подобная есть например у dklab (вроде называется DBSimple), хотя конечно лучше написать свою
Чем лучше? На мой взгляд, всегда лучше использовать популярную стороннюю библиотеку, которая хорошо протестирована (речь не конкретно про библиотеку от dklab — не использовал, оценить не могу), чем изобретать велосипед.
> А все эти PDO, дотнеты — это муть.
Сильно :)
Пацан к успеху идет, не мешайте :)
да я только потом заметил, удалил бы комментарий, если бы мог :)
Ой, у вас прям паранойя. Цифра в профиле ничего не значит и ничего не говорит о личности автора.
Та если б я за цифрами следил… Тем более, сча есть мэджик батон для восстановления, так сказать, девственной плевы.

Я просто на ваши комменты смотрю уже давно ;)
> А все эти PDO, дотнеты — это муть.
> Сильно :)

Ну не расписывать же мне на 2 страницы конкретные примеры, где использование PDO требует кучу писанины. Посмотрите, как там сделан например биндинг переменных — уродливо и неуклюже, нужно отдельный statement для каждой переменной. Также там доступен всего лишь один плейсхолдер. Я посмотрел, как он устроен, прикинул, сколько лишней писанины придется делать, если его использвоать, и сделал краткий вывод («муть»), не верите — идите, пользуйтесь, только потом не жалуйтесь, что неудобно, и что у вас лапша, а не код полчается. Мне например морально тяжело видеть, когда для простых вещей приходится писать кучу строчек, или что еще хуже, копипастить код из файла в файл.

И не писать же всякие набившую оскомину примеры неудачного синтаксиса из C# вроде:

SomeSpecialList list = new SomeSpecialList();

и так далее.
А я вот нихрена не понял, при чем здесь списки из дотнетов. Такое ощущение, что вы не шарите, что:

— во-первых, то, что вы привели, это c#, а не .net. Пишите на boo/Nemerle/IronPython/PhalangerPHP, и будет вам счастье на .net с любимым синтаксисом, например;

-во-вторых, тот же c# на месте не стоит, если чо:
var list = new SomeSpecialList(); // an example of wrists-friendly way ;)
> Пишите на boo/Nemerle/IronPython/PhalangerPHP, и будет вам счастье на .net с любимым синтаксисом, например;

Ага, и по RDP на сервер коннектиться, чтобы IIS черехз ГУИ настраивать — счастья то! Что только люди не придумают, лишь бы извратиться.

> во-вторых, тот же c# на месте не стоит, если чо:

Ну вот, можно порадоваться за шарпщиков, чуть меньше теперь у них голова болеть будет.
А, вы из этих, которые для веб только… Ну, iis command line+power shell никто не отменял; правда, сложность в том его учить надо, это вам не php.

Впрочем, ладно, счастливого magic-квотирования вам.
> Чем лучше? На мой взгляд, всегда лучше использовать популярную стороннюю библиотеку, которая хорошо протестирована (речь не конкретно про библиотеку от dklab — не использовал, оценить не могу), чем изобретать велосипед.

Тем, что на мой взгляд, содержит несколько даром ненужных функций, абсолютно неправильный подход к кешированию (кеширование рез-в запросов, это чушь, MySQL умеет делать это намного лучше), и ограниченнный набор плейсхолдеров, мне того, что есть не хватает. Кроме того, там вроде нет lazy connect. Зачем мне кривое и громоздкое, пусть и протестированное решение?

Ах да, еще по поводу  «протестированности»: это не так. Я использовал как-то их библиотеку. где-то год назад. Уже тогда. для нормальной работы под PHP5, без ваарнингов и нотисов, пришлось руками ее патчить. Полгода спустя, с новой версией PHP − опять проблемы, пусть я их и решил за 20 минут, но все же.

Кроме того, я коду, который я лично написал, доверяю куда как больше, чем неизвестно кем написанному (да еще и как выясняется, устаревшему).
потому что вы работаете один.
А вот я лично вашему коду доверяю меньше, чем сторонней известной библиотеке =) так-то
Ну не знаю, как минимум стоит потестировать сторонний код, прежде чем лепо использовать. А то вот, WP, Drupal или Joomla — на словах, крупные openSource проекты, награды, а в код страшно заглядывать.
Вы уверены что ваш код объективно лучше?
Ну субъективно (мне) он больше нравится. А чтобы говорить «объективно лучше», нужны какие-то критерии.

Разрешите не согласиться. Почему PDO — это муть, когда во-первых в них уже реализовано именно то, что вы описываете, да еще и на более низком уровне (а значит быстрее), а во-вторых последнее время PDO используются во многих ORM (см. Doctrine, Propel).
Потому что реализовано это плохо и неудобно. Я хочу, чтобы было так:

> $top_posts = getAll('SELECT id, title, body FROM ?_posts WHERE category =? AND author IN (?a) LIMIT 10 ', $category, $authors);

> $comment = array(array('author' => 'me', 'title' => '...', 'body' => '...'), array('author' => 'me2', 'title' => '...', 'body' => '...'));
> do('INSERT INTO ?t ?insert', 'commects', $comments);

А они предлагают уродливый объект с неудобным интерфейсом, ну-ка, сколько мне придется лапши написать для решения той же задачи? Вот то-то. Проектируют не для людей, а для индусов (которым пофиг что писать, лишь бы копеечку заплатили), ни один образованный человек этим пользоваться не захочет.
странных вещей ты хочешь %-) а я хочу так:

$top_posts = $db->query( 'SELECT id, title, body FROM posts WHERE category = ', $category, ' AND author IN ', $authors, ' LIMIT 10 ' )->row;
$top_posts = $db->query( 'SELECT id, title, body FROM posts WHERE category = ', $category, ' AND author IN ', $authors, ' LIMIT 10 ' )->table;

разумеется
Интересный подход, не сталкивался с таким. Но способ с плейсхолдерами имеет то преимущество, что 1) если запрос большой, с многими параметрами, он читабельнее и не превращается в кашу из кавычек и запятых и 2) плейсхолдеры могут быть разных типов, а с вашим подходом это не сделать :)

разбиваем на строки и всё получается красиво:
$authors= $db->query('
    select uri, title
    from thing
    where uri in (
        select object
        from link
        where relation = "?rel:author"
        and subject = ',$uri,'
    )
')->rows;

а каких типов тебе не хватает?
Вот примерный список:

— стандартный строковой плейсхолдер
— числовой (не добавляет кавычек вокруг, используется например в LIMIT ?d, ?d)
— плейсхолдер-имя таблицы (к нему авт-ки доб. префикс, я использую префиксы для имен таблиц)
— плейсхолдер, делающий из массива строк список через запятую
— плейсхолдер, вставляющий префикс таблицы
— плейсхолдер для идентификатора (у них могут быть другие правила экранирования и другой вид кавычек)

Ну и пара «монстров» — плейсхолдеров, один для создания INSERT-выражения с кучей скобок из массива, второй, для выражения, которое используется в UPDATE… SET…

И вроде специальный плейсхолдер, для добавления имени таблицы (в сложных JOIN-запросах например) перед именами полей.

Вообще, стремлюсь перейти на использовние моделей и Table Gateway (и запросы писать руками не буду), так что того, что у меня давно написано, вполне хватает.
> числовой (не добавляет кавычек вокруг, используется например в LIMIT ?d, ?d)
зачем? субд сама приведёт тип

> плейсхолдер, делающий из массива строк список через запятую

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

> плейсхолдер-имя таблицы (к нему авт-ки доб. префикс, я использую префиксы для имен таблиц)
> плейсхолдер, вставляющий префикс таблицы

это не плейсхолдер, а скорее макрос, ибо не требует передачи данных в каждом запросе

> плейсхолдер для идентификатора (у них могут быть другие правила экранирования и другой вид кавычек)

лучше не использовать идентификаторы в которых нужно что-то экранировать, ибо тем самым создаём себе геморрой на ровном месте

> Ну и пара «монстров» — плейсхолдеров, один для создания INSERT-выражения с кучей скобок из массива, второй, для выражения, которое используется в UPDATE… SET…

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

> И вроде специальный плейсхолдер, для добавления имени таблицы (в сложных JOIN-запросах например) перед именами полей.

не надо делать сложных джойнов, пожалейте базу %-)

> > числовой (не добавляет кавычек вокруг, используется например в LIMIT ?d, ?d)
зачем? субд сама приведёт тип

Гм… с нестрогой типизацией в PHP явно указывать, что мол, тут число, а тут строка, мне как-то надежнее, чем потом по коду бегать и писать intval()/strval(). Хотя можно и так.

> > плейсхолдер, делающий из массива строк список через запятую
> зачем? достаточно просто передать массив и он сериализуется в такой вот список

Но ведь бывают список строк, а бывает например список идентификаторов (например, список полей для SELECT). Тут надо 2 плейсхолдера.

> лучше не использовать идентификаторы в которых нужно что-то экранировать, ибо тем самым создаём себе геморрой на ровном месте

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

>> Ну и пара «монстров» — плейсхолдеров, один для создания INSERT-выражения с кучей скобок из массива, второй, для выражения, которое используется в UPDATE… SET…
> для этого лучше использовать вообще отдельный метод, ибо тут нужно не только формировать запрос, но и разбивать его на части, если он получается слишком длинным

Насчет «на части» — да, здравая мысль, но я пока с такими огромными запросами не сталкивался. Но все же плейсходер для превращения ассоц. массива в набор вида field = 'value' and field2 = 'value' довольно-таки удобен.

Вот, нашел у себя еще один плейсхолдер, «рекурсивный», ?(), он берет переданный массив, содержащий кусок запроса с плейсхолдерами и параметры, интерпретирует его и вставляет полученный результат :) Годится для использования с ORM, где запрос часто составляется кусками.

> > И вроде специальный плейсхолдер, для добавления имени таблицы (в сложных JOIN-запросах например) перед именами полей.

> не надо делать сложных джойнов, пожалейте базу %-)

Ну мало ли, вдруг понадобится.
> потом по коду бегать и писать intval()/strval()

это зачем? о0

> Но ведь бывают список строк, а бывает например список идентификаторов (например, список полей для SELECT). Тут надо 2 плейсхолдера.

implode( ',', $ids )

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

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

> Насчет «на части» — да, здравая мысль, но я пока с такими огромными запросами не сталкивался.

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

> Вот, нашел у себя еще один плейсхолдер, «рекурсивный», ?(), он берет переданный массив, содержащий кусок запроса с плейсхолдерами и параметры, интерпретирует его и вставляет полученный результат :) Годится для использования с ORM, где запрос часто составляется кусками.

идиты! 0_0" рекурсия — зло! если разрывать запрос на части, то теряется его связность и повышается риск сборки его в неправильном порядке и геморроя с отладкой.

> Ну мало ли, вдруг понадобится.

всё-равно не понятно, чем тут плейсхолдеры помогут
>>> Но ведь бывают список строк, а бывает например список идентификаторов (например, список полей для SELECT). Тут надо 2 плейсхолдера.

> implode( ',', $ids )

Фи, индусокод. Зачем тогда вообще плейсхолдеры делать. Не говоря о том, что вы забыли про mysql_real_escape_string, а?

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

Тем не менее, бывают случаи, когда запрос собирается из кусков, например добавляются условия или сортировка. или еще что-нибудь.
> Зачем тогда вообще плейсхолдеры делать.

у меня нет никаких плейсхолдеров.

> Не говоря о том, что вы забыли про mysql_real_escape_string, а?

нет, просто не надо данные от пользователя использовать в качестве идентификаторов. но даже если очень хочется — этапе ремапинга имён весь мусор отфильтруется.

> Тем не менее, бывают случаи, когда запрос собирается из кусков, например добавляются условия или сортировка. или еще что-нибудь.

в этом случае динамически собирается правильная структура, а потом она уже сериализуется с экранизацией параметров
> нет, просто не надо данные от пользователя использовать в качестве идентификаторов. но даже если очень хочется — этапе ремапинга имён весь мусор отфильтруется.

> в этом случае динамически собирается правильная структура, а потом она уже сериализуется с экранизацией параметров

Ну вот :) У нас разные подходы. Мне просто удобнее отдать данные, как есть, а код работы с БД пусть сам их там экранирует внутри, я *принципиально* не хочу вручную вызывать escape-функции и проверять, опасные это значения или нет. Я просто ставлю знак вопроса, и знаю, что туда будут подставлены данные в правильном виде.
позиционные плейсхолдеры при больших количествах сильно страдают в плане наглядности.
Не сталкивался с такими мега-запросами :). Впрочем, куча переменных, кавычек и запятых тоже будет не очень смотреться.
я же показывал — всё замечательно смотрится. а в случае выноса подставляемых значений отдельно — при чтении приходится безконца скакать глазами туда-сюда, чтобы определить что куда пойдёт. от 5 параметров — это не такие уж и мегазапросы
Здесь кусок кода, предложенный Джоном Ли:
$username = $_POST['username'];
$password = $_POST['password'];
if (!get_magic_quotes_gpc()) {
$username = addslashes($username);
$password = addslashes($password);
}

Это неправильный совет.

1) Addslashes некорректно работает с мультибайтовыми кодировками; мало того, что оно испортит данные и в случае, например, с японской кодировкой Shift-JIS, вполне возможно проведение sql-инъекции для вышеприведенного кода. Magic quotes по той же причине лучше всего отключать, если это невозможно (недоступны htaccess/php.ini) — то использовать stripslashes и задуматься о смене хостинга.

2) На самом деле, поведение addslashes еще и зависит от настройки magic_quotes_sybase (по сути, magic_quotes включает автоприменение addslashes к входным get/post/cookie данным), что может оказаться отдельным сюрпризом.

3) Для каждой СУБД есть своя функция экранирования, реализованная в соответствующией клиентской библиотеке. Например, для mysql это mysql_real_escape_string.

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

Кстати, заметил, что по безопасности сайтов очень редко пишут на Хабре, а ведь тема очень интересная, если учесть, сколько тут разработчиков!..
bind variables! да большинство программистов вообще не слышали про это.
А если учесть новомодное декларативное программирование, где большинство запросов в базу лежит в исходном коде вот так:
select {field_list} from {table} where {where_Clause}

то блин мы имеем то, что мы имеем. :(
Бывают проблемы, которые не так легко фиксаются с помощью bind.

Во-первых, если давно написанный кривой интерфейс подразумевает передачу строки с SQL-запросом в качестве параметра или возвращаемого значения. Типа такого (Java):

interface SQLDatabaseDriver {
  public String getNamesQuery();
  public String getValueByNameQuery(String value);
};

class SomeSpecificDatabaseDriver implements SQLDatabaseDriver {
  public String getNamesQuery() { return "SELECT name FROM tbl";}
  public String getValueByNameQuery(String name) { 
    return "SELECT value FROM tbl WHERE name='"+name+"'";
  }
};

Пусть такой интерфейс использует некий класс, который наполняет коллекцию данными из произвольной SQL-базы. Интерфейс мог быть реализован 20 раз для разных сторонних баз и вдобавок опубликован с целью, чтобы сторонние разработчики писали плагины (и они их написали!). А тут вдруг задумались об SQL-инъекциях. Что делать? Оборвать руки проектировщику интерфейса, правильно. Только проблему это не решит.

Второй проблемный случай, когда мы генерируем запрос с непостоянным количеством подставляемых полей. Нам это требовалось при массовой вставке данных в базу. Делать prepare(«INSERT blahblah VALUES(?,?,?)») и потом тысячу раз execute(var1, var2, var3) существенно медленнее, чем загнать всё в один длинный INSERT. Особенно разница ощутима, когда база по сети и далеко. Мы сделали классик на перле, который инкапсулировал множественную вставку в базу, используя функцию $dbh->quote(), которая умеет экранировать спецсимволы в запросах и делает это правильно для любой СУБД, для который установлен перловый драйвер. Когда набиралась максимально допустимая длина запроса, проталкивали в сокет и начинали формировать новый запрос. Прекрасно работало и с MySQL, и с Oracle, с UTF-8 тоже. К сожалению, я не нашёл аналога $dbh->quote() в Java (плохо искал?). Ручная реализация экранирования кажется ненадёжной, так как некоторые СУБД требуют удвоение кавычки, а некоторые — добавление слэша перед ней.
Как же я все-таки не люблю, когда мешают в одном файле разные языки. Предпочитаю sql хранить отдельно. Идеальным считаю вообще использование хранимых процедур и функций. Особенно это хорошо, когда есть отдельный ДБА или ДБ-программер.
UFO just landed and posted this here
У меня не нашёл SQL injection в тестовом примере.

Значение в форме такое:




<form method="post">
	<input type="text" name="q" />
</form>

<?php
if (isset($_POST['q']) and !empty($_POST['q'])) {
	mysql_connect('localhost', 'root', '');
	mysql_select_db('ruzievkz');
	
	$sql = 'SELECT * FROM route WHERE route_id='.$_POST['q'];
	
	echo '<pre>';
	print_r($sql);
	echo '
';

$result = mysql_query($sql);

echo '
';
	var_dump(mysql_fetch_object($result));
	echo '
';
}

echo '
';
var_dump($_POST);
echo '
';
?>
Пардон, случайно нажал Ctrl+Enter и отправилась каша. =\

Плагин вот чего не обнаружил:
Тест собственно такой: 9 OR route_id = 1 AND route_id = 2 OR route_id = 3
Форма и обрабочик: pastebin.com/h4rKFxcs
Хотя, конечно, тут уже вряд-ли тестировщик поможет.
Использование рекурсивной проверки всех данных ( включая ENV, _SERVER и прочите ) в начале выполнения программы спасает от инъекций. Лет пять назад писал модуль, который банальным инклудом вставлялся в любой конфиг.пхп. Вроде до сих пор где-то работает :)
если не пускать мусульман на самолёт, то его не угонят
А потом на e-mail и в тикет-системы приходят фамилии Д\\\\\\\\\\\\\\\\\\\\\\\\\\\'артаньян… :)
>>> Как вы видите, есть много решений (и прежде всего все простые) которые вы можете предпринять

Не можем, ибо это не решения, а ужас самый настоящий. От одного addslashes() аж передёрнуло.

Для избежания инъекций надо всего лишь понять что не данные пользователя мы вставляем в запрос, а «то нечто» что сервер воспримет за эти данные (а не за часть запроса)

То есть еогда мы пишем запрос к базе данных:
SELECT * FROM `table` WHERE `word`='какое-то слово';
Cамо «какое-то слово» не опасно. Опасно будет если мы оправим это «слово» в запрос! Надо отправлять не «это слово» а то что SQL сервер примет за это слово. Можно даже так:
SELECT * FROM `table` WHERE `word`=0x4841434b454420425920564c4144534f4e;
Данный пример упрощённый, в реальности mysql_real_escape_string справляется с этим вполне, более того есть не мало готовых классов для работы с БД которые сами составляют запросы (замечу в 99% web скриптов запросы более чем элементарны, в противном случае надо менять разработчика)
Sign up to leave a comment.

Articles