Защита на PHP

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

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

Итак, что же я там нашёл? В принципе, ничего особенного, за исключением того, что в некоторых, в том числе и "секретных" директориях, отсутствовали файлы index.html (или другие index.*, интерпретируемые как стартовые). Кроме того, не было соответствующих настроек прав доступа. Что из этого следует? При наборе в адресной строке такого адреса на директорию без index.* файла, злоумышленнику прямо в браузере откроется весь содержимое папки со всеми возможными последствиями (это уже в зависимости от того, что там хранится).

Как этого не допустить? Достаточно в каждую папку на сервере, если там нет файла index.*, поместить его туда. В самом файле можете писать что угодно — от "Вход запрещен" или пустого файла до перенаправления, например, на стартовую страницу сайта. Второй вариант предпочтительнее с точки зрения заботливого вебмастера в сторону добросовестных посетителей. Если кто-то подзабыл, напомню, как осуществить редирект:

<html>
<head>
    <META HTTP-EQUIV="Refresh" CONTENT="0; URL=http://site.com">
</head>
<body></body>
</html>

Ну, конечно, ещё оформить можно по своему усмотрению и т. д. и т. п.

Теперь пора заняться настройкой прав доступа (не путать с просто правами файла — тем, что можно делать с файлом). Для их настройки существует простое и одновременно мощное средство. Настройка осуществляется путём размещения на сервере файла .htaccess. Можно создавать несколько файлов .htaccess — по одному в разных папках вашего сайта. Действие файла распространяется на все вложенные папки, кроме папок, в которых имеется другой файл .htaccess. Данный файл является служебным файлом, вследствие чего он недоступен посетителям сайта даже для чтения.

Мы не будем рассматривать все свойства данного файла конфигурации, так как нас интересует только одно — защита файловой системы сервера (для тех, у кого жажда познаний неисчерпаема, ищите описание данного в интернете).

Для запрета или разрешения доступа нужно использовать директивы Deny или Allow соответственно. Перед данными директивами идёт директива Order, указывающая порядок следования директив Deny и Allow. Для лучшего понимания, давайте рассмотрим пример:

Order Deny, Allow
Deny from all
Allow from syte.com
Allow from 10.25.0.55

Поместив данные директивы в файл .htaccess, мы запретим доступ ко всем ресурсам в этой и вложенных папках всем, кроме компьютеров с адресами syte.com и 10.25.0.55.

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

Сессии в PHP

Нет, студенты, не пугайтесь. Сессии в PHP намного более приятные процедуры, чем привычные для вас, зимние или летние.

Когда были выпущены первые версии PHP, программисты столкнулись с серьезной проблемой, а именно с отсутствием глобальных переменных — то есть все результаты работы скрипта, хранимые в переменных, после его исполнения уничтожались и не были доступны в дальнейшем (наподобие того как в Паскале и других языках программирования, после завершения работы функции, уничтожаются переменные, объявленные внутри этой функции). Легче всего для понимания примеры, что ж, давайте, рассмотрим пример:

<?php
$test = "Этот текст задается в файле index.php";
echo $test;
?>

И есть файл test.php, содержащий следующий код:

<?php
echo $test;
?>

Если выполнить данные скрипты, то в результате работы первого скрипта мы получим надпись "Этот текст задаётся в файле index.php". Второй же скрипт выдаст нам пустоту, так как значение переменной $test не передалось второму скрипту (и не было в нём задано).

Тогда программисты и начали использовать Cookies для хранения глобальных переменных. Однако у этого метода есть большие недостатки. Начиная от громоздкости и заканчивая, наверное, самым неприятным — всё хранится на стороне пользователя (читать — хакера). Да и в конце концов, у пользователя может быть попросту отключены Cookies. Многие программисты в те времена перестали использовать PHP.

Однако, появление сессий всё изменило. Теперь вся информация хранилась на сервере, а идентификация пользователя проходила по уникальному идентификатору сессии. Но обо всём по порядку.

Поскольку далеко не во всех случаях скрипту нужно использовать сессии, то их использование нужно указывать явно. Для этого существует команда session_start();, вызов которой говорит серверу, что данная страница нуждается во всех переменных, связанных с пользователем. Сессию нужно открывать до того, как какие-либо данные будут отправлены пользователю, так что желательно вызывать её в самом начале скрипта.

