LFI2RCE via Eternal waiting
Información Básica
Por defecto, cuando se sube un archivo a PHP (incluso si no lo está esperando), generará un archivo temporal en /tmp
con un nombre como php[a-zA-Z0-9]{6}
, aunque he visto algunas imágenes de docker donde los archivos generados no contienen dígitos.
En una inclusión de archivo local, si logras incluir ese archivo subido, obtendrás RCE.
Ten en cuenta que por defecto PHP solo permite subir 20 archivos en una sola solicitud (configurado en /etc/php/<version>/apache2/php.ini
):
También, el número de nombres de archivo potenciales es 62*62*62*62*62*62 = 56800235584
Otras técnicas
Otras técnicas se basan en atacar protocolos de PHP (no podrás si solo controlas la última parte de la ruta), divulgar la ruta del archivo, abusar de archivos esperados, o hacer que PHP sufra un fallo de segmentación para que los archivos temporales subidos no sean eliminados. Esta técnica es muy similar a la última pero sin necesidad de encontrar un zero day.
Técnica de espera eterna
En esta técnica solo necesitamos controlar una ruta relativa. Si logramos subir archivos y hacer que el LFI nunca termine, tendremos "suficiente tiempo" para fuerza bruta de archivos subidos y encontrar cualquiera de los que se hayan subido.
Ventajas de esta técnica:
Solo necesitas controlar una ruta relativa dentro de un include
No requiere nginx ni un nivel inesperado de acceso a archivos de registro
No requiere un 0 day para causar un fallo de segmentación
No requiere divulgación de ruta
Los principales problemas de esta técnica son:
Necesita que un archivo(s) específico(s) esté(n) presente(s) (puede haber más)
La increíble cantidad de nombres de archivo potenciales: 56800235584
Si el servidor no está usando dígitos, la cantidad total potencial es: 19770609664
Por defecto, solo se pueden subir 20 archivos en una sola solicitud.
El número máximo de trabajadores paralelos del servidor utilizado.
Este límite junto con los anteriores puede hacer que este ataque dure demasiado
Tiempo de espera para una solicitud PHP. Idealmente, esto debería ser eterno o debería matar el proceso PHP sin eliminar los archivos temporales subidos, si no, esto también será un problema
Entonces, ¿cómo puedes hacer que un include de PHP nunca termine? Simplemente incluyendo el archivo /sys/kernel/security/apparmor/revision
(no disponible en contenedores Docker desafortunadamente...).
Inténtalo simplemente llamando:
Apache2
Por defecto, Apache soporta 150 conexiones concurrentes, siguiendo https://ubiq.co/tech-blog/increase-max-connections-apache/ es posible aumentar este número hasta 8000. Sigue esto para usar PHP con ese módulo: https://www.digitalocean.com/community/tutorials/how-to-configure-apache-http-with-mpm-event-and-php-fpm-on-ubuntu-18-04.
Por defecto, (como puedo ver en mis pruebas), un proceso PHP puede durar eternamente.
Hagamos algunos cálculos:
Podemos usar 149 conexiones para generar 149 * 20 = 2980 archivos temporales con nuestro webshell.
Luego, usar la última conexión para fuerza bruta de archivos potenciales.
A una velocidad de 10 solicitudes/s los tiempos son:
56800235584 / 2980 / 10 / 3600 ~= 530 horas (50% de probabilidad en 265h)
(sin dígitos) 19770609664 / 2980 / 10 / 3600 ~= 185h (50% de probabilidad en 93h)
¡Ten en cuenta que en el ejemplo anterior estamos completamente DoSing a otros clientes!
Si el servidor Apache se mejora y pudiéramos abusar de 4000 conexiones (a medio camino del número máximo). Podríamos crear 3999*20 = 79980
archivos y el número se reduciría a alrededor de 19.7h o 6.9h (10h, 3.5h 50% de probabilidad).
PHP-FMP
Si en lugar de usar el módulo php regular para apache para ejecutar scripts PHP, la página web está usando PHP-FMP (esto mejora la eficiencia de la página web, por lo que es común encontrarlo), hay algo más que se puede hacer para mejorar la técnica.
PHP-FMP permite configurar el parámetro request_terminate_timeout
en /etc/php/<php-version>/fpm/pool.d/www.conf
.
Este parámetro indica la cantidad máxima de segundos cuando la solicitud a PHP debe terminar (infinito por defecto, pero 30s si el parámetro está descomentado). Cuando una solicitud está siendo procesada por PHP el número de segundos indicado, es eliminada. Esto significa que, si la solicitud estaba subiendo archivos temporales, porque el procesamiento de PHP fue detenido, esos archivos no se van a eliminar. Por lo tanto, si puedes hacer que una solicitud dure ese tiempo, puedes generar miles de archivos temporales que no serán eliminados, lo que acelerará el proceso de encontrarlos y reduce la probabilidad de un DoS a la plataforma consumiendo todas las conexiones.
Entonces, para evitar DoS supongamos que un atacante estará usando solo 100 conexiones al mismo tiempo y el tiempo máximo de procesamiento de PHP por php-fmp (request_terminate_timeout
) es 30s. Por lo tanto, el número de archivos temporales que se pueden generar por segundo es 100*20/30 = 66.67
.
Luego, para generar 10000 archivos un atacante necesitaría: 10000/66.67 = 150s
(para generar 100000 archivos el tiempo sería 25min).
Luego, el atacante podría usar esas 100 conexiones para realizar una búsqueda de fuerza bruta. **** Suponiendo una velocidad de 300 req/s el tiempo necesario para explotar esto es el siguiente:
56800235584 / 10000 / 300 / 3600 ~= 5.25 horas (50% de probabilidad en 2.63h)
(con 100000 archivos) 56800235584 / 100000 / 300 / 3600 ~= 0.525 horas (50% de probabilidad en 0.263h)
Sí, es posible generar 100000 archivos temporales en una instancia de tamaño medio de EC2:
Ten en cuenta que para activar el tiempo de espera sería suficiente incluir la página LFI vulnerable, para que entre en un bucle de inclusión eterno.
Nginx
Parece que por defecto Nginx soporta 512 conexiones paralelas al mismo tiempo (y este número puede mejorarse).
Last updated