PHP遠程DoS漏洞深入分析
及防護方案

5月14日,國內爆出php遠程DoS漏洞,官方編號69364。利用該漏洞構造poc發起鏈接,很容易導致目標主機cpu的佔用率100%,涉及PHP多個版本。綠盟科技威脅響應中心隨即啓動應急機制, 應急響應工作隨即啓動。

  1. 15日夜,啓動漏洞分析工作,同步將分析結果發送產品團隊;

  2. 16日,發佈產品規則升級通告,綠盟科技RSAS產品升級相繼就緒,客戶通過在線及離線升級的方法,即可獲得漏洞的檢測能力;同時,在線漏洞檢測引擎就緒;

  3. 17日,漏洞深入分析進行中。綠盟科技NIPS產品升級就緒,客戶通過在線及離線升級的方法,即可獲得漏洞的防護能力;

  4. 18日,我們回顧此次PHP漏洞的信息要點,從PHP漏洞防護的角度進行總結,爲大家制定防禦方案提供補充信息。


PHP遠程DoS漏洞

4月3日,有人在PHP官網提交PHP 遠程DoS漏洞(PHP Multipart/form-data remote dos Vulnerability),代號69364。由於該漏洞涉及PHP的所有版本,故其影響面較大,一經發布迅速引發多方面關注。14日,各種PoC已經在網絡上流傳。此次漏洞具備如下特性:

  1. 一旦被利用成功,可以在迅速消耗被攻擊主機的CPU資源,從而達到DoS的目的;

  2. PHP在全球的部署量相當大,爲攻擊者提供了相當多可以攻擊的目標;

  3. PHP官方目前僅給出了5.4及5.5版本的補丁受此漏洞影響的軟件及系統包括PHP的如下版本。
    PHP 5.0.0 – 5.0.5
    PHP 5.1.0 – 5.1.6
    PHP 5.2.0 – 5.2.17
    PHP 5.3.0 – 5.3.29
    PHP 5.4.0 – 5.4.40
    PHP 5.5.0 – 5.5.24
    PHP 5.6.0 – 5.6.8

綠盟科技常年密切關注PHP的安全問題。綠盟科技威脅響應中心在獲知相關信息後,隨即啓動應急機制,相關工作隨即啓動。本文章將會深入分析該漏洞,並給出應對方案。

PHP遠程DoS漏洞分析

2015年5月15日夜,綠盟科技威脅響應中心在獲取PHP漏洞傳播情況的同時,也在進行漏洞的分析工作,通過重現漏洞的攻擊過程,分析其工作原理,得以清晰識別及檢測該漏洞方法。

Boundary中的鍵值對分隔

PHP是一種流行的Web服務器端編程語言,它功能強大,簡單易用,利用它編寫網絡應用程序,可以應對大規模的Http請求,所以很多業務環境中都部署了PHP。考慮規範性,PHP在設計之初就遵循rfc規範,進行各個協議模塊的封裝及過程處理。PHP與其他同樣遵循rfc規範的語言及環境相比,不過是處理方式不同。

而從rfc1867開始,http協議開始支持”multipart/form-data”請求,以便接受多種數據格式,包括多種變量甚至是文件上傳。multipart/form-data中可以包含多個報文,每一個報文boundary(分隔符)分隔開來,而每個報文中都包含了多行鍵值對,鍵值對用冒號分隔,這樣的設計是爲了讓程序可以清晰的區分這些數據。

boundary

但如果由於某種原因,鍵值中間缺少了那個冒號,PHP函數會將下一對鍵值合併到了上一行,形成這樣的鍵值對,“鍵1:值1鍵2值2”。由於PHP進行鍵值合併的算法不夠優化,這樣的事情發生幾次還沒什麼,如果數以百萬記,就變成了一種災難。

在下面的例子中,當a的部分達到一定數量的時候(幾十萬行or上百萬行),由於每行鍵與值之間並沒有冒號分隔,函數就自動將下一行的鍵值對合並,這樣數據越來越大,越來越長,函數針對這些數據不斷執行內存的分配和釋放,最終被攻擊目標主機的CPU資源被耗盡。

WebKitForm

*注:PHP中,Boundary是可以自定義的,比如“—–WebKitFormBoundarypE33TmSNWwsMphqz”

這樣的代碼,在抓包時顯示情況如下

packet

Boundary報文解析過程

PHP在main/rfc1867.c中,有兩個函數都涉及boundary的解析,包括SAPI_API SAPI_POST_HANDLER_FUNC及multipart_buffer_headers函數。DoS漏洞出現在main/rfc46675pxultipart_buffer_headers函數。

PHP先解析解析multipart/form-data http請求, http請求體的入口函數在SAPI_POST_HANDLER_FUNC(rfc1867.c中的函數),SAPI_POST_HANDLER_FUNC函數首先解析請求的boundary,也就是POST請求中第一次定義時的boundary;並且在其內部調用了multipart_buffer_headers,該函數先找到boundary(也就是一次引用的boundary),會和定義時的boundary比較。如果相等即找到第一次引用的boundary,接下來會逐行讀取請求的輸入以解析body port header(也就是解析第一次引用boundary後面的內容)。

