Pull to refresh

Comments 115

Техническое определение этого — эскапизм

Это переводится на русский как «экранирование»
Эскапизм — термин из другой области)). Это бегство от проблем общества) Чему напрямую был посвящен фильм «Таинственный лес» 2004
Ктулху сожрет Вашу душу за это!
Каждый раз, натыкаясь на эту ссылку не могу удержаться и прочитываю весь коммент целиком.
Но при этом я продолжаю парсить HTML регулярками.
А что если необходимо подставить в запрос динамически имена полей (например после ORDER BY) или что-то другое? На эту тему есть не плохая статья: phpfaq.ru/slashes
В этом случае я предпочитаю явно прописывать то что хочу получить.
В случае ограниченного множества значений можно писать как-то так (пример синтетический):

$orderBy = isset($_GET['by'])? $_GET['by']: false;
switch ($orderBy) {
case 'name':
$queryBuilder->orderBy('name');
//…
}
Совершенно верно. С этим никто не спорит.
Просто это пример того, что «забыть как о кошмарном сне» не получится.

PDO и prepared statements — не панацея. Пример немного притянут за уши — но всё же:
Вот есть у меня массив строк, которые я хочу поместить в IN() в запросе. «В лоб» добавить через prepared statement не получится — придётся… снова как встарь химичить с динамическим составлением запроса, добавляя в него переменные! И тут уже каждый начнёт действовать в меру собственных представлений. У кого-то получится составить строку из плейсхолдеров, а кто-то пойдет другим путем, и — ошибётся…
И таких примеров множество.

Поэтому не нужно прятать голову в песок и гордо заявлять, что «у нас на всё есть способ». Способ этот при ближайшем рассмотрении оказывается довольно ограниченным.

Сама по себе идея подготовленных выражений, впрочем, как раз и является той самой серебряной пулей. Но только не в той чрезвычайно ограниченной реализации, которую предлагает PDO.
Вот моя заляпуха для PDO, которая плейсхолдеры множит для IN и для пакетной вставки, вдруг будет интересно: pastebin.com/BYwSW2GZ
Вот не нравится мне, что $valueKey пишется прямиком в запрос, безо всякой обработки.
Инъекцию там провести, скорее всего, не удастся — будет ошибка «плейсхолдер не найден» при исполнении — но всё равно, сама возможность вписать в запрос произвольный код вызывает очень неуютные ощущения. Я бы не стал экономить на переменной — счетчике.

А вот почему arrayPrepare() не делает execute() — я не понял. Если бы эта функция не делала привязки — тогда понятно: можно было бы сделать несколько вызовов одного и того же prepared запроса. А так она все равно ведь выполняет один запрос за раз — зачем тогда делать отдельный вызов execute()?

Я бы вместо
foreach ($params as $key => $value) {
     $statement->bindValue($key, $value);
}

написал
$statement->execute($params);

Вот не нравится мне, что $valueKey пишется прямиком в запрос, безо всякой обработки.
Инъекцию там провести, скорее всего, не удастся — будет ошибка «плейсхолдер не найден» при исполнении — но всё равно, сама возможность вписать в запрос произвольный код вызывает очень неуютные ощущения. Я бы не стал экономить на переменной — счетчике.

Да, именно такая ошибка и происходит, если написать туда гадости всякие.

А вот почему arrayPrepare() не делает execute() — я не понял. Если бы эта функция не делала привязки — тогда понятно: можно было бы сделать несколько вызовов одного и того же prepared запроса. А так она все равно ведь выполняет один запрос за раз — зачем тогда делать отдельный вызов execute()?

Возможно так и нужно сделать — ведь все равно каждый раз разное количество значений в массиве может быть. Наверное, когда я так делал — хотел единообразия со стандартной цепочкой prepare-execute
Как уже замечено в статье, экранирование актуально не только для запросов в базу данных.
Не нужно считать свой сайт априори защищённым, только если в движке вместо mysql_connect используется волшебное new PDO().
У меня в дополнение к PDO в запросах HTML генерится через DOM. Немного тормозно, ну да нагрузки нет почти :)
Имеющиеся в виду «родные» подготовленные выражения не работают, к примеру, в полнотекстовом поиске. Или, как правильно замечают ниже, для идентификаторов. Так что нужно не прятать голову в песок, «нам это не нужно!», а понимать, как работает каждый инструмент и применять его по назначению. Приходите на Девконф, я там как раз буду об этом рассказывать
Выше ерунда написана. Подвел меня старик Билл Карвин. Наврал про полнотекстовый поиск, а я и поверил.
Сейчас сунулся проверять — всё работает.
Что, впрочем, не отменяет других случаев, когда «родные» prepared statements не работают.
mysql_real_escape_string не защищает от sql-инъекций, использовать надо только плейсхолдеры.
$id = '0; drop database some_db;';
print mysql_real_escape_string($id);

0; drop database some_db;
> Если мы применим экранирование к пользовательским данным до объединения их с запросом, то проблема решена.
Представим что есть запрос
select * from table where id = $id

Если в $id находится 1 OR 1=1, то получим запрос
select * from table where id = 1 or 1=1

