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

Комментарии 97

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

Проблема коммуникации гик-новичок действительно существует — я даже отметил её во вступлении.
Беда в том, что когда новички пишут для новичков — выходит ещё хуже.
В общем, если можете указать на конкретные неясности в статья — я был бы благодарен
Я вот сейчас занимаюсь серьёзным проектом и решил всерьёз заняться защитой от инъекций прежде, чем выходить в продакшн. Нагуглилось тысяча всего, но ваша статья оказалась самой исчерпывающей и самой полезной. При этом вполне понятной тому, кто пока не «знал этого и так») Так что большое спасибо!
Не гикам хотелось бы готовых решений. Или прямо ткнуть в них носом, т.к. по тексту они не очень понятны. Лучше пример класса для работы с БД, которым, например, вы пользуетесь (следующая статья не вышла еще?) и описать что с ним делать. Это особенно актуально для проектов, где БД используется на элементарном уровне, поэтому нет людей, которые в этом отлично бы шарили, а защита все-таки нужна…
На тот момент ещё не вышла, а вот только сегодня: habrahabr.ru/post/165069/
Спасибо за этот комментарий, он заставил меня активизировать работу по доведению класса до публичного состояния :)

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

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

Вся надежда остаётся на комментарии по существу, в ответ на которые я могу аргументированно переубеждать заблуждающихся.
SQL Injection по сути это проблема недостаточно грамотных или очень ленивых программистов. Правило тут одно — проверять переданные из формы данные на валидность.
К сожалению, вы неправы. В статье как раз подробно разбирается — почему.
Кавычки в имени (d'Artagnian) вполне валидны, или вы будете выдавать юзеру ошибку на имя типа strstr($name, "DROP TABLE students")? (простите, но у вас SQL injection в никнейме, выберите другой) Надо не валидировать, а экранировать, и всё будет ок.
Я думаю, что проблема тут скорее в терминологии, и elve имел в виду то же самое. Особенно, если учесть, что термин «экранировать» тоже не очень удачный.

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

Потом еще проблемы обучения сотрудников встают, SQL едионообразен и четок в структуре, а тут у каждого свой синтаксис.
Признайтесь, вы написали комментарий, не читая статью? :)
На самом деле, всё наоборот — код (прикладной) значительно упрощается.

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

Вот пример кода с плейсхолдерами

$data = $db->getAll(«SELECT * FROM news WHERE id IN(a:) AND theme=s:»,$ids,$theme);

Покажите, пожалуйста, как сделать его проще?

Совершенно такой же запрос будет на голом SQL, только в параметрах будут чищенные переменные. При этом он будет кушать меньше ресурсов, т.к. строка просто соберется используя функции языка, а не самописные функции на php для обработки конструкции.
Подождите-подождите.
Во-первых, что значит «чищеные»? От чего чищеные?
Во-вторых, не святым же духом они станут «чищеными»? То есть, какой-то код понадобится?

Вас не затруднит привести полный код составления запроса, вместе с «очисткой» переменных? А то заявление про «усложнение кода» выглядит несколько голословно, уж извините.

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

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

mysql_query(«SELECT * FROM news WHERE id IN (».clean_int_arr($_GET['ids']))." AND theme=".clean_str($_GET['theme']));
Почему то мне сейчас вспомнились времена, когда magic quotes ещё не были ересью DEPRECATED в PHP 5.3.0 и REMOVED в PHP 5.4.0.
А код для clean_int_arr и clean_str?
Вы уж будьте последовательным — объясните, почему прогонять данные через mysql_real_escape_string это катастрофическое заблуждение?
В качестве единственного средства защиты — это так и есть. Подробное объяснение, мне кажется, есть в статье, причём в нескольких местах. Но если коротко, то основная проблема состоит в том, что mysql_real_escape_string — это лишь часть необходимого форматирования, применимая только к строкам, при этом абсолютно бесполезная для любых других элементов запроса. Не говоря уже о том, что сама по себе эта функция к защите от инъекций не имеет никакого отношения. Она служит для форматирования строк.
Откуда информация о том, что PDO::ATTR_EMULATE_PREPARES по-умолчанию — true?
Ну, во-первых, из личного опыта, а во-вторых, исходя из принципа построения всех публичных фреймфорков, о котором говорил Расмус на конфе — максимальная перестраховка :) А режим эмуляции по определению более совместим.
во-первых, из личного опыта

