GDB调试CVE-2018-5711 PHP-GD拒绝服务漏洞

下载、编译PHP源码

从github的PHP-src克隆下含有漏洞的版本,最好采取7.0以上版本,编译时候会比较简单,本次选用PHP7.1.9。编译环境为 阿里云 Ubuntu 16.04 LTS

git clone --branch PHP-7.1.9 https://github.com/php/php-src
Cloning into 'php-src'...
remote: Counting objects: 725575, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 725575 (delta 11), reused 12 (delta 3), pack-reused 725538
Receiving objects: 100% (725575/725575), 301.72 MiB | 11.96 MiB/s, done.
Resolving deltas: 100% (562883/562883), done.
Checking connectivity... done.

由于下载的源码是没有configure文件的,首先要编译buildconf文件

./buildconf --force

可以使用-h选项看到帮助

./configure -h

为了方便快速编译,我编写了一个脚本。 因为我们要来调试gd,所以要加上 with-gd

#!/bin/sh
make distclean
./buildconf --force
./configure \
        --enable-maintainer-zts \
        --enable-debug \
        --enable-cli \
        --with-gd \
        "$@"

配置好后一般会有错误提示,如果无错误提示可以使用 make -j2 来编译源码。由于我采用的是阿里云单核主机,所以使用j2参数。检测安装情况

sapi/cli/php -v
PHP 7.1.9 (cli) (built: Feb  2 2018 11:28:48) ( ZTS DEBUG )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

可以看到目前PHP和gd库已经正常安装。

安装GDBGUI

为了方便我们的调试,我们安装一个很方便的GDBGUI,具体的网址可以GOOGLE一下。 快速安装的命令为

pip install gdbgui

以后我们可以运行gdbgui来进行远程调试。记得加上—auth选项,-r选项开启外网访问。

screen gdbgui -r --auth  
View gdbgui at http://0.0.0.0:5000
exit gdbgui by pressing CTRL+C

开始调试

运行后PHP后可以发现触发漏洞

然后我们来部署一下POC环境

/usr/src/php-src/sapi/cli/php

在命令行输入参数 开始执行

 run /root/poc.php

根据之前的问题分析,我们定位到问题出现在源码/usr/src/php-src/ext/gd/libgd/gd_ gif_in.c 中我们在左侧输入地址,并在 gdImageCreateFromGifCtx 函数放置断点

运行到断点处

Breakpoint 3, php_gd_gdImageCreateFromGifCtx (fd=0x7ffff4077000) at /usr/src/php-src/ext/gd/libgd/gd_gif_in.c:135

观察一下上下文目前并没什么特别。一路运行到 第214行 ,前面全部是GIF头和color table的解析,如果文件结构不合理,会返回并提示 invalid GIF file.

可以看到目前处理字符是 逗号 (0x2c),如果我们查看 poc.gif 的话。已经处理到了20位置,

继续向后读取9个 bytes之后到达 29h 位置。后面的03 FF为触发漏洞的第一处关键。

继续向下执行,进入到ReadImage方法,到代码 568行,读取了一个byte (03) 去与 MAX_LWZ_BITS 做比较,只有小约等于MAX_LWZ_BITS的时候才会继续进行。MAX_LWZ_BITS定义为12.

一路步入LWZReadByte_方法, 第一次调用是在做一些初始化操作。

随后进入while loop中,flag为0且sd->fresh为true, 进入第458行。 (如果为了方便可以直接在这里下断点。 随后进入真正触发漏洞的do while loop)

此个do while loop的终止条件为sd->firstcode != sd->clear_code ,

其中 sd->clear_code = 8 , sd->codesize = 4 , *ZeroDataBlockP = 0 。

first code为GetCode的返回值。当first_code为8的时候,此处循环就会继续进行

步入GetCode 首先是一系列判断数据长度的对循环终止条件的判断。

此处发现,如果 scd->done 为true时候会返回-1 ,同时结束外层do while loop。

接下来走到了第398行,此处为本次漏洞的第一现场,由于count为unsigned char,而GetDataBlock 读取文件完毕后会返回-1。 而count不会被至于-1。

截止至此,其他的大多文章都分析到这里。我对GIF的构造和具体的问题成因还是很感兴趣,所以我们在深入一层,结合GIF的构造,探索造成问题的根源。水平有限,如果有问题请尽情指正

步入GetDataBlock_ 方法。 继续步入ReadOK, ctx->getBuf 通过一个动态指针调用到 fileGetbuf 方法。

fileGetbuf 中会 fread 方法读取文件内内容。

size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;

对参数进行分析。 buf为存储数据的目标, size为1, count为1,最后一个为数据源。

即是此处从stream中读取1个1byte数据到buffer中。 并返回成功读取的大小。

此处我们可以看到 fctx->f 即将被读取的为 ff 。

执行后 返回数值为1. buf中第一个byte为 ff. 回到 GetDataBlock领空之后的stack状态为 count = 255 (ff)

进入下一个if条件,再次进入 fileGetbuf 其中 size为 255. 读取的内容即为 ff 后面的 垃圾数据

载入位置为 之前 ff所在位置(0x7ffffffe9877)+255 = 0x7fffffff9b62

再次回到GetDataBlock领空 此时count为 255(0xff), buf中储存的是随后的GIF中的数据(0x88)。

一路读取数据 直到 scd->buf 装满我们构造的数据。

继续执行若干次后进入了死循环,触发漏洞。

对跳出循环条件进行分析,除了其中一个跳出条件为异常处理外,另外两处

  1. Line 398: scd->done = TRUE; 这个理论上来说应该是文件读取完成后,由GetDataBlock返回-1从而退出循环,但是本次漏洞根源即为count无法为负值,因此不可行
  2. Line 411: ret |= ((scd->buf[i / 8] & (1 << (i % 8))) != 0) << j; 此处提取了buf中的数据的一个byte中的第一位。 如在我们poc.gif中均为0x88, 每次ret运算结果即为8。而sd->clear_code即为8 从而导致无法退出循环。

我们也可以尝试,如果我们使用77替换POC.gif中的88后,并无法触发漏洞。因为ret处将取值为7,从而满足 sd->first_code != sd->clear_code。 因此精心构造的poc可以在利用条件1的基础上,构造满足条件2的情况即可触发漏洞。

修补方法

可以看到,官网对本漏洞的修补即为将count的类型变更为int。 即保证当文件读取完成后可以退出循环。

本文分享自微信公众号 - FreeBuf(freebuf)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-02-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据和云

故障诊断 | 系统级追踪诊断方法及案例分享

所谓操作系统,是应用程序与服务器硬件进行沟通的中间层。应用程序的所有操作,都是和操作系统进行沟通交互。操作系统负责将所有交互转化为设备语言,进行硬件交互。 我们...

38330
来自专栏JavaEdge

redis3.2启动配置文件redis.conf说明

34640

Web服务器压力测试工具Siege

Siege是一款HTTP压力测试和基准测试的实用工具,可用于在压力条件下对Web服务器的性能进行测量。它的评估依据包括传输数据量、服务器的响应时间、事务处理速率...

43630
来自专栏枕边书

初探PHP多进程

准备 我们都知道PHP是单进程执行的,PHP处理多并发主要是依赖服务器或PHP-FPM的多进程及它们进程的复用,但PHP实现多进程也意义重大,尤其是在后台Cli...

41380
来自专栏bboysoul

在树莓派上搭建使用gitlab

gitlab是一个代码托管平台,因为我的树莓派系统是安装在128G的u盘上的,用来放电影太小,但是空着就是空着了,所以还不如再搭建一个gitlab服务来存放自己...

36320
来自专栏散尽浮华

centos6下redis cluster集群部署过程

一般来说,redis主从和mysql主从目的差不多,但redis主从配置很简单,主要在从节点配置文件指定主节点ip和端口,比如:slaveof 192.168....

625100
来自专栏编舟记

java9 模块化系统小试

JPMS 全称是 Java Platform Module system(Java 平台模块化系统)。它的目的简单直接:编译期间检查和强化封装。随之而来的好处就...

20830
来自专栏程序员互动联盟

【专业技术】8大你不得不知的Android调试工具

1. 查看当前堆栈 1) 功能:在程序中加入代码,使可以在logcat中看到打印出的当前函数调用关系 2) 方法: new Exception(“print ...

738130
来自专栏大数据文摘

麻省理工三位教授教你一步步创建自己的R程序包(附完整教程下载)

20330
来自专栏Java学习123

LoadRunner11-遇到问题及解决办法

69450

扫码关注云+社区

领取腾讯云代金券