mysql_real_escape_string тут не поможет, т.к это просто экранирует специальные символы, которых может и не быть.
mysql_real_escape_string, как и любую другую функцию надо использовать с умом, если память не изменяет, то она рассчитана на строковые значения, т.е. что-то вроде
"select * from table where id = '".mysql_real_escape_string($username)."'"
а для цифровых можно использоваться простенькое
"select * from table where id = ".(int)$id

ну а вообще pure php как то не в моде нынче и всё это скрыто слоями фреймворков и прочих оберток, где с подобным не заморачиваются
UFO just landed and posted this here
Можно конечно, но я как то привык что цифры не надо в кавычки заключать.
UFO just landed and posted this here
вообще-то не любые. LIMIT '10' выдаст ошибку, например
UFO just landed and posted this here
Название функции mysql_real_escape_string как-бы намекает.
Судя по количеству минусов, мой доклад на Девконф оказывается куда актуальнее, чем я предполагал. Впрочем, учитывая, что в мануале до сих пор написана ерунда, говорящая практически обратное, минусующих в чем-то можно понять — заблуждение их добросовестное.
UFO just landed and posted this here
То есть вы хотите сказать, что читатели не смогли мысленно подставить приведенную переменную в запрос, и поэтому стали минусовать? Что-то мне сомнительно, если честно. Не такая уж непосильная операция.

Каким образом относятся обратные кавычки к дискуссии об экранировании строк — я тоже не очень понял.
UFO just landed and posted this here
Ну, более корректной формулировкой будет «Не предназначена для защиты от инъекций».
Но и исходная формулировка имеет право на жизнь, как зеркальная по отношению к утверждению «mysql_real_escape_string защищает от sql-инъекций» — которое очевидно является ложным, и — понимаемое буквально — приводит-таки к инъекциям!

Предназначением этой несчастной функции никогда не была защита от каких бы то ни было инъекций, а всего-навсего форматирование строк. О чем, собственно, и говорит нам название функции — таких слов, как «защита» или «инъекция» в нем нет.
Причем только часть необходимого форматирования.

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

Если бы mysql_real_escape_string защищала от инъекций, то тогда можно было бы и числа, и идентификаторы ей обрабатывать. Но, разумеется, это не так. Защиту обеспечивает корректное форматирование всех элементов запроса, в соответствии с их типами. И говорить, что функция, которая обеспечивает лишь частичное форматирование для литерала всего лишь одного типа, предназначена для защиты от чего бы-то ни было — это противоречить логике и фактам.

Именно поэтому фраза из мануала «Если не пользоваться этой функцией, то запрос становится уязвимым для взлома с помощью SQL-инъекций.» является несусветной глупостью.
Во-первых, далеко не факт, что становится уязвимым.
Во-вторых — что важнее — она создает у читателя убеждение, что если пользоваться этой функцией, то запрос становится неуязвимым. А lightsgoout ещё в самом начале наглядно показал, что это не так.
Что характерно, в описание mysqli_real_escape_string() эту чушь тащить не стали — описание этой функции предельно корректно и не упоминает об инъекциях вообще.

Неуязвимым для инъекции является корректно отформатированный строковый литерал.
Поэтому, если бы в мануале было написано, что
— обработанный этой функцией блок данных в обязательном порядке должен быть заключен в кавычки
— и что если мы не залючаем данные в кавычки, то применение этой функции бессмысленно, и что для литералов всех остальных типов надо применять другие варианты форматирования (с указанием — какие конкретно) — только только в этом случае можно было бы упоминать об инъекциях.
UFO just landed and posted this here
> «Форматирование» это средство экранирования, а не самоцель!

На самом деле конечно же нет. Цель передать запрос базе данных, для этого его нужно правильно отформатировать (привести в соответствие с заданным форматом). А экранирование — это как раз одно из средств. В статье перечислены другие: вырезание символов и запрет пользовательского ввода с этими символами.
UFO just landed and posted this here
Нет-нет, цели у нас на самом деле одинаковые!
Смотрите:
Во-первых, «защита» не может быть основной целью, поскольку есть много случаев, когда никакой и угрозы-то нету — но форматировать запрос нам надо всё равно. Допустим, данные приходят не от хакера, а от админа, из онлайн редактора. Никаких инъекций в них нет и быть не может. Но форматировать-то их всё равно надо. Иначе запрос тупо вылетит с ошибкой на первом же апострофе.
То есть — согласитесь — дело, всё-таки, не в защите?

Во-вторых, есть много случаев, когда «замена спецсимволов на их безопасные аналоги» не помогает от слова «совсем». Один из примеров приведен в корневом комментарии этой ветки.
То есть, сама по себе такая замена не может являться целью — она будет работать только в определённом контексте. И вот определение этого контекста, уместности применения тех или иных «замен» — это и есть то самое форматирование.

Беда в том, что SQL запрос состоит не из одних только строк.
И по этой причине ограничиться только экранированием строковых спецсимволов, как это предлагает автор статьи — увы, не получится.

