Сетевая оборона на PHP

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

Большинство таких атак происходят в результате использования "дыр" в серверных скриптах. Именно о прикрытии этих самых лазеек для хакеров и будет рассказано в данной статье.

Итак, как было сказано, хакер может получить доступ к сайту через серверные скрипты (конечно, есть множество других возможностей, например, украсть пароль - трояном, снифером или даже нагло, с компьютера пользователя, но это уже отдельная тема). Почему именно серверные? Да потому что с клиентскими - теми, что исполняются на машине посетителя сайта, он ничего не поделает. Они не имеют никаких прав доступа к серверу, разве что могут получать от него информацию, да и только, но не в коем случае, клиентские скрипты не смогут самостоятельно изменить что-то на сервере.

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

Прежде всего, хочу сказать, что представленные тут примеры не гарантируют на 100% того, что вас никто не взломает, такое просто невозможно. Всегда, даже в самых распространенных и совершенных системах есть узкие места, пример тому сенсация полугодовой давности, когда на сайтах по всему миру в запросах на всеобще признанном языке SQL (Structured Query Language - структурированный язык запросов) была найдена грубейшая ошибка, получившая название SQL Injection. Но при этом вы увидите самые частые фатальные ошибки в защите и сможете на должном уровне защитить себя от атаки не только любителя, но и профессионального хакера.

Для начала вам нужно запомнить только одно - никогда не верить данным, полученным от посетителя - ведь они могут представлять собой вредоносный скрипт, который к стати, может быть как серверным, так и клиентским (будет работать на машинах посетителей). И этого вполне хватит, конечно, если в самих скриптах недопущены критические ошибки и нормально настроен сам сервер, но обо всем по порядку.

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

В качестве первого шага к защите, ограничьте длину имени и адреса электронной почты. Это не только улучшит безопасность, но и предотвратит попытки некоторых людей оставить абсурдно длинные имена. Для этого добавим атрибут maxlength=25 к полю ввода:

...
<input type="text" name="user_email" maxlength="25">
...

Этот метод ограничит возможность ввода более 25 символов в поле. Однако, такой способ не удержит опытных хакеров. Они могут в адресной строке передать что-то вроде:

...
guest.php?user_email=ha_ha_ha_slabaja_zashita_ha_ha_ha_tyt_bil_super_haker
...

Поэтому давайте усилить защиту, добавив в начало PHP-скрипта следующий код:

<?php
$user_email = $_POST['user_email'];
...

Здесь мы получаем значение переменной $user_email из соответствующих полей POST-массива. Не забудьте также изменить форму для отправки данных, явно указав метод передачи — method="post":

...
<form action="guest.php" method="post">
...

Будет ли хакер способен что-то сделать? Безусловно. Даже при использовании метода POST данные не передаются через адресную строку, но их все равно можно подменить, используя различные инструменты — от стандартных утилит Windows до языков программирования вроде Delphi. Например:

...
POST /guest.php HTTP/1.0
user_email=vetaki_ja_tebla_vzlomal...
...

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

Что делать в таком случае? Паниковать? Конечно, нет. Но об этом позже.

Если хакер попытается вот так нагло передать данные, мы можем остановить его следующим образом:

<?php
$referer = getenv("HTTP_REFERER");
if (!ereg("^http://my.domain.com")) {
  echo "Hack off";
  exit;
}
...

