LXF102:Доверительный PHP
|
|
|
Доверимся PHP
- В определенных кругах разработчиков безопасность PHP ставится под сомнение. Фрэнк Полманн развенчивает многие из этих мифов...
Все мы знаем, что PHP может быть источником весьма серьезных проблем с безопасностью. Регулярно обнаруживаются новые эксплойты, атакующие непосредственно интерпретатор; PHP-программисты часто имеют дело с системами управления контентом (CMS), нередко становясь при этом жертвами взломщиков.
Однако существуют весьма простые способы обеспечить безопасность, приняв во внимание то, на чем базируется работа PHP, а именно, web-сервер. За основу возьмем Apache, хотя часть последующих примеров применима также и к другим серверам.
Как запускать PHP-программы
Разбираться придется с несколькими различными случаями. Программы PHP могут, во-первых, выполняться как CGI-скрипты; во-вторых, запускаться в рамках модуля Apache; и в-третьих, программы PHP можно писать как CGI-скрипты, но выполнять как часть так называемого модуля FastCGI. Скрипты CGI вызывают особенно неприятные проблемы с безопасностью, ибо каждый CGI-скрипт выполняется как отдельный процесс. А каждый процесс, выполняемый web-сервером, наследует идентификатор пользователя (uid) web-сервера, что не есть хорошо, если кто-нибудь заполучит контроль либо над CGI-скриптом, либо над исполняемым файлом Apache: тогда выполнение Apache и всех других CGI-скриптов будут во власти атакующего.
Существуют, однако, способы держать PHP-скрипты в безопасности без обращения к средствам второй линии защиты, вроде применения chroot’а к Apache и оставшейся части пакета {L,M}AMP. Зададимся вопросом о способах присвоить каждому CGI-скрипту отдельный uid; в частности, поговорим о suEXEC. Но есть более фундаментальные приемы, с которыми нужно ознакомиться, прежде чем прибегать к средствам типа suEXEC и suPHP.
Первичное укрепление Apache
Практическую безопасность PHP можно подразделить на четыре части: безопасность файловой системы, безопасность Apache, уровень безопасности PHP-интерпретатора и безопасность PHP-клиента. Первыми тремя мы займемся целиком, а говоря о suEXEC, кратко остановимся на четвертой. Предположим, имеется стандартная схема размещения файлов: все файлы Apache расположены в /usr/local/apache2/.
ServerRoot /usr/local/apache2 DocumentRoot /usr/local/apache2/htdocs Основной конфигурационный файл /usr/local/apache2/conf/httpd.conf Параметры SSL /usr/local/apache2/conf/ssl.conf ErrorLog /usr/local/apache2/logs/error_log AccessLog /usr/local/apache2/logs/access_log cgi-bin /usr/local/apache2/cgi-bin Исполняемые файлы Apache /usr/local/apache2/bin
Можно, а иногда и нужно, иметь несколько каталогов cgi-bin; этим мы воспользуемся при разговоре о местоположении PHP-интерпретатора.
Права доступа и последствия
Следует принять в расчет два сценария:
a Различные пользователи не должны иметь возможность случайно или умышленно перезаписывать файлы или каталоги друг друга.
b Каждый пользователь может иметь дело с виртуальной копией Apache, избегая таким образом некоторых, но не всех, проблем с правами доступа.
Данная возможность отражается на производительности и безопасности, но в данном контексте мы ее обсуждать не будем.
Права доступа PHP CGI
Когда PHP скомпилирован для запуска с Apache, и используется файл конфигурации по умолчанию, рабочий режим PHP – это режим CGI-скрипта. Каждый CGI-скрипт, независимо от языка программирования, должен иметь необходимые права доступа, чтобы владелец скрипта мог запустить его. Это значит, что владелец скрипта должен уметь считывать и запускать сам скрипт, а Apache должно быть достаточно только прав на запуск. Если верить документации Apache, права доступа каталогов cgi-bin, где Apache ищет CGI-скрипты, должны быть таковы:
drwxrwxrwx
что также известно как права доступа 0777. Как мы увидим позже, все сценарии внутри каталогов cgi-bin должны восприниматься Apache как CGI-скрипты, и не иначе; отсюда следует, что надо привести в согласие права доступа всех скриптов, размещенных в каталоге cgi-bin (о файлах с данными и с контентом речь пока не идет). Права 0777 допустимы, но это рискованно, так как если вы предоставите их, любая брешь в защите приведет к тому, что люди смогут запросто перезаписывать и даже заменять CGI-скрипты, если они вызываются и модифицируются напрямую.
Строго говоря, единственный пользователь (или группа!), кто во время разработки и тестирования должен иметь право считывать, записывать и получать доступ к каталогу cgi-bin – это web-разработчик, или группа таковых. Тогда права на каталог cgi-bin снижаются до
drwxrwxr-x
они же – права доступа 775. Имеет смысл также предположить, что web-разработчику нужно считывать, изменять и выполнять файлы внутри каталога cgi-bin (и выводить их список!). По завершении разработки, когда CGI-скрипты попали на сервер, группе web-разработчиков в целом, по идее, незачем в них писать.
Выходит, права доступа для cgi-bin – 755, как и сделано по умолчанию на многих установках Apache и пакета LAMP (Linux, Apache, MySQL, и PHP). Они также подразумевают, что пользователь Apache, если таковой существует, сможет выводить список файлов в данном каталоге, исполнять и считывать их.
Права доступа CGI-скрипта
Так как всем остальным пользователям нужно уметь считывать CGI-скрипты, но только Apache приходится их выполнять, администратор должен установить всем CGI-скриптам права 644.
$ chmod 644 {любое_имя_файла}.cgi
Настройка Apache для PHP
Чтобы заставить web-сервер действовать по заданному сценарию, мы должны удостовериться, что прописаны определенные директивы Apache, и присутствуют определенные модули. Прежде всего следует убедиться, что пользователь Apache – единственный, которому система разрешает запускать Apache, и что этот пользователь – единственный член группы Apache. Назовем этого пользователя httpd, и группу тоже назовем httpd.
$ groupadd httpd $ useradd httpd -g httpd -d /dev/null -s /sbin/nologin
Нам нужно создать для Apache нового пользователя, чтобы пресечь любые попытки взломщиков «угадать» владельца скрипта во время работы. Мы не дадим самому Apache многих привилегий: у него даже не будет отдельного входа в систему. Владельцу скриптов и файлов данных, конечно, полагается быть web-разработчиком или кем-то из группы web-дизайна.
Данная мера безопасности окажется бесполезной, только если кто-нибудь сумеет «угадать» идентификатор web-сервера, но для этого требуется несколько больше, чем способность выполнять CGI-скрипты.
Еще нам понадобится возможность создавать другие каталоги cgi, чтобы объединять наши скрипты вместе по типам. Желательно группировать вместе PHP-скрипты, так как можно, а иногда даже нужно, иметь копию интерпретатора PHP, запущенную внутри CGI-каталогов. Обычно считается, что это – дыра в безопасности, но с ней можно управиться способом, которым мы потом займемся отдельно.
UID и GID созданного в системе Linux/Unix пользователя httpd, отображаемые в списке Linux-пользователей, должны совпадать с UID и GID пользователя и группы Apache.
Директивы http.conf, присутствующие в главном разделе конфигурации Apache, таковы:
User httpd Group httpd
Позаботимся, чтобы Apache мог выполнять CGI-скрипты. Это гарантируется модулем mod_cgi (он доступен как часть базового дистрибутива Apache), но существуют некоторые директивы, которые следует добавить к httpd.conf еще до работы модуля. Нам нужно сообщить httpd.conf, что CGI-скрипты могут запускаться, и откуда они могут запускаться. PHP должен запускаться из какого-либо каталога cgi-bin, независимо от того, один или несколько пользователей работают с CGI-скриптами. Мы должны начать с директивы, запрещающей всему, что располагается за пределами дерева web-сервера, запускаться владельцем или любым пользователем Apache. Потом мы сможем ослабить наши правила, но для начала лучше проявить максимум консерватизма.
Следующие директивы тривиальны, но совершенно необходимы для безопасных операций Apache:
<Directory /> Order deny,allow deny from all </Directory>
Это – стандартная директива, она гарантирует, что Apache не сможет обслуживать файлы по всей файловой системе, даже дерево web- сервера. Конечно, теперь придется потрудиться, чтобы Apache СМОГ обслуживать нужные файлы, так что добавим другую директиву, сообщающую, что Apache может обслуживать файлы данных и контента из DocumentRoot:
DocumentRoot /usr/local/apache2/htdocs <Directory /usr/local/apache2/htdocs> Order allow,deny Allow from all </Directory>
Для правильной работы PHP-скрипты обязаны восприниматься как CGI-скрипты.
Чтобы никто не вздумал разрешить выполнение CGI с использованием файлов PHP не из каталога /usr/local/apache2/cgi-bin (или любого другого, явно прописанного вами в директивах Directory), добавим
Options -Includes -ExecCGI
в главный конфигурационный раздел httpd.conf.
Нужно также назначить каталог, содержащий CGI-скрипты; это делается директивой SetHandler. Тот факт, что мы разрешаем исполнение CGI через директиву Options, на вид противоречит предыдущей директиве, но вы ведь помните, что нельзя выполнять только CGI-скрипты, не содержащиеся в указанном ниже каталоге.
<Directory /usr/local/apache2/cgi-bin> Options +ExecCGI SetHandler cgi-script </Directory>
Сперва, однако, не мешает предупредить Apache, с какими файлами следует обращаться как со сценариями PHP. Итак, в главном файле конфигурации говорим Apache, что мы используем скрипты CGI – это мы уже сделали, осталось сообщить, что мы используем файлы PHP:
AddHandler application/x-httpd-php .php
Вы можете добавить другие обработчики-handler’ы и, если захотите, новые расширения файлов, например, php3 или php5, но это на ваше усмотрение. Здесь приведен фрагмент httpd.conf: понадобится еще несколько директив, чтобы наш httpd.conf заработал, но советую добавить предыдущие директивы, в соответствии с нашими рекомендациями. Я добавил директиву ServerRoot, чтобы httpd.conf стал понятнее, и вот наши изменения:
User httpd Group httpd ServerRoot /usr/local/apache2 DocumentRoot /usr/local/apache2/htdocs Options -Includes -ExecCGI AddHandler application/x-httpd-php .php <Directory /> Order deny,allow deny from all
</Directory> <Directory /usr/local/apache2/htdocs> Order allow,deny Allow from all </Directory> <Directory /usr/local/apache2/cgi-bin> Options +ExecCGI SetHandler cgi-script </Directory>
Теперь нам понятна основная структура прав доступа и те изменения, что мы сделали в httpd.conf для стандартного укрепления Apache. Нужно также укрепить нашу установку PHP – неважно, выполняются ли приложения PHP как CGI-скрипты или внутри модуля Apache.
Еще о настройке Apache
Сначала поразмыслим о том, чего именно мы хотим – допустим, гибкости: например, возможности запуска PHP и как скрипта CGI, и как модуля. Если мы добавим еще одну директиву конфигурации, например
Action application/x-httpd-php /cgi-bin/php
к уже существующей директиве
AddHandler application/x-httpd-php .php
то сможем работать с любой установкой PHP, независимо от того, где располагается PHP-интерпретатор и запускается ли приложение PHP как скрипт CGI или в рамках модуля. Директива Action включается посредством модулей mod_actions, доступных по умолчанию в базовом дистрибутиве Apache. Это гарантирует, что все PHP-скрипты могут запускаться как скрипты CGI, так как PHP определяется как конкретный тип MIME, который должен запускаться как скрипт CGI. В итоге Apache сможет обрабатывать все PHP-скрипты.
Настройка и компиляция PHP
Нам следует запустить настроечный скрипт со специальной опцией перенаправления, чтобы иметь возможность поместить PHP-интерпретатор в любой указанный нами каталог. Обратите внимание, что PHP-интерпретатор может размещается в потенциально опасном месте, а именно, в любом общедоступном каталоге cgi-bin.
$ sudo ./configure \ > --enable-force-cgi-redirect \ > --prefix=/usr/local/apache2/php $ make $ sudo make install
Взгляните на опцию enable-force-cgi-redirect: вы только что разрешили копирование интерпретатора PHP в любой каталог cgi-bin. Опция настройки сделает невозможной любую попытку получить доступ к интерпретатору PHP через URL. В любом случае, исполняемые файлы PHP CGI не обрабатывают аргументов командной строки.
Другой путь: подготовить php.ini для скриптов PHP CGI
Мы также должны добавить директивы на уровень выше сервера Apache, в файл php.ini (файл настройки PHP). Удобной возможностью является ввод так называемой директивы doc_root.
doc_root хорошо работает, если при компиляции не используется опция конфигурации enable-force-cgi-redirect. PHP-интерпретатор в этом случае будет искать файлы PHP, начиная с каталога, упомянутого в директиве doc_root:
doc_root /var/www
прикажет PHP-интерпретатору искать скрипты PHP CGI и файлы данных, начиная с этого каталога. Бывает, что некоторые, а то и все, CGI-скрипты по соображениям безопасности расположены не в обычных каталогах cgi-bin. Тогда можно создать отдельное дерево каталогов и внести этот путь в doc_root. Помните, что это не будет работать, если PHP выполняется как модуль Apache.
Нам также следует сообщить PHP-интерпретатору, к каким файлам данных он получает доступ, добавив
open_basedir /var/www
или любой другой подобный каталог. Это следует поменять, если вы используете разные пути каталогов или, естественно, многопользовательский режим.
Запуск PHP как модуля
Для завершения картины запуска PHP и доступа к файлам с использованием PHP нам нужно поговорить о запуске PHP в качестве модуля. Это имеет решающие преимущества, так как производительность программ PHP в этой конфигурации наиболее высока. Но в этом случае сложнее держать разных пользователей в стороне друг от друга.
В то же время, разделение привилегий, хоть и не столь легко достижимое, все же проще с CGI-скриптами, основанными на suEXEC. Другими словами, использовать PHP как модуль в ситуации массового хостинга следует крайне осторожно. Но мы попробуем получить представление о том, как выглядит этот сценарий и что можно сделать, чтобы его обезопасить.
Убедитесь, что интерфейс между apache и его модулями доступен: он известен как apxs2. Первым делом нужно сменить каталог на то место, где располагается исходный код PHP, и выполнить настроечный скрипт вроде следующего:
$ sudo /.configure --with-apxs2=/usr/local/apache2/bin/apxs \ > --prefix=/user/local/apache2/php
Если модули еще не активированы, можете сделать это вручную: добавьте следующую строку в файл httpd.conf, позаботившись, чтобы расширения файлов, которые вы собираетесь использовать в вашей установке PHP, распознавались Apache:
LoadModule php5_module libexec/libphp5.so
Возможно, вам придется добавить директиву DirectoryIndex, чтобы отображался индексный файл.
DirectoryIndex index.html index.php
suEXEC
Наконец, классическим решением проблемы запуска CGI-скриптов под UID’ами, отличными от UID пользователя Apache, является suEXEC. И наоборот, если Apache установлен вместе с suEXEC, он не станет выполнять скрипты CGI под root’ом и/или администратором.
При правильной настройке suEXEC почти так же эффективен, как chroot, хотя chroot считается только второй линией защиты: suEXEC будет выполнять только те скрипты, правом запуска которых обладает только их владелец. Если, например, права на запись распространяются на группу или на весь мир, скрипт не выполнится; точнее, он не выполнится, если хоть какое-нибудь право доступа распространяется на весь мир. В основном будут запускаться скрипты с правами доступа 750.
Сам suEXEC может быть запущен только Apache. Группа, к которой принадлежит Apache – в нашем случае это httpd – имеет только одного члена, самого себя. Всему миру нельзя будет запускать ничего, если suEXEC включен. Apache следует переключаться между собой и другим пользователем, чтобы запускать CGI-скрипты от его имени. Чтобы достичь этого, нужно либо запустить Apache под root’ом (очень плохая идея), либо в оболочке SUID – тогда Apache сможет запускать процесс под другим ID для каждого динамического CGI-запроса. Таким образом, для каждого динамического запроса Apache выполняет два процесса: двоичный файл SUID и собственно сам скрипт.
Настройка suEXEC
Одной из причин, почему suEXEC, будучи довольно продуманным приложением, не сильно популярен, является довольно сложный процесс настройки: его необходимо сконфигурировать и скомпилировать вручную, так как suEXEC не есть часть дистрибутива Apache. Однако он полностью поддерживается Apache Foundation и чрезвычайно хорошо интегрируется с web-сервером. Некоторые дистрибутивы Linux или пакеты LAMP могут содержать уже настроенный suEXEC, однако это лучше проверить лично.
Вам придется немного поиграть с опциями настройки. Пожалуйста, обращайте пристальное внимание на детали: suEXEC чрезвычайно суров, когда дело касается ошибок в информации о пути к каталогу или UID’ов, которые не соответствуют выдаваемым базовой системой.
$ ./configure --enable-suexec \ > --suexec-bin /usr/local/apache2/bin/suexec
Предыдущую опцию следует проверить: она должна быть работоспособной и жестко закодированной для вашей системы. Если вы случайно ее пропустите, Apache даже не сможет найти suEXEC. Переменные PATH работать не будут. (Документация по Apache рекомендует /usr/sbin/suexec). Проверьте ваше систему на предмет размещения Apache по умолчанию и ваших требований.
> --with-suexec-caller=httpd
Теперь вы понимаете, почему мы так настаивали, чтобы Apache запускался под пользователем httpd. При использовании suEXEC не обязательно позволять Apache отступать к пользователю root или пытаться запускать Apache и PHP в системе, требующей каждому запросу выполняться от имени отдельного пользователя. Это возможно, но в таком случае вам следует перекомпилировать Apache с другой потоковой моделью.
> -- with-suexec-userdir=public_html
Если вы запускаете PHP-скрипты, на которые имеет права только определенный пользователь, отличный от httpd, вам следует указать здесь родительский каталог для всех пользовательских директорий; то есть независимо от того, кто пишет скрипты – Петя, Вася или Коля, PHP-скрипты должны быть доступны из каталога public_html, например, каталога html/cgi-bin. Несмотря на мое обещание не говорить о виртуальных хостах, пожалуйста, убедитесь, что все они совместно используют корневой каталог, упомянутый здесь!
> --with-suexec-docroot=/home
задает каталоги, начиная с которых suEXEC будет проводить поиск файлов и скриптов каждого пользователя.
Если виртуальные хосты и файлы .htaccess не используются, это должно соответствовать конфигурации, представленной файлом httpd.conf. Мы можем оставить установки в .config по умолчанию, но стоит проверить документацию Apache, чтобы понять, какие опции не совсем отвечают условиям вашей системы: suEXEC имеет много правил, но мало исключений.
Доступ suEXEC к PHP
Нам также нужно убедиться, что suEXEC позволяет обычному пользователю иметь доступ к PHP. Как уже говорилось, каждый пользователь должен иметь копию PHP-интерпретатора в своей каталоге cgi-bin.
Теперь следует подключить модуль Apache mod_rewrite. Сделав это, мы можем добавить следующие правила в httpd.conf:
RewriteCond %{REQUEST_URI} .\php
Они гарантируют, что любое преобразование URI затрагивает только PHP-файлы. Вам нужно убедиться, что все запросы, которые должен обрабатывать PHP-интерпретатор, действительно доходят до него; в то же время mod_userdir должен увидеть запрос и послать его в нужное место: интерпретатор php, расположенный в каталоге cgi-bin.
RewriteRule ^/~(w+)/(.*)$ /~$1/cgi-bin/php/$1/$2 [NS, L, PT, E=Redirect_STATUS:302
Флаги Rewrite гарантируют, что
- (NS): внутренние подзапросы игнорируются;
- (L): это последний запрос – после него не допускается запросов на перезапись;
- (PT): сейчас mod_rewrite остановит обработку, а mod_userdir примется за дело, и, наконец, переменной REDIRECT_STATUS будет присвоено значение 302, чтобы PHP мог обработать скрипт.
Этот последний штрих в заклинании завершит нашу попытку создать безопасный PHP-сервер. Существуют другие модули для содержания PHP-скриптов в безопасности, а их доступа к файлам – в четких границах. suPHP, которому требуется suEXEC, запускает только PHP-скрипты, безопасно и под различными UID’ами. LXF
Настройка безопасности
Поскольку мы будем иметь дело с настройками безопасности вашего web-сервера, само собой разумеется, что не рекомендуется трогать копию Apache, поставляемой с вашей системой. Проверьте, запущена ли копия Apache:
$ ps -aux | grep httpd
Обнаружив, что Apache невозмутимо накручивает обороты, на время остановите web-сервер. Если у вас Debian или Ubuntu или любая другая система на базе Debian, сработает следующее:
$ sudo /etc/init.d/Apache2 stop
Чтобы обеспечить эксперименты исключительно с копией Apache и PHP собственной сборки, установите Apache 2.2.6 из исходных текстов:
$ lynx http://httpd.Apache.org/download. Cgi $ gzip -d httpd-2.2.6.tar.gz $ tar xvf httpd-2.2.6.tar $ cd httpd-2.2.6 $ ./configure $ make $ make install
Ваша копия Apache будет установлена в /usr/local/apache2; если вы желаете установить его в другое место, видоизмените команду configure таким образом:
./configure -prefix=/path/to/Apache
Больше защиты
К проведенному нами исследованию безопасности нужно добавить новый уровень защиты: создать отдельный каталог cgi-bin, не являющийся частью установки Apache. Затем мы можем поместить туда PHP-интерпретатор и добавить следующие строки
<Directory /var/www/cgi-php> Order allow,deny Deny from all </Directory>
в файл httpd.conf. Причина, почему это работает, в том, что мы добавили внутреннюю переадресацию посредством директивы Action:
Action application/x-httpd-php /cgi-bin/php
Добавляя максимально строгий контроль внешнего доступа и озаботившись правами доступа к файлам, мы не прекратим работу PHP-интерпретатора, так как контроль доступа по сети работает только для внешней переадресации, а не для внутренней: он закроет любой прямой доступ к PHP- интерпретатору или каталогу cgi-bin из нелокального источника, но при вызовах правильным пользователем PHP-скрипты будут работать идеально.
Запуск suEXEC и PHP
Надо добавить к Apache модуль mod_userdir посредством apxs, и добавить директивы UserDir в файл httpd.conf, например:
UserDir public_html UserDir disabled root
suEXEC находит файлы по запросу пользователя, в данному случае – каноническому ~user, и приставляет к имени пользователя docroot. suEXEC может добавлять к имени пользователя общий подкаталог, что приводит к
/docroot/~username/publicfolder
Чтобы было совсем здорово, ему также нужно найти cgi-bin, что мы ранее уже разрешали, но только для пользователя Apache, а именно, httpd.
Так как же средний держатель учетной записи на хостинге Apache сможет получить выделенный cgi-bin?
Очень просто:
<Directory /home/*/public_html/cgi-bin/> Options +ExecCGI SetHandler cgi_script </Directory>