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

Введение в безопасность 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 выбросит стандартную ошибку.

Читайте также:  Используем GET запрос 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-код!

Рейтинг
( Пока оценок нет )
Блог о программировании, продвижении и дизайне.
Комментарии: 1
  1. Витя Первушин

    Отличная статейка, я вот все ищу в интернете как обезопасить свой сайт. Пишу свой движок и перехватываю все $_GET, $_POST, $_COOKIE а так же $_SESSION своими функциями. Так проще работать к примеру с SESSION когда функция сама запускает сессию (Session_start) и выполняет все часто используемые команды.

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: