This Might Be Useful

Основы безопасности PHP

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

Содержание
Демонстрация ошибок ·
register_globals ·
SQL injection и magic_quotes ·
Валидация данных ·
Авторизация ·
Вывод ·

Демонстрация ошибок

Почему так часто я вижу, зайдя на какой-нибудь сайт что-то подобное этому:

Warning: Use of undefined constant LOCAL_SERVER - assumed 'LOCAL_SERVER' in /web/includes/page-definitions.php on line 13

Это одна из стандартных PHP ошибок, которая а) некрасива для пользователя; б) потенциально опасна. Поэтому их необходимо перехватывать и упорядочивать.

Во первых, функция error_reporting позволяет нам решить, какие ошибки мы хотим видеть. В принципе, достаточно просто выключить показ всех ошибок (error_reporting(0)), но нам нужно не это, потому что об ошибках мы хотим знать. Константа всех ошибок - E_ALL. В пятой версии появилась константа E_STRICT, показывающая строгие замечания по поводу кода. Разумеется, их желательно видеть, но они не входят в E_ALL, потому будем использовать числовое значение error_reporting(8191), которое вбирает всё, вплоть до новых ошибок шестой версии.

Примечание для любознательных: error_reporting(E_ALL | E_STRICT) не подходит, ибо тогда PHP 4 будет ругаться, не зная, что такое E_STRICT. С численным значением никаких проблем не будет.

Добавляем проверку на DEBUG - константу, выставленной в конфиге, и, с помощью set_error_handler, будем отлавливать ошибки в уже запущенном сервисе. Кстати, свой репортер ошибок должен возвращать true, иначе PHP выбросит стандартную ошибку.

Результат:
(Насчёт сравнения переменной с пятью параметрами я не уверен в выборе метода: in_array красивее, и гораздо медленнее, а switch case case быстрее, но совсем некрасиво. Красота - субъективное дело...)

PHP:
  1. if (!DEBUG) {
  2.   function errorHandler ($errno, $errstr, $errfile, $errline) {
  3.     // Запись в БД или отсылка по почте вебмастеру.
  4.     if ($errno == E_ERROR ||
  5.         $errno == E_PARSE ||
  6.         $errno == E_CORE_ERROR ||
  7.         $errno == E_COMPILE_ERROR ||
  8.         $errno == E_USER_ERROR) {
  9.       // Сообщение пользователю. Мол, «простите, облажались маленько»...
  10.     }
  11.     return true;
  12.   }
  13.   set_error_handler('errorHandler');
  14. }

register_globals

До версии 4.2.0 директива register_globals была в PHP включена по умолчанию. Привело это к тому, что многие привыкли, что если в форме есть <input type="text" name="username" />, то в PHP коде можно проверять if ($username == 'admin') ...

Однако это потенциальная дыра, которая привела ко множеству взломов. Поэтому к POST, GET, COOKIE переменным надо обращаться через superglobals $_POST, $_GET, $_COOKIE. Многим это показалось слишком трудно и стала очень популярной команда import_request_variables, возвращающая всё на круги своя. Так вот. Не делайте этого.

Другая проблема с register_globals:

PHP:
  1. ...
  2. if (check_admin($..., $...)) {
  3.   ...
  4.   $user_level = 169;
  5. }
  6. ...
  7. if ($user_level> 150) {
  8.   echo 'Boom!';
  9. }

Если пользователь — не администратор, а переменная $user_level не инициализирована (ей не придано значение 0 в начале скрипта, в надежде, что оно 0 автоматически), то нехороший человек может дописать в адресной строке foo.php?user_level=999 и получить доступ.

SQL injection и magic_quotes

Так популярна среди начинающих конструкция

PHP:
  1. $user = mysql_fetch_assoc(mysql_query("SELECT * FROM `users` WHERE `username` = '{$_POST['username'}' AND `password` = '{$_POST['password']}'"));

опасна. Если пользователь введёт вместо пароля ' OR `username` = 'admin, то система впустит его как админа.