Какой личный опыт подсказал Вам, что там true по-умолчанию?

во-вторых,… максимальная перестраховка

Максимальная перестраховка? Это отдает непониманием происходящих процессов. Сразу вспоминается анекдот про два jmp подряд, на случай, если первый не сработает.

Я так понял, что Вы не знаете, но предполагаете? Верно?
Ну, я бы не назвал это предположением. У меня есть опыт.
Возможно, он недостаточный/устаревший, но это именно практический опыт, а не предположение.

Если у вас есть опровержение, я с удовольствием его выслушаю и исправлю статью.
Как мне убедиться, что Вы, например, не обманываете меня? Как я могу проверить, что там true? Вы же как-то узнали?
Да, прошу прощения, я не написал.
Путём выполнения вот такого кода

try {
$dsn = 'mysql:dbname=test;host=127.0.0.1;charset=GBK';
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$sth = $dbh->prepare(«SELECT id FROM users WHERE name LIMIT ?,?»);
$sth->execute(array(«1»,«2»));
} catch (PDOException $e) {
echo $e->getMessage();
}

И прочтения сообщения об ошибке, в котором переданные параметры оказались подставлены на место плейсхолдеров.
Вот оно! Спасибо! Вам стоило привести этот пример в статье. Я на Хабре не раз натыкался на голословные утверждения, особенно касательно PDO, поэтому требую пруфов.

P.S. Вам стоит поработать над подачей материала — очень тяжело дается Ваша статья.
Хорошо было бы составить некий checklist, с которым стоило бы сверять каждую строчку кода. А то вроде бы и знаешь, а память дырявая — вот и пропустишь некоторые проверки.
Так чеклист же и состоит всего из двух пунктов? Плейсхолдеры для данных и белый список для всего остального.
Вы считаете, что этого недостаточно? А можете привести какую-то конкретную ситуацию, в которой
данные рекомендации окажутся неприменимыми или двусмысленными?
На конкретных примерах всегда лучше видно проблему.
Попытаюсь составить, насколько смогу. Там больше двух, точно.
Было бы очень интересно взглянуть.
Возможно, речь не о динамической сборке запроса?
Дело в том, что плейсхолдеры, как раз, и служат для сокращения чек-листа для этой операции. «Добавил через плейсхолдер — и забыл». Разве что тип надо указывать. Но для родных препаредов и это не нужно.
0. хватит шишек:
0.1. велосипедам — нет! используем известные библиотеки\фреймворки, где уже многое исправлено
0.2. своевременный апгрейд
0.3. логи и мониторинг подозрительной активности.

1. входные данные для каждого слоя:
1.1. слой контроллера (код): фильтрация данных из суперглобалов (всех)
1.1.1. инициализация данных, мусор может быть вредным (например, те же автоглобалы)
1.2. слой модели\данных (sql): используем prepared statements или иные placeholders, хотя бы экранизацию(escaping)
1.3. слой вида (html): используем escaping везде, где требуется вывод данных
1.4. слои не должны доверять друг другу и полагаться, что не придут вредные данные.
1.5. минимизация функциональности: делать что нужно и не больше (например, для отдачи файла НЕ надо его исполнять).

2. фильтрация данных:(xss+code\sql injection)
2.1. до максимально узкого значения. например, если должно быть число, пытаемся кастить в число.
2.2. до более широкого диапазона можно прогонять через регулярки. не забываем про длину.
2.3. если данные исчислимы (выбор из списка), то сопоставить реальным данным числовой идентификатор.
2.4. не перестараться, не портить данные «универсальными» чистками.

3. аутентичность:
3.1. минимальный срок жизни данных:
3.1.1. используем сессионные данные, для доступа используем трудноугадываемый идентификатор
3.1.2. для подтверждений: длинный ОДНОРАЗОВЫЙ ИД.
3.1.3. логин, повышение прав: регенерация ИД, подтверждение пароля (session hijacking)
3.1.4. формы и иные stateful запросы также имеют одноразовый ИД (csrf,request\form spoofing)
3.2. в зависимости от важности данных устанавливаем сложность проверки юзверя (двухфазный вход, генераторы паролей, заставляем сложные пароли)
3.3. права доступа (как ФС, так и системные)

