\"Кавычки \". Cоставление запросов, слеши, SQL Injection
Слеш, или бэкслеш, от английского back slash - обратная косая черта ("\"), которая непонятным образом вдруг сама собой появляется в ваших переменных. Добавляется он к некоторым спецсимволам, но в основном его замечают из-за кавычек.
Происходит это из-за специальных настроек PHP, обычно включённых на хостинге по умолчанию. Теоретически, эти настройки могут повысить безопасность скриптов, работаюющих с БД. Практически же, от автоматического добавления слешей часто получается путаница и неудобство, как при работе с БД, так и при её отсутствии.
Ниже мы подробно разберём оба этих случая.
За автоматическое добавление слешей отвечают директивы php.ini, которые носят общее название "волшебные кавычки":
magic_quotes_gpc и magic_quotes_runtime
Если включена первая, то PHP автоматически добавляет слеши к данным, пришедшим от пользователя - из POST, GET запросов и кук (а так же - к логину и паролю, полученным через HTTP Authorisation).
Если вторая, то слеши добавляются к данным, полученым во время исполнения скрипта - например, из файла или базы данных.
Как избавиться
Если вы работаете без базы данных, или же работаете с БД правильно (о чём будет написано ниже), лишние слеши вам только мешают, и от них надо избавляться. Проще и правильнее всего отключить автоматическое добавление, в настройках PHP.
Это можно сделать либо поправив соответствующие директивы в php.ini, если у вас есть к нему доступ, либо создав в коневом каталоге сайта файл .htaccess, и добавив в него строчки
php_flag magic_quotes_gpc 0
php_flag magic_quotes_runtime 0
Если отключить таким образом не получается, то придётся писать код разной степени сложности, чтобы очистить от слешей входящие данные. (Впрочем, если вы хотите написать переносимое приложение, не зависящее от настроек PHP, то написать его всё равно придётся. И включать отдельным блоком в начале ваших скриптов).
С данными, получаемыми во время работы, разобраться проще всего: достаточно в начале скрипта написать
set_magic_quotes_runtime(0);
Для данных, полученных от пользователя, всё гораздо сложнее. Для этого кода нам потребуется две функции:
* проверить, добавил ли PHP, можно с помощью функции get_magic_quotes_gpc.
* удаляет слеши функция stripslashes.
Соответственно, с помощью первой надо проверить, и, если PHP добавил, то перебрать все входящие переменные и очистить с помощью второй.
Если вы работаете правильно, при register_globals = off, то достаточно применить stripslashes ко всем массивам, содержащим данные, приходящие из браузера.
к примеру, можно включить во все скрипты сайта вот такой код:
<?
function strips(&$el) {
if (is_array($el))
foreach($el as $k=>$v)
strips($el[$k]);
else $el = stripslashes($el);
}
if (get_magic_quotes_gpc()) {
strips($_GET);
strips($_POST);
strips($_COOKIE);
strips($_REQUEST);
if (isset($_SERVER['PHP_AUTH_USER'])) strips($_SERVER['PHP_AUTH_USER']);
if (isset($_SERVER['PHP_AUTH_PW'])) strips($_SERVER['PHP_AUTH_PW']);
}
?>
Правила составления запросов MySQL
Для начала - немного о том, почему вообще нужны эти слеши.
Если мы подставляем в запрос какие-либо данные, то, чтобы отличить эти данные от команд SQL, их надо брать в кавычки.
К примеру, если написать
SELECT * FROM table WHERE name = Bill
то база решит, что Bill - это имя другого поля, не найдёт его, и выдаст ошибку. Поэтому подставляемые данные (в данном случае имя Bill) надо заключать в кавычки - тогда база сочтет его строкой, значение которой надо присвоить полю name:
SELECT * FROM table WHERE name = 'Bill'
Однако, и в самих данных могут встречаться кавычки тоже. К примеру,
SELECT * FROM table WHERE name = 'Д'Артаньян'
Здесь база данных решит, что 'Д' - это данные, а Артаньян - команда, которую она не знает, и тоже выдаст ошибку. Поэтому и надо прослешивать все данные, чтобы объяснить базе, что встречающиеся в них кавычки (и некоторые другие спецсимволы) относятся к данным.
В результате мы получим правильный запрос, который ошибок не вызовет:
SELECT * FROM table WHERE name = 'Д\'Артаньян'
Таким образом, мы выяснили, что при подстановке данных в запрос, следует придерживаться двух правил:
- все вставляемые в запрос данные должны быть заключены в кавычки (одинарные или двойные, но удобнее и чаще используются одинарные).
- во всех строковых переменных должны быть экранированы слешами спецсимволы.
Следует специально отметить: добавленные слеши НЕ идут в базу. Они нужны только в запросе. При попадании в базу слеши отбрасываются. Соответственно, распространенной ошибкой является применение stripslashes при получении данных из базы.
На самом деле, всё вышесказанное относится к данным строкового типа и датам. Числа можно вставлять не прослешивая и не окружaя кавычками. Если вы так делаете, то ОБЯЗАТЕЛЬНО! насильно приводите данные к нужному типу перед вставкой в запрос, например:
$id=intval($id);
Однако для простоты (и надёжности) можно и с числами работать, как со строками (проскольку mysql всё равно преобразует их к нужному типу). Соответственно, мы будем любые данные, вставляемые в запрос, прослешивать и заключать в кавычки.
Так же, есть ещё одно правило - необязательное, но его следует придерживаться во избежание появления ошибок:
Имена полей и таблиц следует заключать в обратные одинарные кавычки - "`" (клавиша с этим символом находится на стандартной клавиатуре слева от клавиши "1") Ведь имя поля может совпадать с ключевыми словами mysql, но если мы используем обратную кавычку, то MySQL поймёт всё правильно:
SELECT * FROM `table` WHERE `date` = '2006-04-04'
Следует различать эти кавычки и не путать одни с другими. Следует также помнить, что обратные кавычки слешами не экранируются.
В случае же неправильных настроек register_globals приемлемое решение и вовсе будет найти затруднительно, поэтому лучше - повторюсь - сразу работать при правильных настройках.
Теперь перейдём к работе с MySQL.
Динамическое составление запросов
Если SQL запрос в скрипте написан целиком, и никак не меняется, например
SELECT * FROM `table`
то никаких проблем с ним и не будет.
Но вся сила наших скриптов именно в динамическом составлении запросов!
Вместо того, чтобы писать готовые запросы на все случаи жизни, мы составляем их на основании поступающих в скрипт данных.
И вот здесь нас подстерегает опасность.
Допустим, мы составляем запрос с использованием переменной:
SELECT * FROM table WHERE name = '$name'
Вроде бы - всё нормально?
А если $name у нас будет Д'Артаньян? Запрос выдаст ошибку!
То есть, переменную перед подстановкой в запрос надо прослешить.
Это можно сделать несколькими путями.
Самый простой (и неправильный) - положиться на волшебные кавычки. Как вы уже догадались, именно для этого случая они и были придуманы. Ради того, чтобы уберечь SQL запросы забывчивых программистов от ошибок, ВСЕ поступающие в скрипт данные прослешиваются без разбору.
Если вы используете чужой код, то лучше воспользоваться волшебными кавычками. Это может создавать некоторые неудобства и не гарантирует вас от ошибок или взлома (поскольку прослешиванием правила составления запросов не исчерпываются) но хотя бы снижает риск. Поэтому, при использовании чужого кода, обязательно убедитесь, что волшебные кавычки включены.
Если жы вы пишете весь код самостоятельно, то следует научиться правильному составлению запросов.
Правильная работа с кавычками при составлении запросов
Итак. Как мы уже узнали выше, чтобы правильно составить запрос, надо заключать данные в кавычки и прослешивать их.
С первым всё понятно. При составлении динамических запросов мы никогда не забываем все данные заключить в кавычки:
$query="INSERT INTO `table` VALUES(NULL,'$name','$date','$price')";
Е сли переменная $price должна быть типа int и мы приведём её к этому типу, то можно её не заключать в кавычки. Однако, если заключим, то беды особой не будет, но зато можно будет сделать работу с данными единообразной.
Второе же - прослешивание - и является тем, ради чего, собственно, по большей части, и написан весь этот текст. Поскольку вызывает больше всего вопросов и затруднений.
Сначала отключим волшебные кавычки. Так, как это описано в самом начале.
Почему это следует сделать?
По многим причинам. Самая очевидная - логическая. "Волшебные кавычки" добавляют слеши не там, где они нужны - при составлении запроса, а еще до попадания в скрипт! Но ведь данные совсем не обязательно после этого будут вставляться в запрос. Может быть, их придётся выводить пользователю, и слеши будут только мешать. Плюс к тому, добавленные слеши помешают, к примеру, правильно проверить длину введённой строки. К тому же, прослешивать нам надо не только пришедшие от пользователя данные, а вообще любые, вставляемые в запрос - многим этот очевидный факт даже не приходил в голову! Список можно продолжать, но вывод один: добавлять слеши надо не автоматом, без разбору, до начала выполнения скрипта, а только там, где действительно надо – при составлении запроса.
Есть и ещё одна причина: при использовании кодировки Unicode, которая приобретает всё большую популярность, а со временем займёт доминирующее положение в веб, волшебные кавычки могут попросту испортить текст, приняв часть мультибайтной строки за спецсимвол.
Теперь займёмся добавлением слешей самостоятельно.
Во-первых, для прослешивания мы воспользуемся функцией mysql_real_escape_string()
Следует помнить, что применять её можно только после установления соединения с базой.
Эта функция делает гораздо больше, чем устаревшие addslashes и mysql_escape_string. Во-первых, она облегчает ведение и чтение логов mysql, заменяя, например, символ перевода строки на "\n" и некоторые другие символы на escape-последовательности. Во-вторых, и самое главное - она корректно работает с многобайтными кодировками, принимая во внимание текущую кодировку MySQL и не портит, таким образом, тексты в кодировке Unicode.
Во-вторых, не забываем, что прослешить надо все те данные, которые мы заключили в запросе в кавычки:
$name=mysql_real_escape_string($name);
$age=mysql_real_esc ape_string($age);
$query="INSERT INTO table (name,age,class) VALUES ('$name','$age',11)";
или:
$query="SELECT * FROM table WHERE name LIKE '".mysql_real_escape_string($_GET['name'])."%' ;";
Видно, что код получается довольно громоздким. Для облегчения составления запросов можно пуститься на разные ухищнения - сделать функцию для составления запросов из массива (очень удобно для запросов типа INSERT и UPDATE), прослешивать массив данных в цикле, и так далее.
Вы можете написать и свою библиотеку или функцию для составления запросов.
Главное - помнить, что только неукоснительное соблюдение правил составления запросов гарантирует вас от взлома БД, а так же сознавать, что применение "волшебных кавычек", при видимой лёгкости составления запросов, не даёт такой гарантии, а только мешает нормальной работе приложения.
Итак, мы научились правильно подставлять в запрос данные.
НО! Динамическое составление запросов не исчерпывается подстановкой данных. Часто нам приходится подставлять в запрос команды SQL и имена полей. И здесь мы уже переходим к теме безопасности:
SQL Injection
SQL Injection - это способ хакерской атаки, когда передаваемые скрипту данные модифицируются таким образом, что запрос, формируемый в этом скрипте, начинает выполнять совсем не то, для чего он предназначался.
Правила защиты от таких атак можно разделить на два пункта:
1. Работа с данными.
2. Работа с управляющими элементами запроса.
Первый пункт мы подробно рассматривали выше. Он, можно сказать, и не является, собственно, защитой. Соблюдение правил добавления занных в запрос продиктовано, в первую очередь, требованиями СИНТАКСИСА SQL. А как побочный эффект мы имеем и защиту от взлома.
Второй пункт гораздо сложнее, поскольку не существует такого же единого универсального правила, как для данных - обратная кавычка никак не защитит имя поля от модификации хакером. Невозможно кавычками защитить имя таблицы, операторы SQL, параметры команды LIMIT, и другие операторы.
Поэтому основное правило при подстановке управляющих элементов в запрос такое:
Если требуется динамически подставлять в запрос операторы SQL или имена полей, баз данных, таблиц, то ни под каким видом не вставлять их в запрос напрямую.
Все варианты таких добавлений должны быть ЗАРАНЕЕ прописаны в вашем скрипте и выбираться на основании того, что ввёл пользователь.
К примеру, если надо передать имя поля в оператор order by, то ни в коем случае нельзя подставлять его напрямую. Надо сначала проверить его. К примеру, сделать массив допустимых значений, и подставлять в запрос только если переданный параметр в этом массиве присутствует:
$orders=array("name","price","q ty");
$key=array_search($_GET['sort'],$orders));
$orderb y=$orders[$key];
$query="SELECT * FROM `table` ORDER BY $orderby";
Мы ищем в массиве заранее описанных вариантов введённое пользователем слово, и, если находим, то выбираем соответствующий элемент массива. Если совпадения не будет найдено, то будет выбран первый элемент массива.
Таким образом, в запрос подставляется не то, что ввёл пользователь, а то, что было прописано у нас в скрипте.
Точно так же надо поступать и во всех остальных случаях
К примеру, если динамически формируется оператор WHERE:
if (!empty($_GET['price'])) $where.="price='".mysql_real_escape_string($_GET['pri ce'])."'";
$query="SELECT * FROM `table` WHERE $where";
Мне сложно представить себе случай, когда имя таблицы может подставляться в запрос динамически, но если такое случится, то имя тоже надо вставлять только из заранее прописанного в скрипте набора.
Параметры оператора LIMIT следует принудительно приводить к целочисленному типу с помощью арифметических операций или функции intval().
Не следует думать, что перечисленными здесь примерами исчерпываются все варианты динамического составления запросов. Нужно просто понять принцип, и применять его во всех подобных случаях.
Подготовленные выражения
До сих пор мы говорили о таком способе работы с БД, когда запрос составляется целиком, в виде строки, и отправляется в базу.
Но есть ещё один, называемый "подготовленными выражениями" (prepared statements).
Суть его заключается в том, что подготавливается шаблон запроса, со специальными маркерами, на место которых будут подставлены динамические компоненты. Пример такого шаблона:
SELECT * FROM table WHERE name=?
Знак вопроса здесь - это тот самый маркер. По-другому он называетсй плейсхолдером (placeholder). Весь секрет в том, что данные на его место подставляет специальная функция, которая "привязывает" переменную к запросу.
Вот как выглядит код в таком случае:
$stmt = $mysqli->prepare("SELECT District FROM City WHERE Name=?");
$stmt->bind_param("s", $city);
$stmt->execute();
В первой строчке мы подготавливаем шаблон запроса.
Во второй - привязываем к маркеру значение переменной $city. При этом - что самое приятное! - все необходимые операции (заключение в кавычки, прослешивание) функция сделает сама.
В третьей строчке выполняем подготовленный таким образом запрос.
Понятно, что возникает много вопросов. Но ни объём, ни тематика данной статьи не позволяют остановиться на них более подробно. Рекомендую обратиться к документации по библиотекам mysqli и PDO, реализующим данный принцип.
Так же, можно использовать библиотеку DbSimple Дмитрия Котерова или PEAR::DB. Основное отличие этих двух состоит в том, что они реализуют механизм подготовленных выражений только внешне. А внутри работают по-старинке - составляя запрос и отправляя его в базу. А PDO и mysqli работают с базой с самого начала, то есть именно шаблон, прямо с вопросиками, уходит в базу, там разбирается, и база сама подставляет привязанные значения к маркерам.
Особенности работы с оператором LIKE
Совершенно отдельный случай - оператор LIKE.
Во-первых, помимо обычного прослешивания, в переменных, которые подставляются в LIKE, надо удваивать слеши. То есть, если в переменной содержится символ \, то его надо удвоить, а после этого выполнить обычное прослешивание, через mysql_real_escape_string.
К примеру, если мы ищем строку
символ \ называется "backslash"
и нам нужно точное совпадение, то мы просто применяем mysql_real_escape_string и запрос получается стандартный:
SELECT * FROM test WHERE field = 'символ \\ называется \"backslash\"'
Если же мы хотим подставить эту строку в LIKE, то сначала надо заменить каждый слеш на два, а потом применить mysql_real_escape_string. В результате получится
SELECT * FROM table WHERE field LIKE '%символ \\\\ называется \"backslash\"%'
Во-вторых, следует обратить внимание на то, что ни одна из функций, добавляющих слеши, не добавляет их к метасимволам поиска "%" и "_", используемым в операторе LIKE. Поэтому, если вы используете этот оператор, и не хотите, чтобы символы _ и % использовались, как маски, то добавляйте слеши вручную. Это можно сделать командой
$data = addCslashes($data, '%_');
Внимание - это не addslashes! В имени этой функции есть дополнительная буква "c".
Таким образом получается, что переменные, используемые в операторе LIKE мы должн обрабатывать отдельно.
сначала заменять один слеш на два, с помощью такого, к примеру, кода:
$var=str_replace('\\','\\\\& #39;,$var);
затем (можно наравне со всеми другими данными, идущими в запрос) прослешиваем:
$var=mysql_real_escape_string($var);
а затем, если хотим, чтобы _ и % соответствовали точно самим себе, делаем
$var=addCslashes($var, '_%');
В результате, если мы будем искать, к примеру, такую строку
символ \ называется "backslash", а символ _ называется "underscore"
то после обработки, в запросе она должна выглядеть так:
'%символ \\\\ называется \"backslash\", а символ \_ называется \"underscore\"
То есть, слеш, который был в строке изначально - учетверился. Остальные символы прослешились, как обычно. Плюс - прослешился символ подчёркивания.
Замечания
* Среди причин, по которым не стоит полагаться на "волшебные кавычки", есть ещё одна. Весьма маловероятная, но всё же. К "волшебным кавычкам" относится на самом деле не две директивы, а три. Третья - magic_quotes_sybase. Мало того, что она вместо слеша добавляет кавычку - так она ещё и отменяет действие magic_quotes_gpc. Если каким-то чудом обе эти директивы имеют статус 'on', то последняя не сработает! То есть, полагаясь на "волшебные кавычки", мы в этом случае получим все прелести неправильно составленных запросов. Вообще, чисто теоретически, надо учитывать наличие этой директивы, поскольку она преподносит ещё и такой сюрприз, как... изменение поведения функций addslashes и stripslashes! Если magic_quotes_sybase = on, то эти функции начинают вместо слеша добавлять и удалять одинарную кавычку соответственно.
* Все приведенные примеры касаются только БД Mysql. Конкретные правила составления запросов могут отличаться для других СУБД, но общий принцип остается прежним:
o если API для работы с БД или сторонняя библиотека предоставляет специальные функции для составления запросов, и есть возможность их использования, то пользоваться в первую очередь надо ими.
o если таких функций нет, то следует искать в документации функции экранирования спецсимволов для этой СУБД.
Примечание: формы
При выводе value в тегах input форм, слеши не помогают.
Чтобы текст в таком поле выводился целиком, value надо заключать в кавычки, а к выводимым данным применять функцию htmlspecialchars()
Пример:
">
<? echo htmlspecialchars($text,ENT_QUOTES)?>
Необходимо так же отметить (хоть это уже совсем не имеет отношения к кавычкам и слешам), что функцию htmlspecialchars следует применять при выводе в браузер вообще ко всем данным, которые получены от непроверенного пользователя.
Источник:
phpfaq.ru