Почему PDO не экранирует кавычки?

359
23 августа 2017, 19:09
$sql = 'INSERT INTO `sell`.`themes` (`name`, `preview`, `them`, `price`, `create_at`) VALUES (?, ?, ?, ?, NOW())';
$pdo->prepare($sql)
->execute(array($n['name'], $n['preview'], $n['them'], $n['price']));

Вот таким кодом вставляю в базу новые записи, когда в значениях любой из переменной есть кавычки они без экранирования вставляются в базу, это нормально?

вот скрин из PMA

Answer 1

Вы, видимо, не до конца понимаете, где и зачем нужно экранирование. Давайте разберём весь путь ваших данных от INSERT до вывода на экран в вашем браузере.

Отправка SQL-запроса

База данных должна уметь хранить произвольные данные, в том числе любые символы в любых строках. Но вот незадача — кавычки это спецсимволы в SQL. Если мы, возьмём, например, такую строку:

хорошие данные"), ("я хакер азаза"); --

и попытаемся её добавить через примерно такой запрос:

INSERT INTO mytable ("сюда подставляем строку")

то получится такой SQL-код:

INSERT INTO mytable ("хорошие данные"), ("я хакер азаза"); -- ")

Да, это SQL-инъекция. Вместо одной исходной строки база данных запишет две: хорошие данные и я хакер азаза.

Но ведь запрещать кавычки для предотвращения инъекции это неудобно, мы же наверно хотим сохранить строку хорошие данные"), ("я хакер азаза"); -- в базе данных как есть, чтобы пользователь её мог потом прочитать в точно таком же виде мог прочитать? (В конце концов, если бы все запрещали кавычки, я бы не смог написать этот ответ, в нём же много кавычек :)

К счастью, есть экранирование. Кавычки экранируются заменой " на \" (если в строке изначально есть обратный слэш \, то он тоже экранируется заменой на \\). С экранированием получается такой код:

INSERT INTO mytable ("хорошие данные\"), (\"я хакер азаза\"); -- ")

Так вот, PDO делает экранирование автоматически. Если всё сконфигурировано правильно, SQL-инъекция в коде из вашего вопроса невозможна.

Но это не значит, что экранированные кавычки так и сохранятся! Когда база данных парсит этот SQL-запрос, она, как бы корректнее выразиться, в общем автоматически убирает экранирование со строки. Экранирование нужно только для того, чтобы при разборе SQL-запроса база данных не попутала кавычку внутри строки с кавычкой, означающей окончание строки. После разбора база данных уже ничто ни с чем не попутает и всё экранирование можно безопасно убрать, и строка хорошие данные"), ("я хакер азаза"); -- в итоге сохраняется как есть. Здесь всё безопасно, база данных сама позаботится об этом, вам важно лишь экранировать строку перед подстановкой в SQL-запрос (повторюсь, PDO это делает автоматически, код из вопроса безопасен).

PMA забирает строку из базы данных

Перед отображением содержимого вам PMA делает запрос вроде SELECT * FROM mytable и в ответе получает нашу строку со всеми кавычками, сохранёнными в предыдущем шаге (как устроен протокол передачи строки от базы данных в PMA, я не знаю, но там тоже всё безопасно). Потом PMA должен запихнуть эту строку в HTML-код с красивой табличкой и передать его вам, чтобы браузер эту табличку нарисовал.

Но здесь уже становится возможно XSS. Это когда в строке оказывается что-то вроде <script>alert("XSS!")</script> — если запихнуть это в HTML-код как есть, то браузер это воспримет не как текст, а как HTML-тег, обозначающий скрипт, и выполнит любой код внутри него (а этот любой код сможет, например, передать злоумышленнику ваши логин и пароль от текущего сайта). Поэтому перед выводом PMA тоже всё экранирует с помощью HTML-сущностей.

Из строки

хорошие данные"), ("я хакер азаза"); --

получается

хорошие данные&quot;), (&quot хакер азаза&quot;); --

а из строки

<script>alert("XSS!")</script>

получается

&lt;script&gt;alert(&quot;XSS!&quot;)&lt;/script&gt;

Если в исходной строке есть символ &, то он экранируется как &amp;. Если там уже есть &amp;, то с экранированием получается &amp;amp; ну и так далее :)

Чтение HTML-кода браузером

Сервер (PMA) отвечает браузеру HTML-кодом, в котором все спецсимволы в ваших строках вот так вот экранированы. Когда браузер читает это всё, он теперь воспринимает это не как теги, а как обычный текст, и опасности появления XSS нет. Но перед выводом на экран тоже убирает всё экранирование — заменяет &quot; обратно на " и так далее.

Поэтому вы в итоге видите не fhfh\" OR 1 и не fhfh\&quot; OR 1, а fhfh" OR 1 — ровно такую строку, какую вы изначально и хотели отправить в базу данных.

Answer 2
$sql = "INSERT INTO 'sell'.'themes' ('name', 'preview', 'them', 'price', 'create_at') VALUES (?, ?, ?, ?, NOW())"; 
$pdo->prepare($sql) ->execute(array($n['name'], $n['preview'], $n['them'], $n['price']));

Категорически НЕЛЬЗЯ без ручного екранирования вставлять так кавычки, только так.

$foo = "fuiyg 'ookjgi' lifuo"; - так можно

$foo = 'fjokt "hkooy" ogupy "jkjf" gju'; и так можно

$foo = 'lkgoo 'dgoov' kifg 'fik' jgu'; - так, и $foo = "fynnfr "fgjiy" dhj "fhji" dgj"; - нельзя

READ ALSO
Как правильно сделать фильтр товаров с чекбоксами model search?

Как правильно сделать фильтр товаров с чекбоксами model search?

Всем привет, не понимаю как правильно сделать фильтр товаров по свойствамУ меня yii2 advanced! В фильтре хочу вывести хотя бы примитивное что бы понять...

378
Подсчёт цены в корзине

Подсчёт цены в корзине

Здравствуйте, не могу разобраться с массивами и sql запросом

200