| |

Explotación de vulnerabilidades en PHP-FPM Nginx

Contenido

  • 1 soporte
  • 2 Detalles de vulnerabilidad
  • 3 Conclusión

Recientemente, se descubrió una vulnerabilidad peligrosa en un paquete de Nginx y PHP-FPM, que a menudo se encuentra en los servidores web. El exploit resultante le permite cambiar la configuración de PHP mediante la introducción de variables de entorno, y una cadena de tales configuraciones conducirá a la ejecución remota de código. Comprendamos las vulnerabilidades en PHP-FPM Nginx y descubramos dónde se equivocaron los desarrolladores de PHP.

Andrei @ d90pwn Danau detectó el primer comportamiento anormal durante la calificación del Real World CTF 2019. El servidor reaccionó de manera extraña al carácter de avance de línea (% 0a) enviado a la URL. Omar @beched Ganiev y Emil @neex Lerner se interesaron en esta idea. Emil descubrió por qué sucede esto, encontró una forma de explotación y escribió un exploit funcional, y Omar llevó este error a RCE.

La esencia del problema se reduce al hecho de que en algunas configuraciones de FPM, un pirata informático puede realizar un ataque de desbordamiento de búfer y escribir en el espacio de direcciones reservado para los datos del protocolo FastCGI. Esto permitirá que se ejecuten comandos arbitrarios en el sistema remoto.

Identificador de vulnerabilidad recibido CVE-2019-11043 y el nombre provocativo para una persona de habla rusa PHuiP-FPizdaM. Para la explotación, el atacante no necesita ningún derecho, por lo tanto, la vulnerabilidad PHP-FPM Nginx tiene un estado crítico. El problema está presente en ambas ramas PHP: 5 y 7, sin embargo, debido a las peculiaridades de la optimización, la operación solo es posible en la séptima versión de PHP.

La vulnerabilidad afecta a las versiones:

  • Ramas PHP 7.1.x: todas las versiones anteriores a 7.1.33
  • PHP ramas 7.2.x – todas las versiones debajo de 7.2.24
  • PHP ramas 7.3.x – todas las versiones debajo de 7.3.11

Pararse

Primero necesitamos un puesto. En mi caso, se utilizarán los ya queridos contenedores Docker. Si no desea entrar en la depuración, puede comenzar el entorno terminado con vulhub .

docker-compose.yml

Comienza con un comando simple: docker-compose up -d.

Quiero ver la vulnerabilidad más de cerca, así que construyamos PHP desde la fuente. Para comenzar, inicie Debian.

Ahora debemos encargarnos de instalar los paquetes necesarios.

Saque la última versión vulnerable de php – 7.3.10.

Configure PHP con soporte php-fpm.

Ahora hagamos la compilación e instalación. Aquí todo es trivial.

Cambiamos los nombres de los archivos de configuración estándar.

Después de eso, cambiamos la ruta a la carpeta donde se encuentran las configuraciones.

En la configuración de php-fpm (/usr/local/etc/php-fpm.d/www.conf) configuramos el número de procesos secundarios. Para facilitar la depuración, recomiendo poner 1.

Ahora depende de los archivos de configuración de Nginx.

/ etc / nginx / sites-enabled / default

Fastcgi_pass contiene la dirección de nuestro PHP-FPM, por defecto se cuelga en el puerto 9000. Explicaré algunas partes de la configuración en el proceso de análisis de la vulnerabilidad.

Luego debe crear el archivo PHP en la raíz del servidor web (/ var / www / html /). Incluso un script vacío es adecuado aquí, lo principal es que tiene la extensión .php y Nginx lo envía a PHP. Creé index.php que muestra un saludo.

/var/www/html/index.php

Ahora todo está listo, puedes ejecutar Nginx.

Y luego PHP-FPM a través del depurador.

Para GDB, habilitamos la capacidad de depurar procesos secundarios e iniciar el servicio.

Stand listo para trabajar con PHP-FPM y Nginx
Stand listo para trabajar con PHP-FPM y Nginx

Detalles de vulnerabilidad

