Xwab
Форумыnavigate_nextПрограммирование на PHP

О фильтрации переменных
Сообщения
Akdmeh

Добрый день!

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

Решил, что раз и навсегда нужно создать тему, куда посылать всех начинающих кодеров, и в ней он получит ответы на все волнующие вопросы.
Сразу замечу, что эта тема - только личное мнение и просто наблюдения по личному опыту.
При дельных замечаниях я добавлю в статью ваше мнение либо исправлю неточности.
Также замечу, что ни один код не является панацеей "напиши это - и заработает всё", нужно думать и размышлять о том, что вы делаете.
добавлено спустя 18 секунд:
Итак, начнем.
Какие же дыры могут быть в скриптах?
Грубо говоря, мы обсудим две самые главные ошибки, которые могут касатся фильтрации: это xss и sql-inj, при чем ошибки часто взаимосвязанные, хотя и разнопрофильные.

Фильтрация для вывода в браузер

Суть любой фильтрации в коде состоит в том, чтобы не нарушать логику работы скрипта.
Что же может нарушить логику?
Обычно посетитель не должен иметь возможность выводить html-код на странице внутри сообщения в гостевой, на форуме или в имени пользователя.
Если он использует html-код - нужно отобразить его без искажений обычным текстом.
Это и состоит суть фильтрации от любого сорта xss - любые данные от пользователя должны быть профильтрированы.
ПРАВИЛО:
ЛЮБАЯ ИНФОРМАЦИЯ ОТ ПОЛЬЗОВАТЕЛЯ, КОТОРАЯ ВЫВОДИТСЯ В БРАУЗЕРЕ, ДОЛЖНА БЫТЬ ОТФИЛЬТРОВАНА ПЕРЕД ВЫВОДОМ! БЕЗ ИСКЛЮЧЕНИЙ (ну почти, если точно уверены в том, что вы делаете)!
Как же сделать информацию от пользователя такой, чтобы она не портила html-код и выглядела как обычный текст?
Нужно заменить:
< на &lt;
> на &gt;
" на &quot;
' на '
& на &amp;
Первые два предовратят трактовку текста как html-тегов, что более-менее понятно. Но часто забывают о кавычках, которые тоже желательно заменять безопасными сущностями, так как если этого не сделать, и использовать подобный код:
<input type="text" name="text" value="<?php echo $var ?>" />
злоумышленник может подставить в $var нефильтрованный текст типа: Some text" onclick="javascript:alert('hacked');
в результате мы имеем риск получить уязвимость:
<input type="text" name="text" value="Some text" onclick="javascript:alert('hacked');" />
заметьте, без единого < или >
Простой алерт - это почти безобидное явление, но если это будет ссылка, на которую можно нажать, а вместо кода отправлять вместе с картинкой куки с сайта - это уже большая уязвимость для сайта.
Последний символ нужно экранировать для целосности ссылок и для валидности страницы, поэтому заменять амперсанд (вот эта штучка: &) тоже желательно.

Итак, можно написать собственноручные "костыли", но для этого существует ЕДИНСТВЕННАЯ ХОРОШО РАБОТАЮЩАЯ функция - htmlspecialchars.
Итак, как её использовать?
Есть определенный текст в переменной $text.
Использование:
$text=htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
Первый параметр функции - текст, который нужно отфильтрировать; второй аргумент - указывает, что обрабатывать надо как одинарные, так и двойные кавычки (это важно!); третий аргумент - кодировка, её желательно указать для избежания повреждений текста.

Когда же её нужно использовать?
Варианта два, и вы можете придерживаться как одного метода, так и другого.
Первый вариант - использовать функцию фильтрации ДО записи в файл/базу данных.
Это позволит профильтрировать только один раз, на что многие аргументируют, будто бы это экономит время.
Второй вариант - в базу записывать все как есть, а фильтрацию делать при выводе.
Нужно это не для скорости исполнения кода (ведь функция будет работать не один раз при записе в базу данных, а каждый раз при выводе), а для того, чтобы в будушем не иметь проблем с возможной модификацией логики скрипта.
Многие поддерживают мнение, что в базе все должно храниться так, как это ввел пользователь. Кроме того, со временем можно будет разрешить некоторые теги, а некоторые нет. Если использовать защиту до записи - необходима будет переработка хранения в базе.
С "недостатков" можно назвать необходимость каждый раз использовать функцию фильтрации при выводе, что требует внимания при написании - можно просто забыть о защите в определенном участке кода, а тогда уж проблем не миновать.

Итак, объявляю еще раз: никаких десятков функций-фильтров.
Одной htmlspecialchars ХВАТАЕТ НА ВСЕ НУЖДЫ!

О переменных-числах

Если вы уверены в том, что в переменной должно быть только целое положительное число, используем код:
$int=abs(intval($int));
intval удалит все, что не есть числом, вторая функция - сделает его в любом случае положительным.
Если в переменной нет числа, будет возвращен 0.
Это можно использовать для получения числа страницы, id и прочее.

О записи в базу данных

Все строки ради избежания sql-injection нужно фильтровать функцией mysql_real_escape_string, или же в классе mysqli метод real_escape_string().
Это позволит сохранить целосность запроса.
Что же делает mysql_real_escape_string?
Эта функция перед всеми специальными символами в mysql ставит бэкслеш \, что позволяет интерпритировать запрос правильно.
Пусть существует запрос:
mysql_query("DELETE FROM `posts` WHERE `user`='$name'");
Он удалит все сообщения пользователя из гостевой с именем $name.
Если ввести в переменную $name имя Spamer - то все будет работать как нужно.
DELETE FROM `posts` WHERE `user`='Spamer'
Но если вместо имени ввести Spamer' OR `user` LIKE '%
То получим запрос:
DELETE FROM `posts` WHERE `user`='Spamer' OR `user` LIKE '%'
Что вообще удалит все сообщения.
Пример немного натянутый за уши, но задача моей заметки не хакнуть гостевую школьника Васи из 9-а, а научиться от этого защищаться.
Как видно, знак ' досрочно завершил строку и позволил исказить запрос в базу данных.
Поэтому некоторые символы, как вы понимаете, неплохо бы экранировать, чтобы запрос был корректен - интерпритатор запросов mysql понял, что строка еще не закончилась.
Итак, функция mysql_real_escape_string заменяет:
\ на \\
' на \'
" на \'
Но обратите внимание! Это используется только при записи в базу, поэтому сама база автоматически удаляет подобную экранизацию уже при самом записи и все возвращает на свое место как было.
Объясняю:
Пусть строка до фильтрации выглядела так:
Spamer' OR `user` LIKE '%
После обработки функцией mysql_real_escape_string она будет выглядеть так:
Spamer\' OR `user` LIKE \'%
Но когда запрос будет собственно выполнятся с помощью mysql, то сама строка опять примет вид
Spamer' OR `user` LIKE '%
Разница в том, что в первом случае строка состояла из имени Spamer, а затем кода злоумышленника - ' OR `user` LIKE '%
После фильтрации вся строка будет восприниматься как единственное целое Spamer' OR `user` LIKE '%, но без зловредного запроса.

Важная мелочь: если использовать mysql_real_escape_string перед выводом у браузер, все переносы строк будут заменятся на видимый текст \r\n или \n. Поэтому нельзя позволять выводить текст после обработки mysql_real_escape_string в браузер. Эта функция используется ТОЛЬКО для записи в базу данных, и фильтровать строку нужно ТОЛЬКО перед собственно записью, а если профильтровали - то уже выводить в браузер не желательно (разве что сохранить переменную до изменения функцией).
Можно делать так: mysql_query("SELECT * FROM table WHERE name='".mysql_real_escape_string($string)."'");
Немного усложняет чтение записи, зато если нужно будет еще вывести $string в браузер - не надо применять функции антифильтрации или создавать еще одну переменную до обработки функцией.

Вторая важная мелочь: символы _ и % не фильтрируются, они также используются для поиска по строке с помощью функции mysql LIKE. Поэтому, если строку вставляете в LIKE, то желательно заменить
_ на \_
% на \%
самому.

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

ПРАВИЛО ЗАЩИТЫ НОМЕР ОДИН:
для записи любых строк, полученных от пользователя, в базу данных необходимо обработать её минимум функцией mysql_real_escape_string, что защитит нас от любых нарушений sql-запроса, то есть, от sql-injection.

ПРАВИЛО ЗАЩИТЫ НОМЕР ДВА:
Любая информация от пользователя, которая выводится в браузере, должна быть обработана функцией htmlspecialchars с тремя аргументами (смотреть выше). Неважно когда - перед записью в базу данных, или собственно перед выводом.
СКАЖИТЕ НЕТ конструкциям типа: mysql_real_escape_string(htmlspecialchars(stripslashes(addslaches($text))));
Использовать нужно только одну функцию htmlspecialchars!

Еще раз алгоритм. Итак, пусть у нас есть строка $var, полученную от пользователя, и которую надо отфильтрировать:
1) Если в переменной должно быть число - используем $var=abs(intval($var));
2) Если переменную надо вывести в браузер - используем $var=htmlspecialchars($var, ENT_QUOTES, "UTF-8");
Не забывайте, это фильтрует только опасные символы для того, чтобы они не работали как html-код. Иногда надо ограничить длину строки, обрезать её - это все должно быть сделано до фильтрации, иначе бывают случаи, когда &amp; разрезается на две части, и мы получаем невалидную страницу (что самое парадоксальное, подобный баг был в самом ВКОНТАКТЕ в личных сообщениях, может и до сих пор сохранилась ошибка).
3) Если переменную надо ввести в базу данных:
а) можно просто очистить с помощью mysql_real_escape_string, но не забыть потом при выводе фильтрировать с помощью htmlspecialchars
б) фильтрируем с помощью htmlspecialchars, затем mysql_real_escape_string, затем запись в базу


Вот и все. Главное, думать, что вы делаете, а не просто использовать 10 функций фильтрации, не понимая зачем они.
Эти простые советы закроют минимум 90% всех возможных уязвимостей в коде, связанных с пользовательскими данными, но, по статистике взломов, даже эти мелочи не исполняются полностью то ли по невнимательности, то ли через мысли "а, никто не догадается" или "никому мой скрипт не нужен".

Успехов!


__________
посл.ред. 10 Дек 2011, 16:17; всего 2 раз(а) 10 Дек 2011, 15:35
PeraX

Прекрасно конечно, но думаю это почти каждый знает

10 Дек 2011, 15:49
TOOZ

Очень будет полезно людям,супер!

10 Дек 2011, 15:51
manyrus

Пропёрло. Теперь можно создавать тематические блоги/статьи в разделе php/mysql.
добавлено спустя 1 минуту:
О записи в базу данных
А как же prepare statement ? Там этих "Все строки ради избежания sql-injection нужно фильтровать функцией mysql_real_escape_string, или же в классе mysqli метод real_escape_string(). " не нужно.

10 Дек 2011, 15:57
Akdmeh

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

10 Дек 2011, 16:13
Максим

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

10 Дек 2011, 17:33
manyrus

Akdmeh, ладно, понимаю, наболело)

10 Дек 2011, 18:06
Akdmeh

6, если отправляешь письмо в режиме html - не будет лишним. Если в режиме plain/text - не обязательно, но все же не было бы лишним, т.к. почтовики разные бывают.

10 Дек 2011, 18:09
Максим

Akdmeh, Как бы там потом не получилось двойной фильтрации типа htmlspecialchars(htmlspecialchars($text)) или ещё чего-нибудь

10 Дек 2011, 18:20
Akdmeh

9, не, скорее всего не получится.

10 Дек 2011, 18:22
Ответить на тему