Приведённый пример, разумеется, элементарен. Но если не решить проблему глобально, всегда можно пропустить какой-нибудь запрос, подверженный SQL injection. Для борьбы с этим разработчики PHP решили сделать так, чтобы вся информация, поступающая от пользователя, подвергалась обработке и все кавычки escapeились (перед ними ставится слэш, что делает команда addslashes). Что случилось? Вся информация от пользователя приходит со слэшами. Даже та, что вроде слэши получить не должна. Например, комментарии к статье. Мало того, это не 100-процентный способ защиты от SQL injection.

Решение. а) со всей входящей информации снимаем слэши, если они есть. б) Всю информацию, поступающую в SQL запрос фильтруем специально для этого созданной функцией mysql_real_escape_string (или аналогом для другой базы данных).

Снимаем слэши:

PHP:
  1. if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
  2.   function stripslashes_deep($value) {
  3.     if(is_array($value)) {
  4.       $value = array_map('stripslashes_deep', $value);
  5.     }
  6.     elseif (!empty($value) && is_string($value)) {
  7.       $value = stripslashes($value);
  8.     }
  9.     return $value;
  10.   }
  11.  
  12.   $_POST = stripslashes_deep($_POST);
  13.   $_GET = stripslashes_deep($_GET);
  14.   $_COOKIE = stripslashes_deep($_COOKIE);
  15. }

Создаём функцию для фильтрации (mysql_real_escape_string - длинно, да и привязанно к формату проверки. А если понадобиться поменять фильтр?)

PHP:
  1. function quote($value) {
  2.   if (!is_numeric($value)) {
  3.     $value = "'".mysql_real_escape_string($value)."'";
  4.   }
  5.   return $value;
  6. }

И используем её везде. Как только какие-то динамические данных отсылаются SQLу, сразу используем quote:

PHP:
  1. $user = mysql_fetch_assoc(mysql_query('SELECT * FROM `users` WHERE `username` = '.quote($_POST['username']).' AND `password` = '.quote($_POST['password'])));

Валидация данных

Валидируйте всё, что вводит пользователь. По умолчанию он злоумышленник.

Не проверяйте на наличие плохих символов. Проверяйте на отсутствие нехороших. Вместо

PHP:
  1. if (are_bad_symbols($data)) boo();

используйте

PHP:
  1. if (!all_good_symbols($data)) boo();
  2. // Например:
  3. is_numeric($data);
  4. preg_match('/[a-z0-9_-]*/i', $data)
  5. ...

Так вы будете уверены, что информация чиста и никаких неожиданностей не будет. Если вы составляете список запрещённых символов, всегда можете просмотреть какой-нибудь нехороший %00 и подобные, о которых, скорее всего, не догадываетесь.

Есть несколько команд, с которыми надо обращаться очень осторожно. Это include, require, readfile, eval, ``, system, exec, create_function, dir, fopen и подобные. Всегда посмотрите дважды, когда используете их, если в них используются данные, которые могут прийти из $_GET, $_POST, $_COOKIE, будьте уверены - кто-то обязательно этим воспользуется.

PHP:
  1. include($_GET['module'] . '.php');

Этот кусок опасен. Если злоумышленник введёт '../../../../../etc/passwd%00', будет рад, а вы - вряд-ли.

Авторизация

Не забывайте, что cookies редактируются ни чуть не сложнее, чем то, что видно в адресной строке. Поэтому всё, что приходит как печенье, потенциально - атака. Так что не надо хранить в cookies уровень доступа пользователя или его ID. Лучше всего дать PHP самому разбираться с этим, используя сессии.

PHP:
  1. $_SESSION['userid'] = 168;

Кстати, в cookies вообще хранить что-либо надо очень скромно и три раза подумать, а надо ли?

Вывод

Всё время думайте о данных в переменных $_GET, $_POST, $_COOKIE, как об атаке злоумышленника. Фильтрация, проверка, недоверие!

Источник статьи: Хабрахабр

· Отключение User Account Control (UAC)
· Включение классического логон-скрина в Windows Vista
· Основы масштабирования (web-проектов)
· Skype. Отключаем режим Supernode.
· Как вернуть себе контроль над директориями

- Коментировать
- Trackback

One Response to “Основы безопасности PHP”

  1. Trackback by Denya’s blog — Monday, October 1, 2007 at 23:07

Leave a Reply

code