Какие дыры делают програмисты в своих скриптах и технология срыва стека (с помощью которой можно исполнисть (или получить шелл))

Приветствую вас хаксоры, админы, юзеры и эээ..амеры =). В этой статье я расскажу всё о скриптах..у или постараюсь рассказать!

Как уже было замечено (я думаю всеми), что поток ламеров повалил мощным потоком в сеть, чтобы хакать..хакать.. ещё раз хакать.. И это понятно..т.к. баги в CGI себя очень сильно зарекомендовали в широких кругах! Почему? Очень просто... Не надо знать *ниха, не надо знать программирование, не надо врубаться как это работает и не надо расставаться со своим любимые браузером =). И даже учитывая то, что осталось только, попивая пиво/колу/воду, поработать мозгою чуток (если это требуется =) и задефейсить сайт....большенство зелёных людей тупо смотрят на экран и говорят: "А что дальше?". В общем я отвлёкся... Давайте сначала попробуем дать формулирувку скрипта: скрипт - это такая программка написанная на каком-либо языке программирования и предназначенная для того, чтобы предоставить пользователю сайта какой-либо вид услуг (удобство в навигации сайта, поиск по сайту и прочее). Скрипты пишутся в основном на 2 языках: Perl и C. Эти 2 языка себя хорошо зарекомендовали среди разработчиков. Каждый язык индивидуален как в синтаксисе написания, так и в мощи разрушительной силы =).

Начнём пожалуй с перловки. Большенство ошибок приходится на то, что не осуществляется или осуществляется, но не коректно..проверка на спецсимволы, используемые *нихом (/\|&%;`'). Давайте рассмотрим основные символы. Символ "|" (Pipe - труба, кранты =) перенаправляет второй команде результат выполнения первой команды (например, "cat /etc/passwd | grep root" выдаст на экран строчку типа root:x:0:0:root:/root:/bin/csh. А если выражаться ещё более просто, то сначала командой cat /etc/passwd в STDOUT записывается результат выполнения данной команды, т.е. просмотр файла /etc/passwd...и после этого команде grep root (поиск строки) передаётся результат первой команды..т.е. само содержимое файла /etc/passwd, записанное в STDOUT, где и производится поиск строки "root".). Следующий символ - ";", который просто даёт возможность поочерёдного выполнения команд, т.е., если мы напишем "ls;id" у нас сначала распичатается содержимое директории, в которой мы находимся, а после этого распичатается информация о нас, т.е. какой у нас uid (User id), gid (Group id), group (так и расшифровывается =). Символ "\" (он же..братный слэш =) - перенаправляет введённое шеллу, т.е., если ввести "\id", то выполнится команда..или просто ввести "\", после чего от вас будут ждать команды..Плюс если кто из вас не знает..есть такая фишка полезная как \\ ! Что она делает? Очень просто..аключённое в обратные слэши выражение выполняется и подставляется результат туда, где это вставлено, т.е. если мы введём cp /etc/passwd \pwd\ , то у нас произойдёт копирование passwd в current dir =). Остальные символы аналогичны, но специфичны =). Итак..раткий курс по *ниху прошёл. Теперь как это связать с перлом (да и другими языками...е. ограничимся С)? Очень просто! Давайте рассмотрим простую программку на перле:

#!/usr/bin/perl
 
printf "You enter: $ARGV[0]\n";

Давайте её разберём =). Программа в одно действие - выводит аргумент, полученный через командную строку на экран. Давайте запустим:

[crazy@localhost crazy]$ ./bug.pl you_lamer_dude!
You enter: you_lamer_dude!
[crazy@localhost crazy]$

Круто? =) Программка кажется нам совсем безобидной. А теперь давайте я дам на съедение нашей программке следующий аргумент:

[crazy@localhost crazy]$ ./bug.pl ;id
You enter:
uid=500(crazy) gid=500(crazy) groups=500(crazy)
[crazy@localhost crazy]$

WoW..=) Да тут ещё и команды исполнять можно.. что собственно произошло? Мы в программе использовали printf (или print..без разницы) с обычными (двойными) кавычками. А обычные кавычки в перле имеют такое магическое свойство..ак подстановка переменных сразу куда надо, передача управление шеллу и прочие полезности.. Вот так вот! Не такая уж и безобидная вещица!

Теперь встаёт вопрос: "А как этого избежать?" Чтобы не допускать таких оплошностей нужно проверять строку, передаваемую printf'у, на спецсимволы. Давайте добавим в код проверку и удаление спецсимволов и посмотрим, что из этого выйдет:

#!/usr/bin/perl
 
$str = ARGV[0];
$str =~ s/[;<>\*\|'&\$!?#\(\)\[\]\{\}:'`""%\\\/]//g;
printf "You enter: $str\n";

Теперь давайте запустим программу:

[crazy@localhost crazy]$ ./bug.pl |F`U\C/K;E&D%
You enter: FUCKED
[crazy@localhost crazy]$

Теперь мы понимаем из-за чего все (практически) проблемы! А что же касается %00 (он же "мёртвый байт", который отсекает всё, что стоит за ним), то тут ничего нового нет: обрубать спецсимволы надо!

Ну чтож..все основные вопросы по основным багам мы разобрали. Теперь же стоит поговорить о таком могучем языке, как Си и ещё раз замолвить слово о "срыве стека". Что это такое? Если не знаете, то поясняю - это такой тип атаки, при которой путём специальных махинаций злоумышленник получает то, что он хочет (обычно это шелл с привилегиями программы, над которой собственно и надругались). Обычно такую атаку проводят на suid'ные программы с привилегиями root'а! Думаю почему объяснять не нужно? В чём же суть данной атаки? А суть в том, чтобы после вызова подпрограммы, которой передаются параметры из внешнего мира, в программе, над которой мы издиваемся..был исполнен нужный нам код, который пишется на ассемблере с учётом ОС (ибо структура и принцип работы каждой ОС различен)!

Давайте разберём эту проблему более подробно на примере! Допустим у нас есть программка, где есть некая подпрограмма:

int subprog(char *str){
  int count;
  char buf[255];
  .....
  strcpy(buf,str);
  .....
  return 0;
}

Вроде ничего страшного нет! Хотя это не так! Как известно..Си - это тот, язык, который даёт неограниченные возможности программисту и полную волю в действиях. И эта вот свобода действий иногда сказывается на безопасности сис-мы! В данном случае, у нас имеется статистический буфер, т.е. с ограниченной длиной, что уже накладывает кое-какие ограничения. Плюс у нас есть крутая фишка, как strcpy, которая копирует содержимое одной строки в другую. Вроде бы всё ок! Функция пашет нормально! Но давайте рассмотрим нестандартную ситуацию: передадим функции такую строчку, длина которой превышает длину переменной buf, куда идёт копирование. Что же тогда произойдёт? А есть несколько вариантов дальнейшего поведения программы: она может продолжить свои работу, но какие-то значения переменных или ещё чего будут подпорчены либо программа критически остановится =). Всё зависит от структуры самой функции. И теперь глянем на стек и посмотрим что же происходит..

До вызова функции стек имеет такую форму: (будем считать, что стек имеет форму обоймы от АК-47 =)

|         |
|         |
|         |
|         | <-верхушка стека, в которую будут записываться новые данные
|XXXXXXXXX| <-какая-то уже занятая
|XXXXXXXXX| <-часть стека
|_________| <-....
 
Теперь глянем на стек после вызова функции:
 
|         |
|         |
|         | <- верхушка стека
|buf[255] | <- наш буфер
|  count  | <- int count;
|ret_addr | <- адрес возврата из функции
|   str   | <- параметры передаваемые функции
|XXXXXXXXX|
|XXXXXXXXX|
|_________|

Итак..после вызова функции..функция сохраняет параметры передаваемые ей в стек, после чего сохраняется адрес возврата из функции, после чего сохраняются описанные пременные в функции..(в каком порядке всё сохраняется видно на рисунке...е. снизу-вверх, влево-вправо).

После этого выполняются какие-то команды..какие - не важно. И потом встречается такая вот функция strcpy(buf,str), которая копирует переданный параметр в буфер. Т.е. в обл. памяти, отведённую в стеке для переменной buf, произойдёт копирование str! А, если учесть, что длина строки, которая копируется, превышает длину буфера, то последствия можно угадать =). Давайте глянем на стек до копирования:

|?????????| <- место занятое
|?????????| <- нашим буфером
|  count  |
|ret_addr |
|   str   |
|XXXXXXXXX|
|XXXXXXXXX|
|_________|

После копирования длинной строки у нас происходит вылез за рамки обл., отведённой для буфера и затерание других данных находящихся снизу..Т.е. где-то так:

|?????????| <- буфер уже после
|?????????| <- копирования в него
|?????????| <- длинной строки
|????addr | <- ...
|   str   |
|XXXXXXXXX|
|XXXXXXXXX|
|_________|

Как видно, после копирования.. нас произошло затирание других данных. Но как это используют хакеры? Очень просто (точнее не так уж это и просто =)..я бы даже сказал трудно)! Допустим длина строки настолько велика, что затирает адрес возврата из функции (как это показано на рисунке выше).. что это означает? А то, что после выполнения функции..альнейшая работа проги продолжится по адресу возврата...кто изучал ассемблер поймёт. А так как у нас адрес возврата подпортился, то программа прыгнет на некий адрес в памяти..уда ей прыгать запрещается...и следовательно будет сброшена кора (в *нихе, если не известно...все вылеты программы фиксируются в так называемом core файлах, чтобы потом понять из-за чего произошёл вылет...т.е. core - дамп куска памяти). А теперь давайте представим, что хакер пишет специальный код на ассемблере специфичный для данной ОС, который запускает шелл..после чего генерирует специальный запрос, передаваемый функции, который меняет адрес возврата из функции так, чтоб он указывал на ассемблеровский код. Если рассматривать стек, то мы увидим следующее:

|?????????| <- пустышка (nop)
|*********|<-| <- хакерский
|*********|  | <- код
|new_addr |--|
|   str   |
|XXXXXXXXX|
|XXXXXXXXX|
|_________|

В результате этого после выполнения функции программа прыгает по новому "адресу возврата", который указывает на хакерский код...после чего происходит выполнение данного кода с привилегиями владельца suid-программы. А если у нас владелец файла - root, то мы получим руутовский шелл..у а далее без коментариев =).

А как этого избежать? Ну можно использовать несколько методов: использовать динамический массив, устанавливать контроль над размером строки, а так же использовать такие функции, которые контролируют длину копирумой строки: strncpy! В общем ясно наверно?! =)

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

На этом, кибер-люди, я заКОНЧУ =).

Hosted by uCoz