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 裝滿我們構造的數據。
繼續執行若干次後進入了死循環,觸發漏洞。
對跳出循環條件進行分析,除了其中一個跳出條件為異常處理外,另外兩處
Line 398: scd->done = TRUE; 這個理論上來說應該是文件讀取完成後,由GetDataBlock返回-1從而退出循環,但是本次漏洞根源即為count無法為負值,因此不可行
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原創獎勵計劃,禁止轉載
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
※創新 突破 成長 | 回顧2017年斗象科技成績單
※就一加手機支付漏洞討論在線支付中的安全風險
TAG:FreeBuf |