Автор ничего подобного не предлагает. Он говорит о необходимости экранирования, но явно упоминает, что им одним нельзя ограничиваться, что есть валидация, санация и подготовленные выражения. Но в том конкретном примере достаточно одного экранирования, если нет каких-то особых семантических требований на уровне приложения.
Не-не.
«Валидация» [пользовательского ввода] не имеет к составлению запросов вообще никакого отношения.
«Санация» — бессмыслица и вред.
Подготовленные выражения — это способ применения корректного форматирования, но не оно само.

«Но в том конкретном примере» — это очень шаткая конструкция. По сути, она домысливается. Но статья, которая не оговаривает принципиально важных вещей, заставляя домысливать читателя самого, не может считаться корректной.
Без домысливания же это утверждение может привести к очень плачевным результатам.

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

А по моему не упускает он. «Помещая текстовые значения в SQL, они должны быть экранированы по правилам SQL.» Просто нужно иметь в виду, что правила SQL заключаются не только в mysql_real_escape_string/ По-моему это очевидно.
О валидации.

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

То, что мы не стали выполнять SQL запрос при невалидном вводе — это вопрос логики приложения. И там вместо SQL может быть что угодно — отправка на почту, запись в файл, RPC-call. Это логика приложения, к запросам отношения не имеющая.

Но если мы до исполнения запроса все-таки дошли, то он должен быть корректным.
И корректность эта строится на своих, специфических для SQL правилах. Вообще никак не зависящих от логики приложения.

Не надо мешать в кучу валидацию приложения и форматирование запросов. Вы понадеетесь на валидацию, потом правила валидации у вас поменяются, и инъекция пролезет.

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

При этом корректное форматирование запросов нам нужно всё равно.
Но если оно обязательно, то любые валидации пользовательского ввода становятся иррелевантны. Их можно делать, можно не делать — к составлению запроса они уже иметь отношения никакого не будут. Это логика приложения.
Есть исключения, типа админок к БД, где логика приложения и правила SQL очень сильно пересекаются или просто минимальный синтаксический разбор запроса или его частей проводим в приложении в целях снижения затрат на IPC и нагрузки на СУБД, но в целом убедили насчет валидации. Но включаете ли вы в форматирование санацию типа «WHERE id = ». (int) $id"?
Выше я говорил о валидации и санации «входящих данных».
Если же говорить о форматировании, то да — для чисел надо выбирать или то или другое.
Я для себя выбрал валидацию. Если для численного плейсхолдера приходят неподходящие данные, то кидается исключение.
UFO just landed and posted this here
UFO just landed and posted this here
Вы зачем-то упорно хотите спорить с фактами и увеличивать себе количество работы.
Обсуждаемая функция не предназначена для защиты, она предназначена для форматирования — о чем говорит её название. Если в строке встречается спецсимвол, его надо экранировать, вне зависимости от наличия или отсутствия каких бы-то ни было инъекций — это просто требование синтаксиса SQL.
Нигде в документации по mysql не написано, что mysql_real_escape_string() служит для какой бы то ни было защиты.
This function is used to create a legal SQL string that you can use in an SQL statement.
— вот что там написано.
Заметьте — в отличие от утверждения «mysql_real_escape_string() служит для защиты от инъекций», такая формулировка не нуждается в многословных толкованиях и многочисленных исключениях. Она просто работает. Будучи программистом, я предпочитаю конкретику философии. А толкования и софистику оставлю гуманитариям.
UFO just landed and posted this here
Всё наоборот. Экранирование — это частный случай форматирования.
А цель — синтаксически корректный запрос.
Для её достижения мы должны применять разные варианты форматирования для различных элементов запроса.
При этом защита от инъекций является необязательным побочным эффектом корректного форматирования.

Я писал багрепорт. мне отказали под предлогом того, что расширение все равно не рекомендовано к использованию, целиком.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
«Вы можете предложить другую функцию, кроме сложения, чтобы выполняла все 4 арифметических действия?» — вот так звучит этот вопрос.

Несчастная mysql_real_escape_string() — всего лишь часть необходимого форматирования всего лишь одного типа SQL литералов. А никак не средство защиты от каких бы-то ни было инъекций.
Применять её для чисел бесполезно.
Применять её для идентификаторов бесполезно.
Применять её для ключевых слов бесполезно.

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

С очень большой натяжкой таким средством можно назвать функцию PDO::quote, которая при неправильном применении вызовет ошибку, но зато хотя бы не допустит инъекцию. Поскольку выполняет полное форматирование, а не частичное — заключает данные в кавычки и искейпит, грубо говоря, те же кавычки внутри.
Впрочем, ушлые похаписты умудряются и этой функции рога обломать. Очень жалею, что после устроенного мной скандала со страницы этой функции в мануале удалили комментарий, который советовал отрывать(!!!) добавленные кавычки, если обрабатываемый литерал — не строка, а идентификатор. Это была просто идеальная иллюстрация к заблуждению «искейпинг защищает от инъекций».

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

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

То есть валидация все-таки нужна :)
Да, и я должен еще раз сказать вам спасибо за въедливость, благодаря которой я оттачиваю формулировки.

