當前位置:
首頁 > 新聞 > GDB調試CVE-2018-5711 PHP-GD拒絕服務漏洞

GDB調試CVE-2018-5711 PHP-GD拒絕服務漏洞

*本文原創作者:xiaoyinglicai,屬於Freebuf原創獎勵計劃,禁止轉載


下載、編譯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。 即保證當文件讀取完成後可以退出循環。


*本文原創作者:xiaoyinglicai,屬於Freebuf原創獎勵計劃,禁止轉載


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 FreeBuf 的精彩文章:

創新 突破 成長 | 回顧2017年斗象科技成績單
就一加手機支付漏洞討論在線支付中的安全風險

TAG:FreeBuf |