воскресенье, 8 июля 2012 г.

Защита текстовых строк в php


Привет!
Сегодня мы продолжим тему защиты (частично используя репост со старого блога, частично - записи с одной темы форума).

Виды уязвимостей

Главную угрозу для безопасности скрипта несут строки, принятые от пользователя. Полученные они могут быть разными способами - как с помощью $_GET, $_POST, так и с помощью $_COOKIES (злоумышленник может подделать cookies с помощью специальных плагинов) и даже $_FILES (чисто гипотетически с помощью имени файла, которое состоит из зловредного кода).

XSS

Если в будущем мы будем выводить информацию, полученную от пользователя в браузер, в ней могут быть html-теги. Главная опасность состоит в том, что с их помощью возможно исполнить вредоносный код JavaScript'а, например, переадресацию на другую страницу, или, что ещё хуже, украсть ваши и пользовательские cookies (в которых часто хранятся сессии для доступа к сайту, или чего хуже - пароли), поэтому html-теги должны быть запрещены, и фильтрировать надо именно их!
Такой тип уязвимости называется XSS (Cross site scripting).
Существует два вида XSS - первая пасивная, и менее опасна - текст в html-кодах выводится за условий, если подать специально сформированную ссылку.
Допустим, на странице есть участок кода <?php echo $_GET['text']; ?>.
Если пользователю подсунуть ссылку page.php?text=<script type="text/javascript">alert('ass!')</script> то тот, кто перейдет по этой ссылке, увидит исполнение JS-кода и очень некрасивое слово у выпадающем окошке;)
Намного хуже будет, если эти данные заносятся в файл или базу данных, и при этом выводятся без фильтрации всем пользователям, которые просто перейдут по ссылке без подозрения на возможность взлом - если это разрешить, то тот, кто перейдет по странице page.php исполнит JS-код, размещенный на странице хакером. Этот вид атаки называется активная XSS, и является одной из наиболее опасных.

SQL-Injection

Еще один вид уязвимости - sql-inj, которая разрешает подставить в sql-запрос то, что изначально не предвиделось, например,
"SELECT `id` FROM `users` WHERE `login`='$login' AND `passw`='$passw'". Если же в $login подставить данные "Akdmeh' AND `id`=1/*", то, фактически, мы зайдем под ником администратора.
Конечно, такой вид уязвимости заметить очень тяжело, но если её допустить, то рано или поздно за это можно очень дорого заплатить (проверено).

Способы защиты

Защита от XSS

Первым делом защитимся от xss-атак, как активных, так и пассивных. Для этого необходимо вывести эквиваленты тех символов, которые формируют html-теги. То есть, нужно заменить:
< на &lt;
> на &gt;
" на &quot;
' на &#0039;
& на &amp;
Важно заметить, что многие забывают о кавычках, которые тоже желательно заменять безопасными сущностями, так как если этого не сделать, и использовать подобный код:
<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');" />
и заметьте, без единого символа < или >.
Последний символ нужно экранировать для целосности ссылок и для валидности xhtml-страницы, поэтому заменять амперсанд (знак &) тоже желательно.

Итак, можно написать собственноручные "костыли", но для этого существует функция htmlspecialchars.
Использовать эту функцию очень просто:
$text=htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
Первый параметр функции - текст, который нужно отфильтрировать; второй аргумент - указывает, что обрабатывать надо как одинарные, так и двойные кавычки (это важно!); третий аргумент - кодировка, её желательно указать для избежания повреждений текста.

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

Защита от SQL-Injection

Все строки ради избежания 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 '%' 
, что вообще удалит все сообщения.
Как видно, знак ' досрочно завершил пользовательскую строку и позволил исказить запрос в базу данных. Поэтому некоторые символы неплохо бы экранировать, чтобы запрос был корректен - интерпритатор запросов 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_real_escape_string, но они также используются для поиска по строке с помощью функции mysql LIKE. Поэтому, если строку вставляете в LIKE, то желательно также заменить _ на \_, % на \% самому.

Обобщение

Вывод 1:
для записи любых строк, полученных от пользователя, в базу данных необходимо обработать её минимум функцией mysql_real_escape_string, что защитит нас от любых нарушений sql-запроса, то есть, от sql-injection.

Вывод 2:
Любая информация от пользователя, которая выводится в браузере, должна быть обработана минимум функцией htmlspecialchars с тремя аргументами. Неважно когда - перед записью в базу данных, или собственно перед выводом.

Вывод 3:
Для фильтрации цифр используйте две функции - $var=abs(intval($var)), на выходе вы получите либо целое положительное число, либо ноль.

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

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

Комментариев нет:

Отправить комментарий