4. client is evil:
4.1. ничего не храним на клиенте, даже в зашифрованном виде. максимум — session id в куках
4.2. валидация на клиенте бессмысленна, только для удобства пользователя
4.3. не полагаемся на данные об ОС\браузере\…
4.4. шифрование на клиенте бессмысленно, но необходимо (если вдруг что-то увели)
4.5. имена файлов, другие пароли и явки — ложь.

5. DoS предупреждение:
5.1. слишком длинные данные + размер загружаемых данных (картинки, например)
5.2. много неудачных попыток входа
5.3. много загрузок
5.4. много запросов, добавляющие данные (напр.комментарии)

6. О шифровании:
6.1. все пароли (в базе) — в виде хешей с солью, проверить на криптостойкость и коллизии.
6.2. для чувствительных данных — шифрование потока по HTTPS\SSL c коротким expiration
6.3. не храним данные в файлах, особенно — в открытом виде.
6.4. статика — отдельно от динамики, с соотв. правами доступа (либо читать, либо выполнять, либо писать)
6.4.1. желательно — статика очень отдельно от динамики (др. сервер, папка, за пределы корня) т.е. чтобы не пересекались

7. Лишнее:
7.1. ничего лишнего — например, никаких .svn, паролей, чеклистов, туду и вообще не жизненно важного для работы.
7.2. никаких инфо-заголовков — об используемом софте, версиях, путях, файлах, именах
7.3. никаких сообщений об ошибках, кроме подсказок юзверю, что он делает не так и что ему делать так.
7.4. источник должен быть виден, исключить возможность подделок, пример:
7.4.1. комментарии должны выглядеть как комментарии и не иначе
7.4.2. в заголовке всех окон ввода — четко имя сайта и функция, должно быть узнаваемо.
7.5. лучше использовать pretty urls — и для удобства, и сокрытия данных.
ну это сильно шире инъекций
однако нельзя рассматривать безопасность как-то отдельно по типам.
«безопасность системы определяется наиболее уязвимым местом» — не знаю, откуда цитата
Просто в контексте данной публикации речь идет именно о БД, хотя в общем контексте вы правы
Мне всегда не хватало такого конспекта. Как думаете, может, стоит опубликовать это отдельным постом?
Мне кажется — да, но рекомендовать не возьмусь. Уж очень непредсказуема реакция хабрасаообщества.
Кстати, хороший вариант — посмотреть сначала на оценки комментария!

В любом случае, я бы сначала поправил п 1.2 — «хотя бы escaping» — это решето.
по п.1.2: не всегда удается хорошо отфильтровать данные, особенно если их «непредсказуемость» высока — например, те же комментарии — можно фильтровать по [a-zA-Z0-9_+ ] — но это убьёт юникод потенциально «хорошие» символы.
Да и внутри всяческих базадрайверов чаще используется именно экранирование «плохих» символов, так что дырявость решета зависит только от реализации, а это к п.0.1.
А вообще 1.2. «хотя бы escaping» относилось именно ко всяким велосипедам и иже, где реализация «prepared statements или иные placeholders» отсутствует или неприменима, т.е. следует понимать так:
… а если все совсем плохо, то все равно ни в коем случае не оставлять данные «сырыми», минимум экранировать.
> не всегда удается хорошо отфильтровать данные, особенно если их «непредсказуемость» высока

Фильтрация к безопасности никакого отношения не имеет. Чтобы гарантированно безопасно вставить данные в базы — достаточно следовать правилу 1 из статьи (плейсхолдеры)
В базы — да. В общем случае — вариантов больше.
Вообще — давайте остановимся на этом, признав, что говорим о разных вещах, придерживаясь одного мнения :)
Ну не совсем одного мнения. Фильтрация — вопрос, касающийся предметной области приложения, а не особенностей работы с тем или иным хранилищем.
А вообще меня очень интересует — что можно еще в этот список добавить? Что я забыл? Какие моменты остались в нем непонятны?
Список достаточно сумбурен — писал из головы, что сразу вспомнил. Может, его для более удобоваримого чтения как-то можно реорганизовать?
прочитание столь долгого и нудного топика утомило меня…
Остаётся только добавить, что «правильно задать кодировку» можно только функциями...

А разве так не будет работать?

$db->query('set names utf8');
Нет, в контексте эскейпинга строк — не будет.
Я там как раз чуть ниже привёл под спойлером код, который это наглядно демонстрирует.