Primero, eche un vistazo al commit que parchea la vulnerabilidad .

Una confirmación que parchea la vulnerabilidad CVE-2019-11043 en PHP 7.3.10
Una confirmación que parchea la vulnerabilidad CVE-2019-11043 en PHP 7.3.10
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
/php-src-php-7.3.11/sapi/fpm/fpm/fpm_main.c

Como puede ver, se agregan comprobaciones adicionales para las variables path_info y tflag. En esta parte del código, se procesan las rutas del formulario /info.php/test.

Coloque el punto de interrupción justo encima de las líneas parcheadas, en la línea 1143, e intente enviar una solicitud GET con un byte de salto de línea (% 0a).

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
El punto de interrupción funcionó en la función init_request_info después de enviar el byte 0x0a
El punto de interrupción funcionó en la función init_request_info después de enviar el byte 0x0a

Por supuesto, primero la URL ingresa a Nginx. Déjame recordarte una línea de la configuración.

por defecto

El byte de salto de línea pasado rompe la lógica de la expresión regular en la directiva fastcgi_split_path_info . Como resultado, la variable env_path_info toma un valor vacío y el pilen se vuelve igual a 0. Luego, path_info se calcula en función de estos valores.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Cuando no se encuentra el archivo solicitado, PHP-FPM sube más en el URI e intenta volver a enviar la solicitud de un script más alto, si lo hay. Determinado por signos /. Por lo tanto, si solicito http: //phprce.vh/index.php/info.php, entonces el script index.php funcionará, ya que no tengo info.php.

PHP-FPM transfiere el control al script principal si no se encuentra el pedido
PHP-FPM transfiere el control al script principal si no se encuentra el pedido
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Por lo tanto, hay dos variables: script_path_translated es el URI completo y pt es la ruta al script anterior. En nuestro caso, tienen una longitud de 37 (0x25) y 23 (0x17) bytes, respectivamente.

  • script_path_translated – /var/www/html/index.php/path\ninfo.php
  • pt – /var/www/html/index.php
Rutas variables a los scripts y su longitud.
Rutas variables a los scripts y su longitud.

En base a estas variables, se considera slen.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Y luego se calcula path_info.

Variables en función de las cuales se calcula path_info
Variables en función de las cuales se calcula path_info
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Ahora en la expresión env_path_info + pilen – slen, solo slen es distinto de cero (0xe). Por lo tanto, path_info no señalará dónde lo necesita, sino a la dirección que se encuentra arriba. En mi caso, esta es la línea 200 en la dirección 0x555556618672. Esta vulnerabilidad se llama desbordamiento de búfer.

La dirección original del valor de path_info y la dirección después del desbordamiento del búfer
La dirección original del valor de path_info y la dirección después del desbordamiento del búfer

Si observa más el código de función init_request_info, verá una pieza curiosa.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Como controlamos la longitud de slen, podemos escribir bytes nulos en cualquier posición por encima de PATH_INFO. Para comprender lo que se puede aprender de esto, debe comprender cómo funciona PHP-FPM con las variables de entorno. Si observa el código un poco más, veremos que uno de ellos está instalado allí: ORIG_SCRIPT_NAME.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

La función FCGI_PUTENV se inicializa como fcgi_quick_putenv.

/php-src-php-7.3.10/main/fastcgi.h

Si observa su código, verá que ella modifica directamente req-> env.

/php-src-php-7.3.10/main/fastcgi.c

Esta construcción se inicializa al comienzo de la consulta con la función fcgi_hash_init.

/php-src-php-7.3.10/main/fastcgi.c
/php-src-php-7.3.10/main/fastcgi.c

Aquí vemos que req-> env es la estructura fcgi_data_seg .

/php-src-php-7.3.10/main/fastcgi.c

Esta estructura almacena las variables globales necesarias para la comunicación entre el proceso PHP-FPM y el servidor web (en mi caso, Nginx). Aquí pos apunta a la dirección del búfer actual, finaliza a la dirección donde termina y los datos al lugar donde desea escribir los datos. Cuando el bloque de memoria asignado para la estructura finaliza (pos es mayor que final), se crea uno nuevo y la dirección del actual se escribe a continuación.

