DoS для PHP через imap_fetchbody
Пример исследования дампа памяти для нахождения ошибки
Как оказалось, если передать функции imap_fetchbody() параметр $secton, длина которого больше 1004 байт, PHP падает по segmentation fault. На некоторых конфигурациях с применением некоторых усилий (подробности, понятное дело, разглашаться не будут) получалось вместе с PHP положить и Apache.
Пример кода, убивающего PHP:
Предполагается, что $imap_handle — ресурс, возвращенный функцией imap_open(), $message_number — номер существующего сообщения в почтовом ящике.
Анализ посмертного дампа памяти показывает, где происходит сбой:
...
Program terminated with signal 11, Segmentation fault.
#0 memcpy () at ../sysdeps/x86_64/memcpy.S:398
398 ../sysdeps/x86_64/memcpy.S: Нет такого файла или каталога.
in ../sysdeps/x86_64/memcpy.S
(gdb) bt full
#0 memcpy () at ../sysdeps/x86_64/memcpy.S:398
No locals.
#1 0x0000000000678788 in _estrndup (s=0x7ff1797aaf4e "", length=2136037704) at /usr/include/bits/string3.h:52
No locals.
#2 0x00007ff1799ce292 in zif_imap_fetchbody () from /usr/lib/php5/20090626/imap.so
No symbol table info available.
#3 0x00000000006e611a in zend_do_fcall_common_helper_SPEC (execute_data=0x7ff17f3e3068) at /build/buildd/php5-5.3.2/Zend/zend_vm_execute.h:313
opline = 0x7ff17f5141a8
should_change_scope = 200 '\310'
...
Видно, что ошибка произошла в функции zif_imap_fetchbody() (что соответствует PHP-функции imap_fetchbody()), когда та вызвала функцию estrndup() для пустой строки (адрес строки — 0x7ff1797aaf4e), но при этом передав ошибочную длину строки (length=2136037704). Значение length примерно равное двум гигабайтам в любом случае должно настораживать.
Пересоберём php5-imap с отладочной информацией:
export CFLAGS
phpize
./configure --with-kerberos --with-imap-ssl PHP_OPENSSL="yes"
make && sudo make install
Повторим тест. PHP по-прежнему должен упасть. На этот раз у нас будет больше информации о сбое:
#0 memcpy () at ../sysdeps/x86_64/memcpy.S:398
No locals.
#1 0x0000000000678788 in _estrndup (s=0x7ff8881ddf4e "", length=2381609288) at /usr/include/bits/string3.h:52
No locals.
#2 0x00007ff8884013c2 in zif_imap_fetchbody (ht=3, return_value=0x7ff88df465d0, return_value_ptr=<value optimized out>, this_ptr=<value optimized out>, return_value_used=<value optimized out>) at /opt/php-imap-5.3.2/php_imap.c:2361
streamind = 0x7ff88df46670
msgno = 1
flags = 0
body = 0x0
sec = 0x7ff88df468c8 "3.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1."...
sec_len = 1005
len = 140705510226248
Теперь у нас есть номер строки, где произошел сбой (файл /opt/php-imap-5.3.2/php_imap.c, строка 2361).
#2 0x00007ff8884013c2 in zif_imap_fetchbody (ht=3, return_value=0x7ff88df465d0, return_value_ptr=<value optimized out>, this_ptr=<value optimized out>, return_value_used=<value optimized out>) at /opt/php-imap-5.3.2/php_imap.c:2361
2361 RETVAL_STRINGL(body, len, 1);
(gdb) print len
$1 = 140705510226248
(gdb) print &len
$2 = (long unsigned int *) 0x7ffffa9bdd80
(gdb) frame 1
#1 0x0000000000678788 in _estrndup (s=0x7ff8881ddf4e "", length=2381609288) at /usr/include/bits/string3.h:52
warning: Source file is more recent than executable.
52 return __builtin___memcpy_chk (__dest, __src, __len, __bos0 (__dest));
(gdb) print s
$3 = 0x7ff8881ddf4e ""
(gdb) print length
$4 = 2381609288
О локальной переменной len сказать что-либо трудно, так как функция __builtin___memcpy_chk() могла ее перетереть. Придется исправлять код.
Находим в php_imap.c в функции imap_fetchbody строку
и исправляем её на
body = mail_fetchbody_full(imap_le_struct->imap_stream, msgno, sec, &len, (argc == 4 ? flags : NIL));
printf("len after = %ld\n", len);
Пересобираем, запускаем, получаем:
len after = 140425249339720
Segmentation fault (core dumped)
Становится видно, что по каким-то причинам функция mail_fetchbody_full() может вернуть пустую строку, но при этом не установить len в 0.
Исправление тривиально: находим
Заменяем на
Пересобираем, устанавливаем, запускаем — voilà, ошибка исчезла, php больше не падает.
Результат: полчаса потраченного времени, ошибка исправлена, php не падает.
Автор: Wandering Soul; опубликовано в: PHP; метки: IMAP, PHP, segfault, ошибкаАвг
2010
Комментарии к статье «DoS для PHP через imap_fetchbody» (2) »
Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.
Оставить комментарий к записи «DoS для PHP через imap_fetchbody»
गते गते पारगते पारसंगते बोधि स्वाहा
Меня зовут Владимир, я программист-фрилансер, специализирующийся на Web-программировании и програмировании под Linux.
По совместительству занимаюсь администрированием LAMP/LNMP-серверов и техническим переводом.


Ссылки на багрепорты Владимира: php-imap, uw-imap.