--- tags: - security - vulnerability - poc - protobuf - google - uncontrolled-recursion - cwe-674 - dos license: mit --- # protobuf — json_format.ParseDict() Uncontrolled Recursion (PoC) **Repo:** `MBM7/protobuf-parsedicts-recursion-poc` **Status:** Responsible disclosure — submitted to Huntr **Severity:** High / CWE-674 **Package:** `protobuf` (PyPI) — google.protobuf --- ## Summary A **7.4 KB** crafted JSON payload with 200 nesting levels crashes any Python server calling `ParseDict()` with a `RecursionError` — unhandled, terminates the process. | Payload size | Depth | Result | |-------------|-------|------------------| | 7,443 bytes | 200 | RecursionError ✓ | | 9,293 bytes | 250 | RecursionError ✓ | --- ## Root Cause `google/protobuf/json_format.py`, `_ConvertMessage()`: ```python def _ConvertMessage(value, message, path): for field_name, field_value in value.items(): ... _ConvertMessage(field_value, sub_message, path) # ← no depth check ``` `ParseDict()` → `_ConvertMessage()` recurses once per nesting level with **no depth limit**. Compare to the binary parser which has `DEFAULT_RECURSION_LIMIT = 100`. --- ## Asymmetry | Parser | Depth limit | |--------|-------------| | `ParseFromString()` (binary) | `DEFAULT_RECURSION_LIMIT = 100` ✅ | | `ParseDict()` (JSON→proto) | **NONE** ❌ | | `MessageToJson()` | **NONE** ❌ | | `MessageToDict()` | **NONE** ❌ | --- ## Attack scenario ```python # Server receives JSON from user, converts to proto: user_json = json.loads(request.body) proto_msg = MyMessage() ParseDict(user_json, proto_msg) # ← CRASH at depth 200 ``` A 7.4 KB JSON payload with 200 nesting levels crashes the Python process. --- ## Reproduce ```bash pip install protobuf python poc_protobuf_recursive_json.py ``` Expected: ``` Nesting depth : 200 levels JSON payload : 7,443 bytes Result : RecursionError — server CRASH ✓ ``` --- ## Suggested Fix ```python def _ConvertMessage(value, message, path, depth=0): if depth > _MAX_RECURSION_DEPTH: # e.g. 100, matching binary parser raise ParseError('Message too deeply nested') for field_name, field_value in value.items(): ... _ConvertMessage(field_value, sub_message, path, depth + 1) ``` --- ## Environment | Package | Version | |----------|---------| | protobuf | 5.29.6 | | Python | 3.12 |