Por ejemplo, así es como esta estructura busca la solicitud actual y los datos almacenados en ella.

La estructura fcgi_data_seg de la solicitud y los datos actuales
La estructura fcgi_data_seg de la solicitud y los datos actuales

Nuestro PATH_INFO también se encuentra allí. Preste atención a la dirección pos, se encuentra arriba, por lo que puede establecer un puntero y escribir bytes nulos allí.

La esencia del problema se reduce al hecho de que en algunas configuraciones de FPM, un pirata informático puede realizar un ataque de desbordamiento de búfer y escribir en el espacio de direcciones reservado para los datos del protocolo FastCGI. Esto permitirá que se ejecuten comandos arbitrarios en el sistema remoto.

Identificador de vulnerabilidad recibido CVE-2019-11043 y el nombre provocativo para una persona de habla rusa PHuiP-FPizdaM. Para la explotación, el atacante no necesita ningún derecho, por lo tanto, la vulnerabilidad PHP-FPM Nginx tiene un estado crítico. El problema está presente en ambas ramas PHP: 5 y 7, sin embargo, debido a las peculiaridades de la optimización, la operación solo es posible en la séptima versión de PHP.

La vulnerabilidad afecta a las versiones:

  • Ramas PHP 7.1.x: todas las versiones anteriores a 7.1.33
  • PHP ramas 7.2.x – todas las versiones debajo de 7.2.24
  • PHP ramas 7.3.x – todas las versiones debajo de 7.3.11

Pararse

Primero necesitamos un puesto. En mi caso, se utilizarán los ya queridos contenedores Docker. Si no desea entrar en la depuración, puede comenzar el entorno terminado con vulhub .

docker-compose.yml

Comienza con un comando simple: docker-compose up -d.

Quiero ver la vulnerabilidad más de cerca, así que construyamos PHP desde la fuente. Para comenzar, inicie Debian.

Ahora debemos encargarnos de instalar los paquetes necesarios.

Saque la última versión vulnerable de php – 7.3.10.

Configure PHP con soporte php-fpm.

Ahora hagamos la compilación e instalación. Aquí todo es trivial.

Cambiamos los nombres de los archivos de configuración estándar.

Después de eso, cambiamos la ruta a la carpeta donde se encuentran las configuraciones.

En la configuración de php-fpm (/usr/local/etc/php-fpm.d/www.conf) configuramos el número de procesos secundarios. Para facilitar la depuración, recomiendo poner 1.

Ahora depende de los archivos de configuración de Nginx.

/ etc / nginx / sites-enabled / default

Fastcgi_pass contiene la dirección de nuestro PHP-FPM, por defecto se cuelga en el puerto 9000. Explicaré algunas partes de la configuración en el proceso de análisis de la vulnerabilidad.

Luego debe crear el archivo PHP en la raíz del servidor web (/ var / www / html /). Incluso un script vacío es adecuado aquí, lo principal es que tiene la extensión .php y Nginx lo envía a PHP. Creé index.php que muestra un saludo.

/var/www/html/index.php

Ahora todo está listo, puedes ejecutar Nginx.

Y luego PHP-FPM a través del depurador.

Para GDB, habilitamos la capacidad de depurar procesos secundarios e iniciar el servicio.

Stand listo para trabajar con PHP-FPM y Nginx
Stand listo para trabajar con PHP-FPM y Nginx

Detalles de vulnerabilidad

Primero, eche un vistazo al commit que parchea la vulnerabilidad .

Una confirmación que parchea la vulnerabilidad CVE-2019-11043 en PHP 7.3.10
Una confirmación que parchea la vulnerabilidad CVE-2019-11043 en PHP 7.3.10
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
/php-src-php-7.3.11/sapi/fpm/fpm/fpm_main.c

Como puede ver, se agregan comprobaciones adicionales para las variables path_info y tflag. En esta parte del código, se procesan las rutas del formulario /info.php/test.