Запрос SET NAMES влияет только на сервер, а «правильные» способы — и на сервер, и на клиент.
Запустил Ваш код. В первых двух случаях ругается You have an error in your SQL syntax и mysql_query возвращает false. В последнем случае не ругается, возвращает пустой результат.

PHP version: 5.3.10-1ubuntu3.2
mysql: 5.5.24-0ubuntu0.12.04.1
«Функции mysql_* уже давно deprecated в PHP» — моя формулировка, даже стыдно немного. У вас получилась отличная статья. Ни одной глупости не увидел, что в наше время редкость для статей по этой теме. Пишите еще.
Вообще-то они действительно deprecated.
Во-первых, это только предложение из мейл-листа. В том же листе и про выход 6.0 писали.
Во-вторых, там как раз написано, что «Not adding E_DEPRECATED errors in 5.4». Текущая версия сейчас — 5.4. То есть, о таком статусе в настоящем времени говорить можно вряд ли.
В-третьих, самая простая проверка показывает нам разницу:
www.php.net/manual/en/function.mysql-list-dbs.php — стоит плашка «Deprecated» (ниже на странице).
www.php.net/manual/en/function.mysql-query.php — стоит плашка «Use of this extension is discouraged», но про Deprecated ничего нет.

А вот собственно и ответ. Т.е. сами разработчики хоронят прекрасную библиотеку.
С этим я согласен, но это никак и не противоречит сказанному мной.
Deprecated — это совершенно конкретный статус, который выдает конкретную ошибку при соответствующем уровне отображения.
Discouraged же — некий неформальный термин, который появился в документации по этому расширению буквально на днях.

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

С тем, что сами же разработчики и хоронят, я тоже не спорю — именно это я и написал. Собственно, мне они же сами и ответили в том смысле, что просто никто не хочет заниматься поддержкой этого расширения. Ну и по второй ссылке между строк читается «и так есть два расширения, зачем ещё с со старым возиться?». Что характерно, никакой другой причины там не написано. «Старое» — и всё. Как будто curl — новое.
а какой, кроме обратной совместимости, смысл в mysql?
есть pdo… есть mysqli…
в том же mysql как-то не замечено prepare и placeholder'ов…

давайте еще кучу «проверенного» функционала из 3 и 4 веток потащим?
Я понимаю, что по комментариям читать проще, но я же обо всём этом написал :)

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

Функционал prepare достаточно спорный.
Набор имеющихся placeholder-ов явно недостаточен — то есть, всё равно надо допиливать. Следовательно, парсер всё равно писать — а значит и основные плейсхолдеры реализовать не проблема, даже в mysql ext.

Если функционал проверенный, то давайте, конечно, притащим. Не вижу никакой проблемы в проверенном функционале, если честно. Что в нём плохого?
Плохой функционал, типа magic_quotes, тащить, разумеется, не надо. Но отказываться от хорошего только по той причине, что он был в 3 и 4 версии — это как-то странно.

В принипе, я не защищаю mysql. Но нахожу аргументы запретителей весьма спорными.
PDO удобно тем, что для mysql и sqlite пишешь одно и то же. Вы для sqlite что используете?
Я не использую sqlite.
А что в mysqli конкретно сырого?
Я привёл в статье два примера ошибок, исправленных совсем недавно.
Это означает, во-первых, что столь серьёзные баги всплывают до сих пор, а во-вторых — они мешали массовому использованию (а значит — тестированию) экстеншена. Что означает довольно большую вероятность появления новых багов в будущем.
Да, но curl разработчики не собираются удалить его из PHP one day, в отличие от mysql.
Об этом и речь.
При этом из аргументов у них только «старая» и «две другие есть».
Ну так для замены «старого» курла даже больше новомодных кунштюков есть — от fopen-wrappers до php_http. Но курл никто из языка не выпиливает.

Так что если у вас есть ссылка на вменяемое объяснение причин удаления — я был бы благодарен на самом деле. Поскольку, как я уже говорил, я не сторонник этого расширения как такового — я сторонник осмысленных и аргументированных действий вообще.
Зашел сюда увидеть картинку из xkcd.com/327/ и был очень удивлен.
Идея типизированных плейсхолдеров — рулез! Сделать у себя, что ли?:) И за ссылку на библиотечку Котерова спасибо, не видел.

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