SAPI_API SAPI_POST_HANDLER_FUNC


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

    /* Get the boundary */

    /* 開始解析boundary */

    boundary = strstr(content_type_dup, boundary);

    if (!boundary) {

            int content_type_len = strlen(content_type_dup);

            char *content_type_lcase = estrndup(content_type_dup, content_type_len);

            php_strtolower(content_type_lcase, content_type_len);

            boundary = strstr(content_type_lcase, boundary);

            if (boundary) {

                    boundary = content_type_dup + (boundary  content_type_lcase);

            }

            efree(content_type_lcase);

    }

    if (!boundary || !(boundary = strchr(boundary, =))) {

            sapi_module.sapi_error(E_WARNING, Missing boundary in multipart/form-data POST data);

            return;

    }

    boundary++;

    boundary_len = strlen(boundary);

    /* 對bondary進行合法校驗 */

    if (boundary[0] == ‘”‘) {

            boundary++;

            boundary_end = strchr(boundary, ‘”‘);

            if (!boundary_end) {

                    sapi_module.sapi_error(E_WARNING, Invalid boundary in multipart/form-data POST data);

                    return;

            }

    } else {

            /* search for the end of the boundary */

            boundary_end = strpbrk(boundary, ,;);

    }

    if (boundary_end) {

            boundary_end[0] = ‘′;

            boundary_len = boundary_end-boundary;

    }

    /* Initialize the buffer */

    if (!(mbuff = multipart_buffer_new(boundary, boundary_len TSRMLS_CC))) {

            sapi_module.sapi_error(E_WARNING, Unable to initialize the input buffer);

            return;

    }

    while (!multipart_buffer_eof(mbuff TSRMLS_CC))

    {

            char buff[FILLUNIT];

            char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL;

            size_t blen = 0, wlen = 0;

            off_t offset;

            zend_llist_clean(&header);

            /* 漏洞函數 */

            if (!multipart_buffer_headers(mbuff, &header TSRMLS_CC)) {

                    goto fileupload_done;

            }

 


multipart_buffer_headers


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

    /* parse headers */

    static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)

    {

            char *line;

            mime_header_entry prev_entry = {0}, entry;

            int prev_len, cur_len;

            /* didn’t find boundary, abort */

            if (!find_boundary(self, self->boundary TSRMLS_CC)) {

                    return 0;

            }

            /* get lines of text, or CRLF_CRLF */

            /* 逐行解析 */

            while( (line = get_line(self TSRMLS_CC)) && line[0] != ‘′ )

            {

                    /* add header to table */

                    char *key = line;

                    char *value = NULL;

                    if (php_rfc1867_encoding_translation(TSRMLS_C)) {

                            self->input_encoding = zend_multibyte_encoding_detector(line, strlen(line), self->detect_order, self->detect_order_size TSRMLS_CC);

                    }

                    /* space in the beginning means same header */

                    /* 如果該行開頭不是空格,則試圖尋找’:'查看是否是有效鍵值對 */

                    if (!isspace(line[0])) {

                            value = strchr(line, :);

                    }

                    /* 如果找到’:'則說明該行包含一個有效的鍵值對,解析它 */

                    if (value) {

                            *value = 0;

                            do { value++; } while(isspace(*value));

                            entry.value = estrdup(value);

                            entry.key = estrdup(key);

                    /* 如果不包含’:',且該行前有一個有效鍵值對,則說明這一行是上一個鍵值對的值 */

                    } else if (zend_llist_count(header)) { /* If no ‘:’ on the line, add to previous line */

                            prev_len = strlen(prev_entry.value);

                            cur_len = strlen(line);

                            /* 進行值合併操作 */

                            entry.value = emalloc(prev_len + cur_len + 1);

                            memcpy(entry.value, prev_entry.value, prev_len);

                            memcpy(entry.value + prev_len, line, cur_len);

                            entry.value[cur_len + prev_len] = ‘′;

                            entry.key = estrdup(prev_entry.key);

                            zend_llist_remove_tail(header);

                    } else {

                            continue;

                    }

                    zend_llist_add_element(header, &entry);

                    prev_entry = entry;

            }

            return 1;

    }

 


出現問題的函數處理邏輯

multipart_buffer_headers函數在解析HTTP請求中的multipart頭部數據時,每次解析由get_line得到的一行鍵值對。當被解析的行是以空白字符開始,或者出現一個不包含 ‘ : ‘ 的行,該行將被當作是上一行鍵值對的延續來處理,將當前的值拼接到上一個鍵值對裏,並且在拼接的過程裏,該函數進行如下動作:

一次內存分配

entry.value = emalloc(prev_len + cur_len + 1);