Coloque el punto de interrupción justo encima de las líneas parcheadas, en la línea 1143, e intente enviar una solicitud GET con un byte de salto de línea (% 0a).

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
El punto de interrupción funcionó en la función init_request_info después de enviar el byte 0x0a
El punto de interrupción funcionó en la función init_request_info después de enviar el byte 0x0a

Por supuesto, primero la URL ingresa a Nginx. Déjame recordarte una línea de la configuración.

por defecto

El byte de salto de línea pasado rompe la lógica de la expresión regular en la directiva fastcgi_split_path_info . Como resultado, la variable env_path_info toma un valor vacío y el pilen se vuelve igual a 0. Luego, path_info se calcula en función de estos valores.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Cuando no se encuentra el archivo solicitado, PHP-FPM sube más en el URI e intenta volver a enviar la solicitud de un script más alto, si lo hay. Determinado por signos /. Por lo tanto, si solicito http: //phprce.vh/index.php/info.php, entonces el script index.php funcionará, ya que no tengo info.php.

PHP-FPM transfiere el control al script principal si no se encuentra el pedido
PHP-FPM transfiere el control al script principal si no se encuentra el pedido
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Por lo tanto, hay dos variables: script_path_translated es el URI completo y pt es la ruta al script anterior. En nuestro caso, tienen una longitud de 37 (0x25) y 23 (0x17) bytes, respectivamente.

  • script_path_translated – /var/www/html/index.php/path\ninfo.php
  • pt – /var/www/html/index.php
Rutas variables a los scripts y su longitud.
Rutas variables a los scripts y su longitud.

En base a estas variables, se considera slen.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Y luego se calcula path_info.

Variables en función de las cuales se calcula path_info
Variables en función de las cuales se calcula path_info
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Ahora en la expresión env_path_info + pilen – slen, solo slen es distinto de cero (0xe). Por lo tanto, path_info no señalará dónde lo necesita, sino a la dirección que se encuentra arriba. En mi caso, esta es la línea 200 en la dirección 0x555556618672. Esta vulnerabilidad se llama desbordamiento de búfer.

La dirección original del valor de path_info y la dirección después del desbordamiento del búfer
La dirección original del valor de path_info y la dirección después del desbordamiento del búfer

Si observa más el código de función init_request_info, verá una pieza curiosa.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

Como controlamos la longitud de slen, podemos escribir bytes nulos en cualquier posición por encima de PATH_INFO. Para comprender lo que se puede aprender de esto, debe comprender cómo funciona PHP-FPM con las variables de entorno. Si observa el código un poco más, veremos que uno de ellos está instalado allí: ORIG_SCRIPT_NAME.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c

La función FCGI_PUTENV se inicializa como fcgi_quick_putenv.

/php-src-php-7.3.10/main/fastcgi.h

Si observa su código, verá que ella modifica directamente req-> env.

/php-src-php-7.3.10/main/fastcgi.c

Esta construcción se inicializa al comienzo de la consulta con la función fcgi_hash_init.

/php-src-php-7.3.10/main/fastcgi.c
/php-src-php-7.3.10/main/fastcgi.c

Aquí vemos que req-> env es la estructura fcgi_data_seg .

/php-src-php-7.3.10/main/fastcgi.c

Esta estructura almacena las variables globales necesarias para la comunicación entre el proceso PHP-FPM y el servidor web (en mi caso, Nginx). Aquí pos apunta a la dirección del búfer actual, finaliza a la dirección donde termina y los datos al lugar donde desea escribir los datos. Cuando el bloque de memoria asignado para la estructura finaliza (pos es mayor que final), se crea uno nuevo y la dirección del actual se escribe a continuación.

Por ejemplo, así es como esta estructura busca la solicitud actual y los datos almacenados en ella.

La estructura fcgi_data_seg de la solicitud y los datos actuales
La estructura fcgi_data_seg de la solicitud y los datos actuales

Nuestro PATH_INFO también se encuentra allí. Preste atención a la dirección pos, se encuentra arriba, por lo que puede establecer un puntero y escribir bytes nulos allí.

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *