PHP – довольно простой скриптовый язык, не требующий серьёзных навыков программирования. Однако, часто при разработке сайтов начинающий веб-мастер может, сам того не подозревая, создать уязвимость сайта себе или своему клиенту. Порой, для решения какой-либо задачи веб-мастер делает поиск в Интернет, и найденное решение помещает к себе на сайт, не вдаваясь в детали, что же именно делает скопированный им кусок кода, открывая очередную «дыру» в безопасности. Несколько простых советов, приведённых ниже, помогут предотвратить появление серьезных уязвимостей Вашего сайта.
Стоит заметить, что простые принципы, описанные здесь, относятся не только к PHP, но также и к любому другому языку, на котором создаётся веб-приложение.

Веб-приложение, каковым являтся любой сайт, как правило, работает по следующему сценарию:

  • получение запроса от пользователя или другого сайта по HTTP
  • обработка запроса PHP-скриптом
  • вывод результата запроса: генерация веб-страницы, HTML или другого кода (к примеру, RSS документа).

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

Содержимое параметров запроса

Протокол HTTP передаёт все параметры запросов в виде строк. В PHP значения параметров приходят как строки, даже если в скрипт ожидает число. Проблем с этим нет, строку «1» PHP с легкостью и незаметно преобразует в число 1, так что в PHP «1»+2 будет равно 3. Это очень удобно, но порой бывает опасно. Если полученное значение попадает напрямую в запрос к базе данных или используется для конструирования имени файла на хостинге, злоумышленник может составить такой запрос, который уничтожит данные в базе данных (SQL Injection) или осуществит доступ к закрытым файлам на хостинге.

$userId = $_GET[“uid”];
// А это грубое нарушение безопасности:
$user = query_database(“SELECT * FROM users WHERE id=$userId);

Заделывается такая «брешь» просто:

$userId = (int) $_GET[“uid”];

Теперь можно смело помещать это значение в SQL-запрос, никакого «мусора», приписанного клиентом там быть не может, теперь это просто число. Ещё можно проверить, не равно ли значение $userId нулю, чтобы не пытаться делать запрос в базу данных с заранее несуществующим ID.

В случае, если в SQL запрос необходимо поместить строку, принятую чрез HTTP-запрос, достаточно воспользоваться mysql_escape_string():

$name = $_GET[“name”];
$name = mysql_escape_string($name);
$user = query_database(“SELECT * FROM users WHERE name='$name');

В этом случае все попытки сфабриковать “зловредный” SQL-запрос, передав в $name хитровыдуманное значение, провалятся.

Заливка файлов (upload)

Аналогичная ситуация возникает с загрузкой файлов через форму заливки. Информация о закачанных файлах появляется в PHP скрипте в переменной $_FILES, причём в $_FILES['имя_параметра']['name'] находится имя файла, под которым он лежал на машине клиента. Реальное же имя, под которым залитый файл оказался на сервере, будет совсем другим — это будет имя временного файла, сгенерированное PHP автоматически. Для удобста веб-мастер может переименовать этот временный файл, назвав его так, как он был назван на машине клиента, взяв это имя из $_FILES.

Тут и сидит очередная потенциальная «дыра» в безопасности. Значение $_FILES['имя_параметра']['name'] клиент (точнее, недоброжелатель) может передать на сервер произвольным, поэтому его необходимо тщательно обработать, прежде чем оно станет пригодно для использовния в качестве имени файла на сервере. Что надо проделать? Примерно следующее:

  • Убрать путь к файлу, (все, что находится перед последним символом ‘/’ или ‘\’)
  • Можно, но не обязательно, проделать транслитерацию, приведя строку с именем файла к латинице.
  • Убрать все символы, которые нарушают читаемость или не поддерживаются в файловой системе сервера. Если точнее, оставить только символы из набора (A-Z, a-z, 0-9, ‘.’ точка, ‘_’ подчёркивание, ‘-’ минус). Этого должно быть достаточно.
  • Обеспечить, чтобы первым символом имени файла не была точка (так обычно обозначаются «скрытые» файлы, но главное, что имя файла «.» или «..» скорее всего недопустимо, а порой и опасно).
  • Можно, но не обязательно, проверить также расширение файла (текст, находящийся после последней точки). Тип содержимого файла так, конечно, не определить, но это можно сделать для обеспечения читаемости имени файла.
  • Удостовериться, что длина получившегося имени находится в разумных пределах: от 1 до 50 символов, к примеру.
  • Наконец, проверить, не совпадает ли полученное имя с уже существующим файлом, чтобы предотвратить «перетирание» важных данных.

Кажется, ничего не забыл, но этот список зависит от конкретной ситуации.

Неинициализированные переменные

PHP в принципе разрешает использование неинициализированных переменных. Правда, при этом он выдает предупреждение. Использование неинициализированных переменных — само по себе уже большое неряшество, но в совокупности с включенной опцией PHP register_globals это становится реально опасным. К счастью, опция register_globals отключена в PHP по умолчанию начиная с PHP 4.2.0. Эта опция автоматически регистрирует все параметры HTTP запроса и содержимое сессии в качестве переменных. То есть, если запрос имеет вид «http://site/script.php?x=y», то со включенным register_globals в скрипте автоматически будет присутствовать переменная $x со значением “y”. Если же скрипт использует переменную $x и не инициализирует ее, то возможна ситуация, когда клиент может повлиять на поведение скрипта, передав в запросе значение переменной, которое от клиента приниматься не должно. Как предотвратить такой тип уязвимости? Тоже просто: в php.ini должны быть следующие параметры:

  • register_globals = Off (это значение по умолчанию)
  • error_reporting = E_ALL (это нужно на машине разработчика, чтобы увидеть все предупреждения PHP)
  • error_log=”путь к лог-файлу PHP” — поможет отловить ошибки и предупреждения из скриптов, которые не выдают явно веб-страниц (скрипты редиректа, итд)

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

Возможно, если в голову придёт еще что-то по данной теме, я допишу, но пока ничего не пришло :)
Спасибо за внимание.

  • Print this article!
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • E-mail this story to a friend!
  • LinkArena
  • LinkedIn
  • MisterWong
  • StumbleUpon
  • Technorati
  • Twitter