兩次內存複製

memcpy(entry.value, prev_entry.value, prev_len);
memcpy(entry.value + prev_len, line, cur_len);

一次內存釋放

zend_llist_remove_tail(header);

當出現多個不包含 ‘ : ‘ 的行時,PHP就會進行大量內存分配釋放的操作,並且分配的空間與拷貝的長度將越來越大。當行的數目足夠多時,拷貝的操作將顯著的消耗服務器的CPU。實際測試中,包含近一百萬行的頭字段可以使服務器的CPU保持100%幾秒或者數十秒。如果併發多個攻擊請求,可能造成更長時間的資源佔用。

漏洞利用原理

攻擊者可通過發送一個2M左右的包含多行multipart頭部數據的HTTP請求來發起攻擊,無需認證,也不依賴PHP程序本身的內容。例如,通過發送畸形請求,每隔若干秒,同時併發多個這樣的請求,就會耗盡目標主機的CPU資源

PHP遠程DoS漏洞檢測

面對如此簡單的漏洞利用,以及較低的攻擊門檻,分析人員迅速將經過安全驗證後的檢測方法向雲端、產品端及服務端傳遞,並建議用戶儘快對其業務環境進行一次全面的漏洞檢測,以便可以儘快拿到第一手數據,爲後續制定漏洞防護方案及執行措施提供數據支撐及決策依據。

雲端檢測

5月16日晚,綠盟科技客戶自助門戶系統Portal發佈PHP遠程DoS漏洞檢測引擎,爲PHP Multipart/form-data遠程DoS漏洞(PHP-69364)提供掃描支持。

DoS

現在您隨時可以使用這個自助系統,對業務環境進行掃描,以便確認是否存在該漏洞,掃描請點擊:https://portal.nsfocus.com/vulnerability/list/

漏洞確認 當掃描結果信息中出現信息“您的檢測目標存在此漏洞”,即可確認當前業務環境中存在該漏洞,建議您儘快制定防護計劃,以避免系統在獲得加固前遭受攻擊。

產品檢測

通過部署綠盟遠程安全評估系統(Remote Security Assessment System),可以在您的業務環境中快速掃描及獲取此次漏洞情況,同時支持1實現漏洞的安全閉環管理,包括預警、檢測、分析管理、修補、審計等幾個環節;2獲取豐富的漏洞和配置知識庫支持,該知識庫是國內領先的安全漏洞庫,目前累計接近3萬條;3靈活部署,並獲得綠盟企業安全中心(NSFOCUS ESPC)進行集中管理,可以有效實現大型網絡的統一漏洞管理。4享有Gartner推薦的信譽保障。

test

針對此次PHP遠程DoS漏洞,綠盟科技漏洞掃描系列產品已經就緒,用戶請儘快升級到如下版本,以便爲您定製自己的防護措施提供第一手數據支撐。

update

PHP遠程DoS漏洞防護

知道了漏洞利用方法,也知道了攻擊檢測方法,那麼漏洞的防護也就知道該如何做了。如果確認您的業務環境中存在這個漏洞,那麼就需要參考上面的信息,儘快制定並啓動加固方案,這些加固從漏洞補丁開始,到產品防護,到整體防護,逐步推進。

漏洞加固 PHP官方已經針對PHP 5.4 及PHP 5.5版本給出了補丁,請使用這些版本的用戶,儘快到官方網站下載並安裝補丁,補丁的下載地址如下:

http://php.net/ChangeLog-5.php#5.4.41

http://php.net/ChangeLog-5.php#5.5.25

update2

如果您使用了PHP的其它版本,請隨時關注PHP官方的最新通告。

產品防護

只是只是安裝了漏洞補丁是不夠的,整體安全等級的提升以及應對未來的攻擊,安全產品是必不可少的一環,將Web系統置於DMZ區域並加以多產品的整體防護,是我們推薦的做法。在如下部署環境中,以綠盟網絡入侵防護系統(Network Intrusion Prevention System,簡稱NIPS)爲例,對業務系統部署NIPS,可以提供PHP遠程DoS漏洞攻擊防護。

protect

目前相關產品的升級信息如下:

update3

請所有使用綠盟產品的用戶儘快升級產品規則。綠盟科技已在軟件升級公告中提供規則升級包,規則可以通過產品界面的在線升級進行。如果您的業務系統暫時還無法升級規則包,那麼可以在軟件升級頁面中,找到對應的產品,通過下載升級包,以離線方式進行升級。 相關信息請訪問:

安全產品介紹:http://www.nsfocus.com.cn/1_solution/1_2_1.html  
產品升級公告:http://update.nsfocus.com/



如果您需要了解更多信息,請聯繫:
綠盟科技威脅響應中心微博 :http://weibo.com/threatresponse
綠盟科技微博:http://weibo.com/nsfocus
綠盟科技微信號:搜索公衆號 綠盟科技
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章