На главную Карта сайта Контакты
    Главная / Статьи / Основы безопасности PHP

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

    Данный материал для начинающих программистов.

    Содержание

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

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

    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		error_reporting(8191);		if (!DEBUG)		// Запись в БД или отсылка по почте вебмастеру.				if	($errno == E_ERROR ||					$errno == E_PARSE ||					$errno == E_CORE_ERROR ||					$errno == E_COMPILE_ERROR ||					$errno == E_USER_ERROR)								return true;			 -->			set_error_handler("errorHandler");		 -->	?>	

    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		...		if (check_admin($..., $...))				...		if ($user_level > 150)			?>	

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

    SQL injection и magic_quotes

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

    	<?php		$user = mysql_fetch_assoc(mysql_query("SELECT * FROM `users` WHERE `username` = "" AND `password` = """));	?>	
    опасна. Если пользователь введёт вместо пароля " OR `username` = "admin, то система впустит его как админа.

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

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

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

    	<?php		function stripslashes_deep($value)			$value = array_map("stripslashes_deep", $value);				 -->				elseif (!empty($value) && is_string($value))								return $value;			 -->				$_POST = stripslashes_deep($_POST);			$_GET = stripslashes_deep($_GET);			$_COOKIE = stripslashes_deep($_COOKIE);		 -->	 -->	?>	

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

    	<?php		function quote($value) $value = """.mysql_real_escape_string($value).""";			 -->			return $value;		 -->	?>	
    И используем её везде. Как только какие-то динамические данных отсылаются SQLу, сразу используем quote:
    	<?php		$user = mysql_fetch_assoc(mysql_query("SELECT * FROM `users` WHERE `username` = ".quote($_POST["username"])." AND `password` = ".quote($_POST["password"])));		?>	

    Проверка данных

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

    Старайтесь не фильтровать, старайтесь валидировать. Другими словами, не создавайте чёрного списка, создавайте белый. Вместо

    	<?php		if (are_bad_symbols($data)) boo();	?>	
    используйте
    	<?php		if (!all_good_symbols($data)) boo();		// Например:		is_numeric($data);		preg_match("/[a-z0-9_-]*/i", $data)		...	?>	
    Так вы будете уверены, что информация чиста и никаких неожиданностей не будет. Если вы составляете список запрещённых символов, всегда можете просмотреть какой-нибудь нехороший %00 и подобные, о которых, скорее всего, не догадываетесь.

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

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

    	<?php		include($_GET["module"] . ".php");	?>	
    Этот кусок опасен. Если злоумышленник введёт "../../../../../etc/passwd%00", будет рад, а вы — вряд-ли.

    Аутентификация

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

    	<?php		session_start();		$_SESSION["userid"] = 168;		session_write_close();	?>	

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

    Вывод

    Всё время думайте о данных в переменных $_GET, $_POST, $_COOKIE, как об атаке злоумышленника. Trust no one! :)

    Источник: http://www.habrahabr.ru/blog/php/20333.html