Дело в том, что у меня пунктик. Был такой старый топик здесь на хабре про авторизацию, где валидация пользовательского ввода подменяла собой корректное форматирование. И тогда у меня не нашлось, что ответить, но я чувствовал, что что-то здесь неправильно.
А потом, сильно позже, в одном коде я столкнулся с очень похожим подходом, но — в какой-то момент правила валидации поменялись, допуская куда больший диапазон символов, но поправить код обращения к базе никто не удосужился.
А поскольку такое случается довольно часто, то у меня сложился такой паттерн в голове — я делаю стойку на слово «валидация [пользовательского ввода]».

Когда же мы говорим о составлении запроса, то да: для строк и идентификаторов получается форматирование, а для чиcел и ключевых слов — валидация. Но ещё раз подчеркну — валидация эта не с точки зрения приложения, как её часто понимают, а с точки зрения синтаксиса SQL.

Отличие же в том, где происходит процесс валидации. В случае с SQL программист вообще его не видит. Он отдаёт переменную в запрос, а дальше не его ума дело. Если же в переменной окажется неподходящее значение — система выкинет исключение.
Пожалуйста. Лишь бы мир менялся к лучшему, вы его вроде активно меняете :)

Есть уточнение — идентификаторам нужно как форматирование (экранирование) так и валидация — идентификатор не может быть произвольной строкой в общем случае (в мускуле точно не может, с другими СУБД так глубоко не рыл).

Ну и да, зря я не уточнил, что имеется в виду именно валидация на соотвествие правилам SQL, а не бизнес-логики. Но вот насчет не видит — не согласен. Не видит — если не хочет видеть. Я вот привык код создания запроса выносить в отдельные функции/методы, которые вызываются после валидации на соотвествие бизнес-правилам (условно — проверяю соответствует ли $_GET[ id] регулярке /\d+/), на несоотвествие которым пользователю выводится сообщение об ошибке), но и в этих функциях делаю проверку на соответствие уже правилам SQL (типа if ($id != (int)$id) — формально может не полностью валидирует, но но хоть как-то — которая бросает исключение, то есть пользователь получает ошибку 500 в общем случае). Часто меня называют перестраховщиком, но я аргументирую тем, что это разные валидации, на них разная реакция и, главное, у них разные функции — первая защищает от пользовательского ввода, а вторая от коллег или даже меня самого, если вдруг в каком-то другом месте приложения решу её использовать повторно и туда как-то просочится строка, к числу по правилам SQL не приводящаяся. Защита от смены целых идентификаторов на GUID — побочный эффект :)
Я с подходом как раз согласен, но эти проверки должен делать уже драйвер БД, которому мы отдали запрос и переменные для подстановки. Саму кухню, как оно там проверяется, программист не должен видеть — ему надо только указать, в каком контексте будет использоваться переданная переменная.

Если же отдавать это программисту — он может забыть или перепутать.
Далеко не всегда есть у программистов возможность (или желание :) использовать возможность подстановки драйвером. У драйвера её тупо может не быть.
Ну, это грех такое говорить про программиста. Если у него чего-то нету, то он это что-то может создать!
Даже для всеми порицаемой mysql поддержка плейсхолдеров уровня PDO пишется за 5 минут:

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

Другое дело, что уровень PDO сам по себе недостаточен — но это уже другой вопрос.

Я всё-таки считаю, что обеспечение гарантированной защиты и значительное сокращение кода — это такие преимущества, которыми нельзя пренебрегать. И единственным препятствием тут может являться только отсутствие желания. И вот как раз решение этой проблемы потребует самых больших усилий. Но оно того стоит.
Но тогда не катит «не видит» :)
В смысле?
Я имею в виду код приложения. Ведь приведенная выше функция лежит где-то далеко в библиотеках, и в повседневной работе программист её кода не видит.

Я к тому, что код типа «if ($id != (int)$id)» не должен появляться рядом с составлением запроса — он должен быть убран с глаз долой.
(кстати, по-моему, он не будет работать, как ожидается, а наоборот — все пропустит. is_numeric или ctype_digit предпочту я)
Если в этом плане, то да, не видит. А вот с «не должен» не согласен. Сравнить с вашим примером — у вас будет одно исключение на все ошибки, а у меня конкретные.

Угу, вот что значит привычка к IDE к сниппетам — $id != (string)(int)$id.

Ну, это же упрощённый пример, имитирующий поведение PDO.
А правильный вариант будет как раз кидать разные ошибки, ещё на этапе парсинга/валидации.

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

Именно поэтому плейсхолдер и обязан быть типизованным — чтобы указывать валидатору контекст. Чтобы программисту не приходилось самому заниматься этой тягомотиной, с риском пропустить что-то или тупо залениться.

Этим мы убиваем сразу двух жирных зайцев:

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

в итоге код
$id = "Вася";
$db->query("DELETE FROM t WHERE id=?i", $id);

вполне себе выкинет исключение о недопустимом типе — всё как заказывали!

Вместо того, чтобы объяснять это в 100-й раз, лучше бы сослался на свой замечательный пост habrahabr.ru/post/165069/, где всё это давно разжёвано =)
Каждый не понимает по-своему.
А мне интересно, что именно разные люди не понимают — это позволяет мне лучше формулировать объяснения…
addslashes в принципе делает то же самое, правда некоторые нюансы не учитывает, а вообще универсальной функции защиты нет, потому что ни приложение, ни СУБД не могут знать что является атакой, а что нормальной функциональностью.