Долго чесал репу, откуда я взял про обработку пустого массива в IN
оказалось — из англоязычной версии: en.dklab.ru/lib/DbSimple/#list6
Сейчас посмотрел по коду — так и есть, Котеров поступил совершенно логично — пустой массив тоже является триггером для пропуска плейсхолдера:
case 'a':
    if (!$value) $this->_placeholderNoValueFound = true;
Не знал, да. Котеров вообще велик, но очень уж герметичен, я на многое у него натыкаюсь только по случаю.

А вот что делать, например, если плейсхолдер типа int, а к нему пришла строка «hello, world»?
Если эта строка прошла валидацию согласно правилам предметной области (бизнес логики) приложения — тогда просто приводить к целому (0) и не париться.
Вот буквально в комментариях к предыдущей статье по теме я утверждал, что библиотека для сборки запросов не должна заниматься валидацией данных. Но что-то сейчас гложут меня сомнения.

И если, скажем, кастить ли пустую строку в 0 — это ещё вопрос, то hello world, пожалуй (или, точнее, несовпадение с "-?\d+") — это повод для исключения. Надо посмотреть по коду, но, вроде бы, столь экстремальное приведение типов осознанно нигде не используется.
Исключение должно было быть брошено валидатором. Если валидатор сказал «окей» — то DAL должен безропотно взять данные и отсанитайзить, совсем не глядя внутрь.
> совершенно логично — пустой массив тоже является триггером для пропуска плейсхолдера:

Смотря с какой стороны посмотреть. Для меня это абсолютно нелогично.

Если array(1) генерирует в итоге IN(1), а array(1,2) — IN(1, 2), тогда совершенно логично, что результатом подстановки array() всегда будет пустое множество, т.е. FALSE.
круто, хехе!
И ведь работает однако! :)
Прям сегодня внесу изменения в либу :)
В смысле
IN (FALSE)
работает, как FALSE. То есть, можно обойтись без громоздких проверок, и даже без котеровского 1=0
Есть мнение, что FALSE заматчится с id = 0, к примеру. Ну или пустой строкой.
false, естественно, заматчится с false.

Запрос для размышления: select null in(null), false in(false)
Как это связано с моими словами?

Запрос вам для размышления:

SELECT FALSE IN (FALSE), 0 IN (FALSE), '' IN (FALSE)

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

В то время как предикат `AND FALSE` будет работать как ожидается и всегда.
PS: переосмыслил — да, IN(NULL) таки панацея, поторопился
id IN(NULL) => NULL => false
id NOT IN (NULL) => NULL => false;

Возможна ошибка.
Как правильно поправил david_mz
IN (NULL)
конечно же
Добавьте к названию статьи что-то типа "… полноценный плейсхолдер".
вот еще ссылочки на анализаторы инъекций не помешали. Ну а если статья претендует на звание исчерпывающей, ей лучше быть в какой-нибудь вики, чтобы народ дочерпывал то, что автор забыл зачерпнуть
Тот самый редкий случай, когда «народ» лучше не допускать до редактирования текста :-)
Хорошая статья.

Единственное можно дополнить: у MySQL не только числовые и строковые типы, но есть ещё NULL. Как со строками с NULL работать нельзя, потому что:

mysql> select isnull(null), isnull('null');
+--------------+----------------+
| isnull(null) | isnull('null') |
+--------------+----------------+
|            1 |              0 |
+--------------+----------------+
1 row in set (0.00 sec)


С датами в MySQL, в принципе, можно работать как со строками. Однако есть нюансы, которые выходят за рамки данной темы.

А если писать плэйсхолдеры для баз, поддерживающих больше типов плюс пользовательские, всё будет ещё интереснее! =)
Да, и при этом NULL — это валидное значение для поля _любого_ типа (если поле не определено как NOT NULL, чего в данном случае мы не знаем). То есть, любой типизированный плейсхолдер должен проверять внутри себя is_null и выдавать правильный текст.

Либо можно сделать как в Котлине, и вставить проверку на NULL в плейсхолдер (то есть условно говоря, “:a” — не допускает NULL, “:a?” — допускает). Но это утяжеление синтаксиса, конечно.
А какой он — правильный текст?
Для вставки это NULL, тут просто. А при сравнении — что?
Ну, NULL — он и есть нулл, всегда одинаковый. А что, у тебя для сравнения другие плейсхолдеры?