// Файл index.php
<?php
session_start();
$test = "Этот текст задается в файле index.php";
session_register("test");
echo $test;
?>

Сессия запущена. Теперь перейдём и посмотрим результат:

<a href="test.php">работа сессии</a>

И файл test.php:

<?php
session_start();
echo $test;
?>

Открываем index.php, кликаем на ссылку и видим, что открывшийся test.php получил значение переменной $test. Обратите внимание, что в функции session_register("test") имя переменной нужно передавать без знака $. Таким образом, после задания переменной $test как глобальной для сессии, она будет доступна во всех дальнейших скриптах данной сессии.

Если переменная больше не понадобится, её можно удалить функцией session_unregister();

Также можно уничтожить саму сессию: session_destroy();

Теперь у нас достаточно знаний, чтобы написать механизм авторизации. Исполним его тремя файлами: index.php, auth.php и done.php. Файл index.php будет содержать форму для ввода логина и пароля. Данные из этой формы будут переданы для проверки файлу auth.php, который в случае удачной авторизации, даст пользователю доступ к файлу done.php.

<html>
<body>
<form action="auth.php" method="post">
Логин <input type="text" name="user_name"><br>
Пароль <input type="password" name="user_pass"><br>
<input type="submit" name="submit" value="вход">
</body>
</html>
<?php
session_start();
if ($submit) {
    if (($user_name == "login") && ($user_pass == "password")) {
        $login_user = $user_name;
        session_register("login_user");
        header("location: done.php");
        exit;
    }
}
?>
<html>
<body>
Неверный логин или пароль
</body>
</html>

Тут, давайте разберемся с кодом. Итак, с начала мы открываем сессию: session_start(); далее проверяем, были ли отправлены данные из формы: if ($submit). Это поможет избежать атаки на перебор примитивных брутфорсов. Проверяем введенные логин и пароль: if (($user_name == "login") && ($user_pass == "password")). В данном случае для простоты у нас только одна пара логин-пароль. В действительности же логины и пароли хранятся в файлах или базах данных. Позже мы обязательно рассмотрим, как и где хранить логины и пароли. Если были введены правильный логин и пароль, объявляем глобальную переменную $login_user и перенаправляем броузер на страницу done.php: header("location: done.php");.

И файл done.php

<?php
session_start();
if (!isset($login_user)) {
    header("location: index.php");
    exit;
}
?>
<html>
<body>
Вы залогинены под логином:
<?php
echo "$login_user";
?>
</body>
</html>

Теперь, зная принцип механизмов авторизации с использованием сессий, рассмотрим и исправим узкие места представленных скриптов.

А потенциально опасными являются следующие моменты:

Для устранения первой уязвимости желательно проделать все, что было описано в предыдущей статье: жесткий прием переменной только из массива POST, проверка $HTTP_REFERER, проверка и урезка переменной. Также можно записывать IP посетителя и после 3 неудачных попыток блокировать его на 15 минут. Однако я бы посоветовал не применять блокировку IP. Гораздо разумнее применить задержку авторизации. Осуществить задержку можно так:

sleep(1); //задержка на 1 секунду

Что же касается второй проблемы с защитой, там всё ещё легче. Несмотря на то, что любой желающий может передать переменную $login_user, содержащую произвольный логин скрипту done.php, всё же кое-что можно сделать. А именно удалить переменную (в PHP нет нужды объявлять переменные, поэтому и понятие удаления переменной можно сравнить скорее с очисткой переменной) с помощью функции unset(); после чего откроем сессию, в которой хранится значение переменной $login_user, взятое с сервера, т.е. истинное значение, на которое хакер никак не может повлиять. Сделать это можно так:

unset($login_user);

session_start();

if (!isset($login_user))
...

Как видите, если переменная $login_user и была передана взломщиком скрипту, мы очищаем её, а далее уже открываем сессию и, если там содержится переменная $login_user — т.е. если была произведена успешная проверка логина и пароля — то даём посетителю доступ к странице.

Теперь вы уже знаете немало о методах защиты своего сайта, в частности о закрытии лазеек для хакеров в PHP-скриптах. В следующей, заключительной статье, мы рассмотрим другие проблемы защиты скриптов на PHP, а также, как я и обещал, способы безопасного хранения логинов и паролей на сервере.