Вы используете mysql_real_escapel_string для защиты от инъекций лишь потому что понимаете как с помощью её побочного эффекта защититься от того, что вы считаете инъекцией в ваших конкретных сценариях. Надеюсь, что сознательно, а не по шаблону. В других сценариях нужны будут другие средства защиты, в третьих нужно будет использовать совместно несколько средств, в третьих — вообще ничего не использовать. Но назначение конкретно mysql_real_escapel_string — не допускать ошибок типа SELECT phone_number FROM users WHERE name = 'Mc'Donalds' , а SELECT phone_number FROM users WHERE name = 'Joe'; DROP TABLE users; --' для неё ничем не отличается, она их обработает абсолютно одинаково, а не потому что там есть DROP или ещё какой признак инъекций. Если бы была функция защита от инъекций, то она пропустила бы первую строку без изменений (инъекции нет, а корректировка синтаксиса в её задачи не входит), а на вторую бы заругалась (или изменила) — инъекция есть (если она распознает такой тип инъекций), пускай и синтаксис корректен.
UFO just landed and posted this here
Если пользователь явно пытается «втюхать» вам какой-то скрипт, вы можете просто удалить его.

Редкий случай, когда задумываешься о том, что хотел удалять автор — скрипт или пользователя, и понимаешь, что оба варианта верные :)
Это очень плохой комикс. Эта добрая женщина советует «дезинфицировать» ввод, делать его «безопасным». А это, на самом деле, невозможно и не нужно. Вместо этого нужно корректно форматировать элементы запроса.
Впрочем, полный свод правил форматирования будет посложнее, чем то, что обычно практикуется в качестве «защиты» — «все экранируй» или «все через prepared statement».
Вообще странно. С одной стороны, deceze один из немногих адекватных пхпшников на СО. И основная идея статьи очень здравая (и реально недоступная для нубов!) — что запрос (или HTML) — это просто текст. Просишь человека показать итоговый запрос, или сгенеренный код формы — упорно показывает пхп код, и в упор не понимает, что результат этого кода — это текст, который и надо показать.
С другой — аргументация хромает. И дело не в устаревших функциях — они-то, как раз, просто для иллюстрации — а в слишком примитивном толковании текста, о котором он пишет. Он у него однородный, Хотя на самом деле это не так.
Упс! пишу с зажигалки, промахнулся со ссылкой. Это было для sectus
М… не знаю. Каких-то особенных ответов последнее время от него не замечал: ). И потом. Эта статья могла быть написана очень давно.
В давние времена, когда я писал на Parser 3, мне очень нравился его механизм чистых/грязных данных (taint/untaint). Он заключался в том, что интерпретатор «знал» откуда пришли данные (от пользователя, из базы, из файла или исходного кода), и знал куда вы эти данные отправляете (в html, в xml, в sql, в пути файловой системы или вызовы системных команд) и в зависимости от сочетания этих факторов на лету делал преобразования строк. Несмотря на то, что иногда приходилось явно указывать, что вы хотите сделать с данными, в большинстве случаев, об экранировании можно было не задумываться.
Экранирования вообще не должно быть в application-коде. Если оно появляется, это «запах» плохой архитектуры. Касается не только SQL, но и HTML и всего остального.

То, что SQL-вставки в подавляющем большинстве языков представляются в виде строк, само по себе большой костыль. Это как если бы язык требовал, например, инструкцию if писать, как обычно, а инструкцию for — в виде строки. И ведь ровно из-за этого и берется проблема SQL-инъекций.

То же самое и в HTML: шаблонизатор в идеальном мире должен понимать, где вставляется значение, а где — разметка. Тогда и XSS не будет.
UFO just landed and posted this here
Я не о том говорил. В операторах if, for, while языка и т.д. нет никаких «injection» и «xss», хотя это тоже строки и «текст» в конечном итоге. Т.е. если бы в языке были средства для нормальной вставки SQL-выражений (минуя строковые литералы), SQL injections бы не возникало. Пример попытки это сделать — LINQ в C#: ru.wikipedia.org/wiki/Language_Integrated_Query
UFO just landed and posted this here
Как это нет?
Были бы за милую душу, если бы пользователи похапе собирали свои скрипты так же динамически, как делают это с SQL и HTML. for и while выручает только то, что они прописаны статикой.

Вся суть SQL инъекции состоит в том и только в том, что мы динамически генерируем программу, не соблюдая корректный синтаксис.
И ПХП скрипт ничем принципиально в этом плане от SQL запроса не отличается: начнем составлять его динамически — точно так же дыр насажаем.