Как видите, мы проверяем, послан ли запрос с одной из наших страниц, открытых в броузере (наш домен - http://my.domain.com). Если всё верно — выполняем что надо, ну а если нет — выводим поздравление хакеру: "Hack off" и заканчиваем работу скрипта: exit.

Ну что, уже ликуете? А зря. Напомню что переменная HTTP_REFERER формируется броузером посетителя, то есть на стороне клиента (читать — хакера), и из всего этого следует, что и в её подлинности мы не можем быть уверены. Подделать её также несложно, как и POST запрос.

Уже устали и не верите в свои силы против этих всемогущих хакеров? Не стоит. Враг, хоть и не выдаёт себя, но уже окончательно устал; до данного этапа дойдут в лучшем случае 5-10% всех пытающихся. Так что, не будем разворачиваться у самого финиша, нанесем сокрушительный удар.

Всегда всё верно говорят, что со стороны хакеров любая система имеет уязвимости. Но я ещё ни разу, почему-то, не слышал, чтобы говорили про обратное — ведь у любой системы есть места, где хакер уже беспомощен. Самое время применить специальное вооружение админов. А именно: безысходность выполнения PHP сценария. Как бы хакер не изощрялся, если стоит exit; — значит exit, и точка.

Итак, допустим нам попался такой редкий хакер, что прорвался сквозь всю нашу предыдущую линию обороны и может спокойно посылать серверу переменные любой нужной длины, откуда хочет. Ну и пусть, а мы добавим сразу после проверки HTTP_REFERER и присвоения значения из массива (например, с POST) жёсткую урезку строки:

...
$user_email = substr($user_email, 0, 25);
...

Теперь мы имеем переменную $user_email длиной в 25 символов (если её исходная была больше, остальные её символы были отброшены), и ни один хакер не в силах этого поменять.

Так, но 25 символов все еще представляют опасность. Разумеется, только в том случае, если это вредоносные инструкции хакера-неудачника, так как дальше мы их профильтруем и удалим/заменим спецсимволы или же вовсе заблокируем.

Какие символы следует блокировать? Это зависит от поля, например, в имени это могут быть все, кроме букв из алфавита, пробела, цифр, ну и пусть знака _. Таким образом, нам следует поступить, например, так:

if (preg_match("/[^(\w)|(\x7F-\xFF)|(\s)]/", $user_name))
{
  echo "В имени есть запрещенные символы...";
  exit;
}

Теперь имена в гостевой будут чистенькими и ровненькими.

Для адреса электронной почты следует разрешить собаку @ и точку, а пробелы и русские буквы запретить.

Для тела сообщения также < следует менять на &lt; а > на &gt; например, так:

$message = preg_replace("<", "&lt;", $message);
$message = preg_replace(">", "&gt;", $message);

А знаки переноса строки на тег переноса <br>:

$message = preg_replace("(\r\n|\n|\r)", "<br>", $message);

Можно использовать и специальные функции PHP - htmlspecialchars(), nl2br() и другие, в общем, думаю, сами разберетесь, а то от темы уходим (а если не разберетесь - мыльте).

Также может быть уместна проверка на пустые сообщение или имя пользователя. Ее можно осуществить как стандартной функцией empty() так и просто проверив, не равна ли переменная "" например:

<?php
if (empty($message)) {
    echo "Пустое сообщение оставлять не стоит";
    exit;
}

или вот так:

<?php
if ($message=="") {
    echo "Пустое сообщение оставлять не стоит";
    exit;
}

В общем, дело вкуса.

<?php
$RIP=$GLOBALS['REMOTE_ADDR'];

Таким способом будет возможность и вычислить неудачника-взломщика или, скажем, ограничить попытки, не давая в сутки более, например, 2 раз оставлять сообщения в гостевой книге. Но я бы не стал так делать. Причин много - начиная от прокси серверов и заканчивая тем, что диал-ап до сих пор господствует на просторах СНГ.

<script language="JavaScript">

function checK(f) {
    if (f.email.value=='') {
        alert("Укажите адрес почты.");
        f.email.focus();
        return false;
    }

    if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,4})+$/.test(f.email.value)) {
        return true;
    }

    alert('Неверный адрес почты.\nПопробуйте еще раз.');
    f.email.select();
    return false;
}

</script>

<form name=f action="guest.php" method="post" onSubmit="return checK(this)">
    <input type="text" name="email" value="введите ваш e-mail" onfocus="if (this.select) this.select()" onclick="if (this.select) this.select()" size=28>
    <input type="submit" name="subscribe" value="Ок">
</form>

Как вы видите, после клика на кнопку Ок, данные, перед передачей скрипту guest.php, проверяются функцией checK. Если введенный адрес пуст или содержит запрещенные знаки, пользователь получит сообщение: "Укажите адрес почты." или "Неверный адрес почты.\nПопробуйте еще раз." соответственно (\n - перенос строки). При этом обратите внимание, что сообщения будут в окне alert() и никакой перезагрузки страницы даже не произойдет: return false. А курсор выделит ошибочный ввод: f.email.focus(); или же f.email.select(), что очень удобно для пользователя, особенно если на странице поле для ввода не одно.

Вы еще тут? Так и знал, уже побежали за компьютерами защищать свои творения и дорабатывать юзабилити. Ну что же, мне остается лишь сказать, что в ближайшее время вы сможете прочитать в МК некое продолжение данной статьи, в котором я опишу всевозможные виды авторизации пользователей, отслеживания сеанса и другие интереснейшие и полезные вещи. Если есть вопросы — смело пишите. До скорых встреч.