Фатальная
проверка
Ранним
утром, еще до восхода солнца, у меня вдруг пискнула ася. Ожил старый
приятель, с которым я не разговаривал целых три месяца. На вопрос:
«Как дела?» - товарищ не ответил. Он лишь попросил меня проверить
его сервер на прочность. Как выяснилось позже, его машину постоянно
атаковал один и тот же хакер, оставляя после себя красочный дефейс
и папку /lameroot. Мне предстояла тяжелая работа по расследованию
этого преступления.
Согласись, что если человек располагает какими-либо данными (IP-адресом,
багом, через который совершался взлом, и т.п.), то разобраться в ситуации
- дело одного часа. С моим другом все обстояло несколько иначе: он
неважно знал *nix, поэтому особо не ворошил логи Апача. В результате
выяснилось, что обращения к его сайту вообще никак не логировались.
Конечно, можно было наладить Web-сервер и ждать новой атаки, но я
выбрал путь посложнее.
Анализируем
ситуацию
Для каждой моей атаки существует причина. В этой ситуации я решил
взломать сервер знакомого с одной целью - проследить за действиями
взломщика и блокировать ему доступ в дальнейшем. Первое, что я сделал,
- попросил товарища предоставить список правил файрвола. Через 5 минут
он с горем пополам запостил мне скрипт. Дело в том, что файр устанавливал
старый администратор, который профилировался исключительно на *nix.
Если верить списку рулесов, файрвол закрывал все порты, кроме 21 и
80. 22 порт начисто фильтровался и был доступен только для локальной
сетки. К сожалению, это все данные, которыми я располагал на тот момент.
У злобного хакерюги было два пути: ломиться на сервак либо через FTP-демон,
либо через бажный скрипт. Первый вариант я отбросил сразу: версия
ProFTPD была стабильной. Даже если у хакера и был приватный эксплойт,
то он не мог им воспользоваться, ибо на FTP был допуск всего у двух
системных пользователей. Учитывая то, что знакомый установил им сверхсложные
пароли, можно сделать простой вывод - атака проводилась через web.
Оставалось лишь залезть на WWW и тупо тыкать в ссылки, пока какой-нибудь
скрипт не сломается под жестким напором.
Дырявый
SMS
Первым делом я натравил своего осла на сайт приятеля. Контент радовал
глаза: портал был посвящен online-знакомствам. Все это дело вертелось
на знаменитом движке e-hoops последней версии (друг думал, что дыра
затаилась в этом проекте, но переустановка до последнего релиза не
помогла избавиться от назойливого хакера). Все скрипты, расположенные
на этом проекте, выполнялись в одном стиле, это значит, что они поставлялись
в виде специальных модулей для e-hoops. Ощупав все сервисы на сайте,
я даже усомнился в том, что атака проводилась через WWW. Быть может,
хакер таки откопал пароль на FTP и дефейсил сайт простым аплоадом
файла? А быть может, взломщик вообще обошел файрвол и таким образом
проник на сервер? От неопределенности у меня уже кружилась голова.
Но тут мой взор упал на неприметный линк, ведущий к скрипту отсылки
SMS-сообщений абонентам Мегафона.
С виду скрипт не представлял никакой опасности, но по некоторым признакам
отличался от других. Дизайн оставлял желать лучшего, а параметры почему-то
передавались методом GET. К моему сожалению, товарищ уже смылся из
аськи. Если бы он был онлайн, я бы просто попросил исходник сценария
и разобрался в коде.
Жаль, что я не был подключен к Мегафону :). Я не мог быть уверенным
в исправности скрипта, но все же мне требовалось проверить его функциональность.
Забив в поле для номера телефона рандомный мобильник и наколбасив
тестовый набор символов, я нажал кнопку «Send». Спустя несколько секунд
скрипт выплюнул какую-то дебаговую инфу. Приглядевшись, я узрел в
ней обмен между сценарием и SMTP-сервером и понял, что скрипт просто
шлет текст SMS на определенный e-mail в домене sms.megafonural.ru,
таким образом добиваясь отправки сообщения. У меня лишь не укладывалось
в голове, зачем нужно постить такую конфиденциальную информацию. Можно
ведь просто сообщить юзеру, что текст успешно отправлен.
Внезапно у меня появилась еще одна идея. А что если скрипт не проверяет
параметры на спецсимволы? Подставлять в качестве текста |id| или %00
глупо - PHP переварит их как миленький. А вот попробовать заюзать
баг функции system() вполне реально. Баг проявит себя в двух условиях:
если переменная не проверяется на символ «;» и если в сценарии используется
системный вызов. Изначально я попробовал вставить точку с запятой
в текст, но к желаемому результату это не привело. По крайней мере,
вывод скрипта говорил о том, что мессага успешно отправлена. Тогда
я решил вставить специальный символ в номер телефона. Но не просто
вставить, а воткнуть команду перед телефоном, обрамив ее «;». В ответ
сценарий вернул мне драгоценную информацию. Теперь я мог с уверенностью
сказать, что хакер-негодяй, беспокоивший моего приятеля, при взломе
пользовался этой веселой SMS-лазейкой. Но помимо дефейса, который
взломщик мог совершить и под nobody-правами, он оставлял загадочную
папку в корне файловой системы. А вот это было уже интересней. Как
он это делал, я пока не знал.
Порутай
через WWW
Из-за файрвола хакер никак не мог воспользоваться бэкдором. Таким
образом, взломщик мог либо закачать шпионский php-сценарий, который
выполнял произвольные команды, либо вообще наслаждаться дыркой в sms-скрипте
без привлечения дополнительных средств. Чтобы прояснить ситуацию,
я выполнил команду ls -la. К моему удивлению, Apache был грамотно
настроен: все документы имели отличный от nobody UID, что не позволяло
хакеру положить файл на Web. По-видимому, конф настраивал старый администратор,
потому как способностей моего приятеля для настройки suexec явно не
хватило. Я еще раз загрузил скрипт, чтобы узнать версию ядра. В ответ
я получил число 2.4.20. Поскольку все служебные префиксы отсутствовали,
стало ясно, что ядро никто не патчил и можно было добыть рута через
обычный mremap-exploit. Но сплойт запускает /bin/bash, возможностями
которого через WWW никак не воспользоваться. Следовательно, таинственный
взломщик модифицировал сплойт и выполнял под рутом произвольную команду.
Раньше я не думал об этом, но теперь у меня созрел план нового метода
обхода файрвола. Оставалось только применить его на практике.
Скачав сплоит себе на винт (www.security.nnov.ru/files/ mremap_pte.c),
я открыл его для чтения и стал просматривать исходный код. К моему
счастью, путь к шеллу был оформлен не в шеллкоде, а в отдельной переменной,
что значительно упрощало мою работу. Мне понадобилось лишь видоизменить
значение переменной launch на /tmp/cmd. Когда я этого добился, я создал
во временном каталоге файл cmd со следующим содержанием:
#!/bin/sh
/bin/chown root:root /tmp/exec
/bin/chmod 0755 /tmp/exec
touch /owned
Теперь
я бережно залил два хакерских файла в каталог /tmp. Для этого мне
пришлось воспользоваться командой curl, поскольку любимого wget’а
на сервере не было. Я начал собирать эксплойт, но тут возникла неожиданная
проблема - он почему-то не захотел компилироваться. Мне было влом
разбираться, в чем дело, и я выполнил это нехитрое действие на другой
машине, а затем просто портировал файл. После запуска злостного munremap'а
в корне успешно создался файл /owned. Можно сказать, что все шаги
моего злодейского коллеги уже были раскрыты, но азарт заставил меня
доработать метод выполнения рутовых команд через WWW.
Пишем
свой интерпретатор
Ты, наверное, не понял, почему в листинге скрипта /tmp/cmd промелькнул
загадочный /tmp/exec. Дело в том, что мне захотелось написать собственный
интерпретатор, при обращении к которому выполнялась любая команда
от пользователя root. Для этого мне пришлось написать одну элементарную
программу на си, а затем скомпилить ее в /tmp/exec. Прога должна обладать
всеми функциями интерпретатора, а запрос читать из произвольного файла.
Изучи исходник моего творения, быть может, ты сам не раз к нему обратишься
:).
Exec.c
- рутовый интерпретатор
#include <stdio.h>
int main() {
FILE *file; /* Файловая переменная */
char cmd[200]; /* 200 символов для команды должно хватить за глаза
*/
setuid(0);
setgid(0); /* Изменяем уид на 0 */
file=fopen("/tmp/c","r");
fgets(cmd,sizeof(cmd),file); /* Вытаскиваем из файла /tmp/c рутовую
команду */
system(cmd); /* И выполняем ее */
fclose(file);
}
Можно
было поступить проще и оформить команду в виде аргумента к шеллу,
но мне удобнее записывать запрос во временный файл. Оставалось залить
мой интерпретатор в /tmp/exec.c и собрать самопал компилятором.
Теперь, когда мне требовалось выполнить рутовую команду, я забивал
ее в файл /tmp/c, а затем запускал /tmp/exec. Перед воплощением идеи
в действительность я повторно стартанул эксплойт, для того чтобы он
засуидил /tmp/exec.
Ставим
капканы
Даже после проверки метода на практике я не остановился на достигнутом.
Мне хотелось удивить приятеля и доказать ему, что даже через Web хакер
может натворить ужасные вещи. Я вбил в качестве команды строку /etc/init.d/iptables
stop, которая должна деактивировать брандмауэр. Переварив злую команду,
скрипт радостно сообщил, что таблицы iptables стали пустыми :). Далее
я прителнетился на 22 порт, и... сервис без проблем выдал свой баннер.
Остальные действия я проводил на автомате: создал юзера с 0 уидом
(простой дозаписью в /etc/passwd и /etc/shadow), а затем залез по
SSH в систему. Надо отметить, что опция PermitRootLogin была почему-то
включена и это позволило мне проникнуть на сервер под суперюзером.
Если верить знакомому, хакер появлялся на его сервере примерно раз
в неделю. Я решил не фиксить самопальный скрипт, чтобы подловить негодяя.
Поэтому я ограничился тем, что включил логирование всех web-запросов.
Наступил вечер. Я поговорил с товарищем, который сердечно поблагодарил
меня за помощь в расследовании. Узнав про баг в SMS-скрипте, друг
разделил мое мнение, решив, что фиксить брешь пока не следует. Как
выяснилось, он попросил какого-то программера написать ему перловый
сценарий, который слал бы произвольный текст на определенный e-mail.
Затем, когда другу захотелось украсить свой сайт, он интегрировал
sms.php и перловую поделку. В результате получилось вот что: сценарий
обрабатывал входные переменные, затем приплюсовывал к номеру «@sms.megafon.ru»
и писал текст сообщения в файл. Финальной строкой был, конечно же,
системный запрос к sms.pl, который принимал следующий вид: system("perl
./sms.pl номер@sms.megafon.ru"). Интерпретатор заставлял считывать
временный файл с сообщением и засылать текст получателю. А вывод дебаг-инфы
был обусловлен тем, что мой приятель захотел протестировать работу
системы, а обнулить переменную $debug в файле sms.pl, как водится,
забыл. Все эти недоработки и отсутствие какой-либо проверки данных
привели к фатальному результату.
Через несколько дней, когда я уже запамятовал о проверке сервера,
приятель снова вышел на связь. Он сказал, что хакер снова оставил
дефейс и папку /lameroot :). То есть действовал по своей привычной
схеме. Вот дурачок :). Я тут же попросил оформить мне SSH-доступ,
чтобы выудить из логов важную информацию. Я не ошибся, взломщик действительно
нашел брешь в SMS-скрипте. Как выяснилось позже, он заливал скомпиленный
mremap.c с интегрированной в него командой, а когда злоумышленнику
хотелось выполнить другой запрос, он тупо пересобирал сплоит. Самое
главное, что горе-хакер не использовал проксика, и его адрес говорил
о том, что нарушитель является клиентом сети крупного московского
провайдера. Впоследствии мой приятель написал телегу в саппорт, и
дефейсы прекратились. Впрочем, возможно, они прекратились из-за того,
что он воткнул проверку на символ «;» в sms.php. Ибо нефиг :).
Где
здесь мораль?
Несмотря на то, что мне никто не заплатил за аудит, я был очень доволен
собой. Во-первых, я удачно попрактиковался в новом способе обхода
файрвола, во-вторых, мой приятель попросил меня последить за безопасностью
на его машине, в результате чего я получил легальный рутовый шелл
:). Ну и в-третьих, я еще раз убедился, что втыкать скрипты неизвестных
авторов опасно для жизни. Как видишь, это может привести к очень неприятным
последствиям.
Что
помогло мне при взломе?
1. Имея нюх на подозрительные скрипты, я быстро нашел лазейку на Web’е
даже при выключенном журналировании запросов.
2. Для удобства я написал простенький интерпретатор, который позволял
мне выполнять любую рутовую команду. Можно было пойти другим путем
- каждый раз заливать эксплойт с вкомпиленной внутрь командой (именно
так и делал хакер-дефейсер). Но мой способ реализуется намного быстрее.
3. Чтобы на скорую руку добавить нового пользователя, мне было достаточно
запустить две команды: первая дозаписывала служебную информацию в
/etc/passwd, вторая - логин и пароль в /etc/shadow.