При этом избежать экранирования в аппликейшен коде очень просто — и не мне автору dbsimple об этом говорить.
Сдается мне, вы путаете помидоры с пирамидами. SQL-запросы в виде строк в коде ЯП и сами операторы этого ЯП — «линейно независимые» сущности. Принципиально разные вещи. Вот если вы начнете eval-ить код на php, тогда да, они станут одного поля ягодами, да только не делает так никто в таком же коньексте, в котором это делают в sql. Попробуйте прочувствовать разницу.
UFO just landed and posted this here
Ну, я об этом и говорю.
Принципиальной разницы нет — есть только разница контекстов.
Программу на языке SQL мы [обычно] формируем динамически.
Программу на языке РНР мы [обычно] формируем статически.
Причем есть исключения и в ту и в другую сторону — модная ныне кодогенерация прекрасный тому пример. Или даже просто сохранение конфига в виде PHP скрипта — тоже весьма распространенное явление.

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

И РНР скрипт, и SQL запрос — это программа.
Программа корректно работает только тогда, когда при её написании соблюдается синтаксис.

А уж как мы её пишем, эту программу — руками ли, скриптом ли — это уже не принципиально.
Принципиален — корректный синтаксис.
Как только мы добились корректного синтаксиса — программа работает без сбоев.
Вот что между ними общего.

При этом, разумеется, синтаксическая корректность — это и есть основная цель. Программа с ошибками синтаксиса работать не будет, или будет, но неправильно.
На пхпклуб как-то раз пришел один кадр с вопросом, отчего у него не работает $s=fread("$f",7); Всегда же работало! Сто раз он по извечной нубской привычке старательно писал переменные в кавычках, и никаких проблем не было. Но на сто первый нашла коса на камень. Это отличный пример того, как язык наказал пользователя за вольное обращение с синтаксисом. Заметим, что писался скрипт руками, но это никак клиенту не помогло.

А защита от инъекций — это всего лишь побочный эффект. Побочный эффект соблюдения корректного синтаксиса.
Кстати — именно это, я считаю, и является основной мыслью обсуждаемой статьи. Аргументация хоть и подкачала, но сама мысль очень верная. «Форматируйте, не защищайте. Получите корректный синтаксис, а заодно и защиту от инъекций как бонус.» — я так её понимаю.
Корректный синтаксис не основная цель, а лишь необходимое условие. Реально используемые инъекции передают синтаксически корректный код, собственно тем они и опасны, что какой-то код выполнится, а не вывалится с ошибкой. Проблема в непонимании семантики, самый типичный пример типа ''WHERE name = '{$_GET['name']}'" — это проблема в непонимании (или в неосознанаии, или в невбитие в подсознании — затрудняюсь точно сформулировать, где у меня эта инфа в мозгу и врезультате какого когнитивного процесса туда попала) того факта, что литералы PHP и SQL (а также параметры HTTP) это совершенно ортогональные вещи, что для PHP эта строка — это тупо набор байт, не имеющий никакой семантики. И содержимое _$GET или $_POST для него такой же тупой набор байт. ЧТо SQL от строки ждет вполне определенного формата, но 'WHERE name = '{$_GET['name']}'" форматом для SQL не является. Это правило форматирования строки для PHP, которая при некоторых значениях превращается в корректную SQL команду. Или в набор команд. Или в синтаксически некорректную строку. PHP об этом знать ничего не может и дело программиста объяснить ему, что между кавычками должна быть подстрока для SQL семантически являющейся для SQL значением строкового литерала и ничем иным. Какой способ он выберет — его программистское дело, если он будет отражать нужную семантику. А если не будет, то либо он не знает семантику SQL, либо семантику PHP. Хотя может формально и знает, но в работе эти знания не использует — они осели у него в голове «мёртвым кодом».
Оппа! Интересная интерпретация. Она не приходила мне в голову. Но при этом — надо признать — буквально следует из моих же слов.
Спасибо за очень ценное замечание. Я подумаю над формулировкой, которая бы исключала такое толкование. Видимо, придется опускаться на уровень форматирования отдельных литералов, а не всего запроса в целом.

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

Остальная часть комментария, как я понимаю, посвящена обсуждаемой статье, и я с ним целиком и полностью согласен.
1. Хватит использовать mysql_.

2. Если что-то хочется поэкранировать в запросах, то нужно писать про использования переменных для метаданных (хотя тут лучше применять белые списки). Или взять какую-нибудь актуальную библиотеку, в которой нет подготовленных выражений. SQLite, например (Да и то, можно пользоваться SQLite через PDO, в котором эмулируются подготовленные выражения).
Хотелось бы посчитать людей, которые прочитали статью целиком и открыли для себя что-то новое. А ещё интересно сколько их них работают веб-разработчиками.
нового — ничего, но в качестве напоминания лишним не будет.
В качестве напоминания о чём? Старпёрам шрамы былых про«бов не дают забыть, а молодёжь инкапсулированна от этого всякими доктринами, форм-валидаторами, шаблонизаторами и прочим.
ну ладно, ладно. глупая ненужная статья. нло, удоли её немедленно.
На самом деле «инкапсуляция» эта — воображаемая.
Форм-валидаторы тут совсем не при чем.
Шаблонизатор, в отличие от составителя SQL запросов, защиты от дурака не имеет и инъекцию пропустить можно за милую душу — даже старательно экранируя все что движется.
Ну а «всякая доктрина», увы — не панацея на все случаи жизни, и в реальной жизни писать SQL все равно приходится, со всеми вытекающими.
Совсем не хочется в споры, но не могу удержаться ))
Форм-валидаторы — обязательная защита любой формы ввода как интерфейса для взаимодействия с пользователем. Если есть интерфейс — должна быть проверка введёных данных. Если год — только цифры, если имя — только буквы, если rich-editor — то знаем что в этом месте стоит ожидать подвоха.
Шаблонизатор — например twig.
Кто использует доктрину — пишет DQL (иначе понту использовать доктрину нет). Кто пишет строго SQL — юзает pdo адаптер, потому-что о том, что надо работать через него не написано только на самых ленивых ресурсах.
Форм-валидатор не обеспечивает никакой защиты текстов. Это логический валидатор, а не форматирующий. А защиту обеспечивает только корректное форматирование, а это совсем другая песня.

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

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