Да, это радует, что в MySQL нет с ним таких заморочек, как, например в Oracle, который пустые строки в нуллы превращает. Скотина (
А не решит ли функция htmlspecialchars() все проблемы со строками, а (int)/(float) — с числами?
Ой.
Во-первых, функция htmlspecialchars(), как видно из её названия, имеет отношение к HTML, а не к SQL. И в SQL она не поможет, а только навредит. Так что строки надо обрабатывать специально предназначенными для этого функциями.
Во-вторых, с числами тоже могут быть проблемы (я о них писал)
В-третьих, кроме строк и чисел есть и другие элементы запроса.
В-четвёртых, самый главный вопрос состоит не в том, какими функциями форматировать части запроса, а кто это будет делать. Если отдавать форматирование данных на откуп программисту, то он когда-нибудь забудет. Поэтому идея состоит в том, что какие бы функции ни использовались — применять их должна программа, а не программист. В этом суть использования плейсхолдеров.
М… Как навредит?

$student_name = htmlspecialchars("Heh' DROP TABLE students", ENT_QUOTES);
$req = "SELECT student_id FROM students WHERE student_name = '$student_name' LIMIT 1";

print($req);

Функция с параметрами превратит этот запрос в безобидный запрос вида… вида, который тут сразу показывается так, как будто там кавычка есть. А на самом деле она заменяется на #039; Это, знаете, как для откручивания хитро закрученного болта можно воспользоваться специальной отвёрткой (всего за $59.95!), а можно — ножом. Результат будет одним и тем же.

Я согласен, что экранировать данные должна программа. Но если она этого не делает, то что уж тут остаётся? А программеру просмотреть такое несколько затруднительно, чес-слово. Да и при использовании самописных обёрток или PDO — сколько потеряется в производительности? Иногда ведь и 1мс нелишне будет…
Я не понимаю вашей приверженности функции htmlspecialchars().
Если уж вы хотите руками форматировать строки, то почему не пользоваться специально предназначенной для этого функцией? Функцией, которая не портит данные?
А после htmlspecialchars в базе останутся все эти html-сущности, которые там даром не нужны.

Опять же, статью вы, судя по всему, не читали. Иначе поняли бы, что «волшебная» функция htmlspecialchars не всегда помогает:
$order = htmlspecialchars("name; DROP TABLE students", ENT_QUOTES);
$req = "SELECT student_id FROM students ORDER BY $order";

Про «программеру просмотреть» и 1мс я, извините, не буду комментировать.
Список «плохих» значений у html и sql качественно разный. Полагаться на их «количественное» сходство — сложноподдерживаемый костыль. Простой пример — «Heh' DROP TABLE students» нам нужно вывести из БД в файл, не предназначенный для вывода в браузер, например в лог. Ваш способ выведет в лог нечитаемую фигню.

Экранировать данные нужно в момент вывода и строго определенным форматом вывода методом (а ыйл-запросы — это вывод приложения). Безопасность — это побочный эффект экранирования, его основное назначение — сохранение данных в первозданном виде. Для безопасности существует фильтрация.
Ясно, спасибо.
пост добавлен в избранное
Такое в хозяйстве пригодится =)
Пользую в проектах один из допиленных вариантов DBSimple Котерова, поверх PDO. Плейсхолдеры и белые списки из коробки идут. В общем то да, верный способ обрубить шаловливому хакеру свободу действий на корню.
Понимаю, что несколько в сторону, но глаз зацепился за:
А ошибки — это всегда плохо.

It depends, на вскидку:
1) от дизайна юзкейсов приложения
В одном случае приложение может перекладывать ответственность на пользователя, репортуя ему напр. о невалидном вводе, в другом — выполнять некий экшн, определенный как дефолтовый.
2) от деплоймент платформы (dev/prod)
На боевом экземпляре приложения все возможные ошибки закрываются синим экраном смерти и тихонечко записываются в лог, на разработческом наоборот — выводятся все потроха (колстеки, вардампы и пр)
Вы не совсем поняли, о чём речь.

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

2) Вы путаете саму ошибку и сообщение о ней. Про сообщения об ошибках речи не было. Я говорил о самой ошибке. Которая существует независимо от способа информирования о ней.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории