Введение в безопасность PHP:
Тысячи людей по всему миру промышляют взломом сайтов и целых сетей. Таким образом они зарабатывают себе на жизнь. В этой статье мы научим обходить самые популярные методы взлома 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)), но этого делать не стоит, потому что мы как раз и хотим видеть ошибки в php коде, вредящие безопасности.
Константа всех ошибок php — E_ALL.
В PHP 5 появилась константа E_STRICT, показывающая строгие замечания по поводу кода.
Разумеется, их желательно видеть, но они не входят в E_ALL, потому будем использовать числовое значение error_reporting(8191), которое вбирает всё, вплоть до новых ошибок PHP 6.
Примечание: error_reporting(E_ALL | E_STRICT) не подходит, ибо тогда PHP 4 будет ругаться, не зная, что такое E_STRICT. С численным же значением никаких проблем не будет.
Добавляем проверку на DEBUG — константу, выставленную в конфиге, и, с помощью set_error_handler, будем отлавливать ошибки в уже запущенном сервисе. Кстати, свой репортер ошибок должен возвращать true, иначе PHP выбросит стандартную ошибку.
Результат:
<?php error_reporting(8191); if (!DEBUG) { function errorHandler ($errno, $errstr, $errfile, $errline) { // Запись в БД или отсылка по почте вебмастеру. 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 включена по умолчанию.
Привело это к тому, что многие привыкли, что если в форме она есть, то в PHP коде можно проверять if ($username == ‘admin’)…
Однако, это потенциальная дыра в безопасности, приводящяя ко множеству взломов.
Поэтому к POST, GET и COOKIE переменным необходимо обращаться через superglobals $_POST, $_GET, $_COOKIE.
Многим это показалось слишком трудно, поэтому стала популярной команда import_request_variables, возвращающая всё на круги своя.
Другая проблема с register_globals:
<?php ... if (check_admin($..., $...)) { ... $user_level = 169; } ... if ($user_level > 150) { echo 'Boom!'; } ?>
Если пользователь — не администратор, а переменная $user_level не инициализирована
(ей не придано значение 0 в начале скрипта, в надежде, что оно 0 автоматически),
потенциальный взломщик может дописать в адресной строке foo.php?user_level=999 и получить доступ, тем самым посылая вашу безопасность php на те самые три буквы.
SQL injection и безопасность запросов
Среди начинающих программистов в php популярна данная конструкция:
<?php $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 { if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { function stripslashes_deep($value) { if(is_array($value)) { $value = array_map('stripslashes_deep', $value); } elseif (!empty($value) && is_string($value)) { $value = stripslashes($value); } return $value; } $_POST = stripslashes_deep($_POST); $_GET = stripslashes_deep($_GET); $_COOKIE = stripslashes_deep($_COOKIE); } } ?>
Создаём функцию для фильтрации (mysql_real_escape_string — длинно, да и привязанно к формату проверки. А если понадобиться поменять фильтр?)
<?php function quote($value) { if (!is_numeric($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, потенциально может стать атакой и нарушить безопасность. Поэтому, не стоит хранить в cookies уровень доступа пользователя или его ID. Лучше всего дать PHP самому разбираться с этим, используя сессии.
<?php session_start(); $_SESSION['userid'] = 168; session_write_close(); ?>
Кстати, хранить в cookies что-либо вообще не советую. Семь раз подумайте, а надо ли это вам…
Вывод о безопасности на PHP
Программируя на PHP, все время думайте о данных в переменных $_GET, $_POST, $_COOKIE, как об атаке злоумышленника, да будет вам безопасный php-код!
Отличная статейка, я вот все ищу в интернете как обезопасить свой сайт. Пишу свой движок и перехватываю все $_GET, $_POST, $_COOKIE а так же $_SESSION своими функциями. Так проще работать к примеру с SESSION когда функция сама запускает сессию (Session_start) и выполняет все часто используемые команды.