Злоумышленнику, чтобы использовать ваш код в своих целях, нужно сначала определить его слабые места. Это достигается зондированием вашего приложения и сбором как можно большего количества информации о нём. Самый распространённый и эффективный способ - это попытки вызвать ошибки приложения.. Предположим, например, что злоумышленник ввёл некорректные данные в поле login-формы и спровоцировал следующее стандартное сообщение об ошибке в PHP:
Notice: Undefined index: content in /usr/local/apache/htdocs/index.php on line 22
Что нам может поведать это сообщение? Очевидно, то, что в коде есть массив с индексом content, неопределённый в строке 22 файла index.php. Благодаря этому сообщению, у злоумышленника есть подсказка о потенциальной дыре, через которую можно замусорить данные приложения. Может быть, это неопределённый индекс где-нибудь в $_GET или в $_POST. В принципе, смоделировать разные контексты и определив, как меняется при этом номер строки, где возникает ошибка, вы можете даже воссоздать общую идею работы скрипта. Кроме того, вид сообщения может также предоставить более подробную информацию о расположении файлов, с которыми работает скрипт (если мы работаем с функциями файловой системы), формат запросов (если работаем с базами данных), и прочие данные. Начнём с того, что обычно пользователям просто не нужно получать сообщение об ошибке со всеми подробностями, если что-то пошло не так. Ну а при определённых обстоятельствах такие подробности могут стать дырой в системе безопасности.
Первый шаг в решении этой проблемы - всевозможные меры по сокращению появления ошибок в скриптах. В каждом уважающем себя PHP-приложении все переменные должны быть определены, или, по крайней мере, проверены isset()-ом, если речь идёт о суперглобальных переменных. Однако сколько бы не было продумано и предусмотрено в вашем приложении, предполагать, что вы учли любые обстоятельства, нереалистично. Поэтому, в "охраняемых" приложениях должны быть реализованы системы обработки и регистрации ошибок.
Смысл систем обработки и регистрации ошибок, по крайней мере, применительно к вопросам безопасности, - это отказывать злоумышленникам в доступе к информации о вашем приложении, но при этом предоставлять её разработчику. Правильная система регистрации ошибок запишет попытки скомпрометировать систему защиты вашего приложения и предоставит вам сведения о том, как и где надо усилить меры защиты.
Система регистрации ошибок в PHP может быть настолько простой или сложной, насколько вы пожелаете. В самом PHP есть выбор из нескольких встроенных механизмов обработки и регистрации ошибок. Например, PHP по умолчанию все ошибки выполнения скрипта выдаёт в браузер, но может быть сконфигурирован так, чтобы ошибки регистрировались, но не отображались. Это поведение контролируется из файла настройки php.ini директивами log_errors и display_errors. Включайте и выключайте сообщения об ошибках в зависимости от ваших программерских нужд. Обычная практика - это отображение ошибок без их регистрации на стадии разработки и отладки приложения. Для готового продукта всё наоборот: регистрация без отображения.
Как сообщения об ошибках PHP вываливаются в браузер, вы видели, а где PHP ведёт лог, если регистрация ошибок включена? Поведение механизма регистрации ошибок в PHP контролируется другой директивой, error_log. Значение может быть выставлено на имя файла, или на строку "syslog", или вовсе не выставлено (по умолчанию). В последнем случае, то есть когда значение error_log в php.ini не выставлено, то логи PHP ведутся за счёт средств web-сервера (например, Apache-вский error-log). Если значение выставлено на имя файла, то PHP будет писать лог в указанный файл, пока будут позволять системные разрешения. Если значение выставлено на ключевое слово syslog, то лог PHP будут вестись средствами журналирования операционной системы. Для UNIX-систем это стандартный системный журнал [syslog], для систем Windows NT и XP это журнал событий [event log].
PHP сам позаботится о регистрации ошибок, если вы используете встроенный обработчик ошибок, однако для своего обработчика (о нём речь пойдёт далее в статье) вам придётся всё сделать своими руками. Для подобных деяний PHP предлагает функцию error_log() со следующим синтаксисом:
error_log($message [, $message_type [, $dest [, $extra_info]]]);
В зависимости от выставленного значения необязательного параметра $message_type (ноль по умолчанию), произойдёт одно из нижеперечисленного:
$message_type равен нулю, то сообщение об ошибке $message будет записано в лог, указанный в директиве конфигурации error_log.
$message-type равен 1, то сообщение об ошибке $message будет отправлено по электронной почте на адрес, указанный в параметре $dest. Любые дополнительные почтовые заголовки можно указать в параметре $extra_info.
$message_type равен 3, то сообщение об ошибке $message будет записано в файл, указанный в параметре $dest. Примечание: Вы наверняка заметили, что для значения $message_type равного 2 поведение не предусмотрено. Это рудимент от PHP версии 3, где удалённая отладка поставлялась в стандартном релизе как его часть. Для PHP4 это уже не так. Функцию error_log() можно использовать для ведения логов в любой части приложения, но, как правило, она применяется как часть своего обработчика ошибок. Примеры - в мануале.
Понимание классификации ошибок PHP, почти также важнО как правильная настройка системы регистрации ошибок. Данная модель определяет, какие типы ошибок регистрируются, как и когда происходит регистрация. Чтобы осмыслить саму модель классификации, вам нужно знать, какие типы ошибок вы можете получить в PHP, и каково значение каждой из них. Эти знания вы можете почерпнуть из мануала или из представленного ниже списка:
E_ERROR означает, что в самом PHP или в одном из его расширений возникла серьёзная внутренняя проблема (например, невозможность выделить память).
E_WARNING обычно выдаётся с целью привлечь внимание к потенциальной ошибке в коде, из-за которой он возможно будет работать не так, как задумано. Пример: какой-либо встроенной функции передаётся скалярное значение вместо ожидаемого составного, например массива.
E_NOTICE - это наименее значительная встроенная ошибка PHP. Этот тип почти никогда не означает, что приложение работает неправильно. Сообщения об ошибках этого типа призвано предупредить разработчика о таких вещах, как использование переменных до инициализации.
E_CORE_ERROR по фатальности равно E_ERROR, но выдаётся только при запуске ядра PHP.
E_CORE_WARNING - не фатальный коллега E_CORE_ERROR и схож с E_WARNING. выдаётся только при запуске ядра PHP.
E_COMPILE_ERROR означает серьёзную ошибку во время компиляции движком Zend Scripting Engine.
E_COMPILE_WARNING предупреждение, нефатально, как и E_WARNING, генерируется во время компиляции движком Zend Scripting Engine.
E_USER_ERROR зарезервирован исключительно для использования с функцией trigger_error() (см. далее в статье). По умолчанию обработка та же, что и у E_ERROR: сообщение об ошибке и прекращение исполнения.
E_USER_WARNING зарезервирован исключительно для использования с функцией trigger_error().По умолчанию обработка та же , что и у E_WARNING.
E_USER_NOTICE зарезервирован исключительно для использования с функцией trigger_error(). В отличие от E_NOTICE, который игнорируется по умолчанию, PHP выдаст сообщение о E_USER_NOTICE пользователю. Директива конфигурации error_reporting определяет, какие сообщения об ошибках пишутся в лог или выбрасываются в браузер, когда оные ошибки случаются. Эта директива представляет собой "битовое поле", а это означает, что типы сообщений можно как угодно комбинировать с помощью логических операторов AND, OR, и NOT.В файле php.ini символ амперсанда (&) означает AND, символ конвейеризации (|) означает OR, а тильда (~) означает NOT. Таким образом, чтобы отображались или писались в лог только серьёзные ошибки, ваша директива примет следующий вид:
error_reporting = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR
Помимо стандартных типов ошибок директива error_reporting принимает специальный тип, E_ALL, представляющий все возможные типы ошибок сразу, то есть как будто вы OR-ами соединила все стандартные типы. Приведённая ниже директива заставляет отображать или писать в лог все типы ошибок, кроме ошибок из коллекции E_USER:
error_reporting = E_ALL & ~E_USER_ERROR & ~E_USER_WARNING & ~E_USER_NOTICE
По умолчанию директива error_reporting сообщает в браузер или лог обо всех ошибках, кроме E_NOTICE.
Вне зависимости от конфигурации PHP в части отлова ошибок, есть несколько способов контроля ситуации во время исполнения скрипта. Самый простой способ - использовать оператор подавления сообщений, @. Поставьте @ перед каким-либо выражением и PHP втихомолку проигнорирует ошибку, если таковая случится при вычислении выражения. Например:
<?php
echo @$myvar;
?>
E_NOTICE, даже несмотря на то, что $myvar неопределена. Ошибку эту PHP проигнорирует и не напишет ничего.
Другой способ изменить настройку управления ошибками в PHP - это использовать функцию error_reporting(). Данная функция непосредственно изменяет "внутреннее" значение конфигурационной директивы error_reporting. Синтаксис функции таков:
error_reporting([$error_value])
$error_value - это новое значение битового поля. В PHP определены константы для всех приведённых выше классов ошибок, и из них можно составить комбинацию с помощью логических операторов PHP. Результатом работы следующей инструкции станет то, что PHP будет сообщать только о серьёзных ошибках, как и в одном из предыдущих примеров:
<?php
error_reporting(E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
?>
Независимо от того, какое значение будет передано в качестве параметра в error_reporting(), на выходе мы всегда будем иметь целое число, равное ранее установленному значению директивы error_reporting. Такое поведение позволяет вам применить особый режим вывода сообщений об ошибках для небольшой части вашего приложения и с лёгкостью восстановить предыдущий режим:
<?php
$old_error = error_reporting(0); /* Сообщения об ошибках больше не генерируем */
/* тут какой-то код */
error_reporting($old_error); /* восстанавливаем предыдущий режим вывода сообщений об ошибках. */
?>
Помимо встроенных средств обработки ошибок PHP также позволяет вам написать ваш собственный обработчик. Определённый вами лично обработчик ошибок позволяет вам осуществлять полный контроль над тем, как ваше web-приложение будет реагировать на ошибки, сгенерированы ли они PHP или сброшенные функцией trigger_error(). Чтобы задействовать определённую вами обработку ошибок, необходимо определить следующим способом:
<?php
function my_handler($error_code, $error_msg [, $error_file [,
$error_line [, $vars]]]) {
/* Собственно реализация обработчика */
}
?>
Как описано в нашем примере, обработчик ошибок должен принимать параметры $error_code и $error_msg; первый - это класс ошибки (например, E_ERROR), а второй - это сообщение, привязанное к данному классу. Ваш обработчик ошибок способен принимать до трёх дополнительных параметров: $error_file, $error_line, и $vars. Первые два - это PHP-файл и строка в нём, где ошибка имела место. Последний параметр, $vars, представляет собой ассоциативный массив, в котором содержатся имя и значение каждой переменной, которые были в области видимости на момент возникновения ошибки.
Создав такую функцию, её надо зарегистрировать в PHP как действующий обработчик ошибок. Для этого используйте функцию set_error_handler(), синтаксис её таков:
set_error_handler($func_name)
$func_name - это строка с именем определённой вами функции (то есть, для моего примера, my_handler). Эта функция вернёт название предыдущего обработчика, то есть при желании можно это название сохранить и потом вернуть всё на место. При использовании определённого вами обработчика ошибок, держите в голове следующие моменты:
E_WARNING, E_NOTICE и коллекции E_USER. Для всех прочих ошибок будет вызываться встроенный обработчик, как если бы вы вовсе не определяли свой альтернативный обработчик.
error_reporting игнорируется. Какая-либо реакция на error_reporting() определяется также в обработчике.
die() или exit(), скрипт продолжит работу, невзирая на класс ошибки. Прекращение работы скрипта при необходимости определяется в обработчике. Последняя функция на сегодня, trigger_error(). Эта функция используется для генерации ошибки, определённой разработчиком. Синтаксис функции таков:
trigger_error($msg [, $error_code])
$msg - это строка с сообщением и описанием об ошибке, а факультативный параметр $error_code - класс ошибки из коллекции E_USER. Если второй параметр опускается, PHP автоматически применяет E_USER_NOTICE. Функция trigger_error() предназначена для использования в связке с обработчиком, определённым программистом; однако если такой обработчик не был определён, PHP среагирует на ошибку через стандартный обработчик.