Server / Памятка начинающим хостерам по затыканию дырок в PHP

Сегодня один перловщик спросил меня, а нельзя ли в PHP через suEXEC что-нибудь да как-нибудь запустить. Штудирование мануалов показало, что в случае mod_php нельзя, да и ненужно. Ну а в случае fastcgi — можно. И это хорошо, это безопасно, но сказка не о том, а о mod_php.
В мануале по PHP кроме функций exec и system, про которые я давно знал и вроде бы их запретил у себя на сервере, я нашел еще функцию popen. Я насторожился, потому что точно помнил — этой функции я в настройках PHP не запрещал!
Тут же из мануала был скопирован и запущен пример. Результат... удивил. Листинги большинства папок сервера, содержимое /etc/passwd, doc_root чужих виртуальных-серверов... Все это предстало перед моим взором :-) Конечно, виноват я. suEXEC'а нету, ведь нету же mod_php, да ну и не все файлы стояли с ограничением на чтение от all. Мой промах, признаю.

Вот код примера:

<? if(!isset($q)) { $q = 'ls -alp'; } ?><html>
<body>
<form method="post">
<input type="text" name="q" value="<?=$q?>">
<input type="submit">
</form>

<pre>

<?php
error_reporting(E_ALL);
$fp = popen($q, 'r');
$read = '';
while (!feof($fp))
{
$read .= fread($fp, 4096);
}
echo $read;
pclose($fp);
?>

</pre>
</body>
</html>

Мне это не понравилось и я полез в конфиг Апача httpd.conf. В список disabled_functions я дописал popen, перезапустился... Нулевой результат. Попробовал вызвать функцию exec — вызывается.

Шоковое состояние...

Что таким образом можно сделать?
Многое. Дело в том, что PHP запускается с правами Апача. А тем временем, Апач имеет доступ ко ВСЕМ файлам ВСЕХ виртуальных хостов. Такова необходимость, иначе он не мог бы их отображать и отдавать клиентам. Соответственно, их можно удалить. Этого мало, чтобы считать эту дырку критической?

Я не знаю, как дела обстоят у крупных хостеров, но почему-то мне кажется, что у 50% эта дырка есть (проверено на примере во время написания заметки)

Как же все-таки запретить пользователям виртуального хостинга через PHP делать гадости, а именно:
A) Читать чужие файлы из чужих папок
B) Вызывать любые системные функции

  • Пункт A

По первому пункту все оказалось просто. Не надо включать никакой Safe Mode, бога ради. Это зло. Это очень-очень неудобно и плохо. Тем более, есть методы проще.
Виртуальный хост в Апаче создается директивой . Это естественно, что для каждого виртуального хоста вы указываете DocumentRoot. Ну так давайте же сделаем так, чтобы скрипты, запущенные из под этого виртуального хоста не могли читать файла ниоткуда, кроме как из DocumentRoot'а. А еще, давайте сделаем так, чтобы сессии для каждого виртуального хоста сохранялись в отдельных папках. Это тоже логично, зачем же все перемешивать?

Добавьте в секцию следующие строчки

<IfModule mod_php4.c>
php_admin_value open_basedir /home/username/
php_admin_value doc_root /home/username/html/
php_admin_value upload_tmp_dir /home/username/tmp/
php_admin_value session.save_path /home/username/tmp/
</IfModule>

mod_php4.c замените на mod_php5.c в зависимости от версии PHP.

В этом примере DocumentRoot этого виртуального хоста:

DocumentRoot «/home/username/html»

Таким образом, первая проблема решена. Попробуйте сделать fopen(«/home/anotheruser/html/hacked.txt»,"a"); — не получится. 

  • Пункт B

Переходим ко второму пункту.
В данном случае я совершил две ошибки: директиву disable_functions я задал там, где этого делать не стоит; да и список запрещенных функций был ДАЛЕКО неполный.

Не повторяйте мою ошибку, не пытайтесь задавать параметр disable_functions в httpd.conf отдельно для каждого виртуального хоста. У ВАС НИЧЕГО НЕ ВЫЙДЕТ! Это раньше так можно было, наверное... потому что у меня именно так и было, а я об этом где-то как-то прочитал.

disable_functions надо задавать в php.ini. Чтобы узнать, какой php.ini используется у вас, выполните в шелле следующую команду:

php -i | grep ini

И с большой вероятностью вы увидите, где лежит ваш php.ini

В нем, в районе 199 строки есть что-то вроде:

; This directive allows you to disable certain functions for security reasons.
; It receives a comma-delimited list of function names. This directive is
; *NOT* affected by whether Safe Mode is turned On or Off.
disable_functions = 

Так вот. Список функций, которые НЕОБХОДИМО ОГРАНИЧИТЬ на виртуальном хостинге:
disable_functions = «popen,dl,set_time_limit,passthru,system,exec,proc_open,shell_exec,proc_close,symlink»

Это минимум. Я не уверен, что это все. Я с 90% вероятностью могу увтерждать, что ни один системный вызов на сервере не пройдет. А вот какую-нибудь еще гадость наверняка можно сделать.

UPD 2006.10.07
К списку опасных функций добавилась symlink.

  • Ссылки
  • Top 10 ways to crash PHP
    Довольно интересная статья. Правда, боюсь, она немного устарела.


Посты на схожую тематику:

  1. Server / FirstVDS.ru
  2. WordPress OpenID plugin and server
  3. Server / Moving
Запись опубликована в рубрике php, server, software. Добавьте в закладки постоянную ссылку.

8 комментариев: Server / Памятка начинающим хостерам по затыканию дырок в PHP

  1. Уведомление: Новости 2.0

  2. Уведомление: H.NET :: Выпуск #242 :: April :: 2006

  3. mivlad говорит:

    Апач имеет доступ ко ВСЕМ файлам ВСЕХ виртуальных хостов. Такова необходимость, иначе он не мог бы их отображать и отдавать клиентам. Соответственно, их можно удалить.

    Вряд ли. Вот считать оттуда пароль базы и удалить что-нибудь в ней — это да.

    А вообще, не исключено, что эта проблема на корню решается использованием php через FastCGI.

  4. Denya говорит:

    Угу. Надо почитать про плюсы и минусы такого решения.

  5. BOLK говорит:

    Конечно же, PHP можно запускать через suEXEC.

    А для PHP в режиме mod_php можно использовать mod_become и его родственников.

  6. Уведомление: Denya’s blog » News / Дырка в PHP4,5

  7. Джетман говорит:

    А что насчет запрета ini_set? ini_set ('max_execution_time', 9000); и вуаля!

  8. Denya говорит:

    Интересно :)

    Надо потестировать. Ведь таким макаром можно ВСЕ что угодно менять, так?