Вот буквально только что на Хабре появилась статья про «Иллюзию безопасности».
И вот это «мой твиг за меня все сделает безопасным» или «мой ПДО адаптер все проискейпит» и являются такой иллюзией, которая может оказаться весьма губительной.
Зачастую встречается однобокое понимание экранирования. Просто набор приемов где-то вычитанных или найденных на собственной шкуре. Но вот понимания «Помещая текстовые значения в (название технологии), они должны быть экранированы по правилам (название технологии).» (хотя и не очень корректная формулировка) нет. И пихают в html шаблоны javascript код экранированный по правилам html (ручками с помощью htnlentities или автоматом с умным шаблонизатором — не суть) Да ещё перед помещением в базу заэкранируют по правилам html по принципу «кашу маслом не испортишь».
2013 год… а вопросы SQL- и XSS-инъекций актуальны как никогда.
Хочу напомнить, что данная статья является переводом. Претензии по поводу актуальности/не нужности/легкости, по поводу того, что все это знают — к непосредственному автору данной статьи: kunststube.net/escapism/. Ко мне, пожалуйста, претензии по переводу ;)
Вот ведь подлецы какие, понапишут на иностранных языках всякий бред, а вам сиди, переводи, мучайся :)

P.S. Последние правки кстати 2012 года, даже там.
Простите великодушно, не на ту стрелочку нажал
На самом деле можно было и компиляцию сделать и переложение, и пересказ. Вы это принесли, вам и отдуваться. Кстати, когда была написана статья?
Все проблемы начинаются с того, что люди ошибочно считают, что xml-документ, SQL-запрос или что угодно — это текст.
Конечно, это все выглядит как текст, и даже все символы не выходят за пределы множества печатных символов, но на самом деле это, в первую очередь, данные.
Данные определенных типов, которые определяют их формат и семантику.
То есть, они ничем не отличаются от бинарных данных, за исключением того, что большинство байтов не выходят за рамки какой-нибудь кодировки.

И если понимать, что работая с этим «текстом» вы работаете с типизированными данными, то тогда становится очевидно, что прежде чем «всухую» в эти данные заносить пользовательский ввод, этот «ввод» необходимо обработать, чтобы получить данные («текст») соответствующего типа.
Более того, в идеальном мире и в языке со строгой типизацией вообще не должно быть возможности «склеивать» строковые (числовые и т.д.) данные с данными типа «выражение SQL». Типы просто не совпадают, операция «склейки» не определена. Тогда все встанет на свои места.
Не только к пользовательскому вводу стоит так относится, но и к данным из БД, файлов и т. п., даже если вы их микросекунды назад туда положили. Ну и ещё неплохо понимать, что многие эти данные не просто данные, а команды, которая машина будет непосредственно исполнять. То есть не просто «бинарник», а «екзешник», с которым, как известно, нужно обращаться особенно аккуратно.
Я предпочитаю хранить в базе HTML сущности «опасных» спецсимволов.
Да, это несколько увеличивает объем хранимых данных, но убивает сразу двух зайцев:
1) как при записи в БД, так и при выборке из нее снижает к нулю возможность выполнить SQL Injection.
2) не нужно заботиться о правильности экранирования данных при сравнении их с данными в БД при выборке определенной информации.

Например, человек в поле «О себе» написал: I love <!--"Mc'Donalds"-->. Ну вот захотел он так написать и все тут.
Перед записью в БД я экранирую эти данные в вот такую строку:

I love &lt;!--&quot;Mc&#39;Donalds&quot;--&gt;

И уже эту строку пишу в БД. А при отображении в браузер показываю ее «как есть».

Соответственно, если, к примеру, какой-то не очень умный человек захочет через поиск по сайту найти пользователя, у которого в профиле указано <!--"Mc'Donalds"-->, я перед поисковым SQL запросом в любом случае экранирую введенные данные в HTML сущности и сравниваю уже как сущности с сущностями.
Я вообще я всегда дико удивляюсь, когда вижу, как разработчики умышленно каверкают введенные данные. Например, введя в форму строку:

<script>alert('ololo');</script>

на выходе можно увидеть нечто вроде

<s cript>alert(\'ololo\');</s cript>

или вообще просто

alert(ololo);

Ну никакой фантазии, господа. Будто рубанком по полену! А ведь программирование — это тонкая и творческая профессия xDD
Умышленно коверкаете как раз вы :) Когда я сохраняю данные, то забочусь именно об их нековеркании (если коверкании или полный запрет всего, что не входит в какой-то белый список явно не задано в ТЗ или его аналогах) — ввел пользователь Joe'; DROP TABLE users; -- — именно это я и сохраняю. Ввёл <script>alert('ololo');</script> — и это сохраняю. Хотя бы для того, чтобы были доказательства попытки взлома, когда он начнет скандалить из-за бана :) И выведу в том же виде, в котором сохранил: вводил в браузере — получит эту строку в браузере.

А какой вид коверкания при выводе буду использовать — как частный случай htmlentities или striptags — завит, грубо говоря, от ТЗ. Если увижу, что в ТЗ этот момент опущен полностью, то выйду на связь с заказчиком и сообщу о потенциальной (sic!) опасности такого пропуска как для заказчика, так и владельца сервиса и/или его посетителей (три эти опасности коррелируют, но это не статическая зависимость, а вероятностная). Сообщу и предложу на мой взгляд разумные способы хотя бы минимизации такой опасности. Если он начнет настаивать на коверканиях, подразумевающих, утрируя, что пользователь получит на экране не то, что ввел, то это будет изменением ТЗ со всеми вытекающими.
1. Непонятно, какое отношение HTML сущности имеют к SQL injection.
2. Предположение о том, что HTML является единственно возможной средой вывода, является очень соблазнительным, но, к сожалению, неверным.

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

Опыт показывает, что с приобретением (порой весьма печального) опыта, разработчики отказываются от такого подхода.
По поводу 1:

Запрос типа

SELECT phone_number FROM users WHERE name = 'Joe'; DROP TABLE users; --'

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

SELECT phone_number FROM users WHERE name = 'Joe&#39;; DROP TABLE users; --'


По поводу 2 (редактирование) — тут я вообще не вижу проблемы :)
К примеру, в БД записана строка Pupkin &quot; Ололоша&quot;

В форму вы передаете эту строку «как есть». В TEXTAREA все HTML сущности автоматически преобразуются в оригинальные символы (но, естественно, не исполняются при этом). В исходном коде вы увидите примерно сделующее:

<textarea>Pupkin "Ололоша"</textarea>

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

При таком подходе «злой юзер» не сможет выполнить SQL инъекцию, и у него никак не получится встроить в HTML страницу «злой код», так как он просто не выполнится.

… но при этом портит данные так, что кроме как в HTML их никуда и вывести-то нельзя

В этом я с вами полностью согласен. Часто, работая с API того же YouTube приходится мучиться с принятыми данными, ибо ютьюб отдает данные с HTML сущностями (в частности — заголовки, ключи и описание видео). Но, все же, в 90% случаев информация в БД хранится именно для того, чтобы отобразить ее на HTML странице. В меньшей степени — для служебного пользования (авторизация, верификация и пр). И в еще меньшей степени — для API или прочих видов нестандартного отображения информации.

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

SQL инъекции включают в себя несколько больший спектр атак, чем описанная комиксе про мальчика Бобби.
Повторное редактирование не обязательно осуществляется через форму.
В это трудно поверить, но в БД, бывает, хранятся не только предназначенные для вывода в HTML данные.
В частности — иногда там хранится чистый HTML из онлайн редактора, который надо выводить, как есть.
А бывает наоборот — не имеющие ничего общего с HTML данные, которые будут испорчены таким вот «форматированием».
При этом «злой» код может быть совершенно невинным с точки зрения HTML, если выводится, к примеру, в яваскрипт.
Самое неприятное — этот список можно продолжать бесконечно, поскольку это всего лишь следствия изначально неверной концепции. Форматировать всё «чохом», без разбора, под одну гребенку — это изначально ущербная идея. Защиты толком не обеспечит, а данные испортит.

Форматирование должно быть адекватным. Специальным для каждого отдельного случая.
Мало того, что существуют различные получатели данных — сами эти получатели неоднородны! Не бывает «форматирования для HTML» или «для SQL»:
SQL запрос состоит из литералов нескольких типов, каждый из которых требует специального форматирования.
В браузер может попадать HTML, текст без разметки, яваскрипт. Каждый, опять же, требует собственного форматирования, неподходящего для других.

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

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

Хотя, на самом деле, есть варианты, когда нужно экранировать (или санировать) строку, помещаемую в БД (или иное хранилище), по правилам html или, скажем, json. Но только в случае кэширования и прочего «хайлоад». И лично я считаю хорошей практикой (нигде ни разу не встречал, кажется, вроде собственный велосипед) такие столбцы в БД (или аналогичные сущности в других хранилищах) явно именовать с указанием на формат. Скажем, не абстрактный столбец description, а description_html, descriptiont_json и т. п., явно указывающий в каком формате хранятся данные, а не абстрактный varchar. Это поможет, надеюсь, модифицирующему код (включая меня самого через какое-то время) избежать повторного экранирования в случае если я уже экранировал, и, главное, задуматься об экранировании если видит рядом два столбца description и description_html (сохранять оригинал по возможности считаю хорошим тоном, даже если все исходные данные уже есть в хранилище).
Sign up to leave a comment.

Articles