Jan 23, 2026·5 min read·396 visits
The Python implementation of Protocol Buffers contained a critical oversight in how it parsed 'Well-Known Types' nested inside `google.protobuf.Any` messages. By recursively nesting `Any` messages, an attacker could bypass the `max_recursion_depth` check entirely. This allows a relatively small JSON payload to trigger an infinite recursion loop in the parsing logic, hitting the Python interpreter's stack limit and crashing the application (DoS).
A logic flaw in Google's Python Protobuf implementation allows attackers to bypass recursion limits using nested 'Any' types, leading to a Denial of Service via stack exhaustion.
Protocol Buffers (Protobuf) are the lingua franca of modern microservices, acting as the efficient, binary glue holding together gRPC architectures. But sometimes, you need flexibility. Enter the google.protobuf.Any type—a polymorphic feature that lets you embed messages without defining their type upfront. It's effectively a void* for your schema.
While powerful, dynamic typing in a serialization format is a notorious minefield. The vulnerability we are looking at today, CVE-2026-0994, is a classic example of what happens when you trust your own "Well-Known Types" (WKT) a little too much.
The concept is simple: developers set a max_recursion_depth to prevent stack overflows. It's the bouncer at the club ensuring things don't get too rowdy. But in version 33.0+, the Protobuf parser inadvertently gave Any messages a VIP pass, allowing them to skip the bouncer entirely. This means an attacker can hand the server a JSON object that looks small but unpacks into a stack-crushing infinite loop.
To understand the bug, you have to look at google/protobuf/json_format.py. When the parser encounters a message, it typically calls ConvertMessage(), a function that dutifully increments a recursion counter, checks if it hits the limit, and then proceeds. So far, so good.
However, the handling for Any messages contained a fatal optimization. When the parser unpacked an Any message, it checked if the inner content was a "Well-Known Type" (like Struct, Duration, or interestingly, another Any). If it was, the code used Python's operator.methodcaller to invoke the specific handler for that type directly.
This direct invocation was the critical failure. By jumping straight to the handler logic, the code completely bypassed the ConvertMessage() gateway. Consequently, the recursion depth counter was never incremented for that nesting level. It was effectively a "free move" in the recursion game. By chaining these free moves together, you could go as deep as you wanted, regardless of the security settings.
Let's look at the diff. It's a textbook example of how a small helper function usage can undermine security guarantees.
In the vulnerable code, methodcaller is used to dispatch the call. Note the lack of self.ConvertMessage:
# VULNERABLE CODE
elif full_name in _WKTJSONMETHODS:
methodcaller(
_WKTJSONMETHODS[full_name][1],
value['value'],
sub_message,
'{0}.value'.format(path),
)(self)The fix, applied in PR #25239, forces the logic back through the main conversion pipeline. This ensures that every layer of the onion is counted against the quota:
# PATCHED CODE
elif full_name in _WKTJSONMETHODS:
# For well-known types (including nested Any), use ConvertMessage
# to ensure recursion depth is properly tracked
self.ConvertMessage(
value['value'],
sub_message,
'{0}.value'.format(path)
)By routing the call back through self.ConvertMessage, the _recursion_depth check is triggered before the nested content is processed. If _recursion_depth exceeds the limit, it throws a ParseError immediately, rather than letting the Python interpreter crash with a RecursionError later.
Exploiting this is trivially easy. We don't need shellcode or ROP chains; we just need JSON. The goal is to construct a nested structure that exceeds the Python interpreter's stack limit (usually 1000 frames) but would theoretically pass the Protobuf parser's default limit (usually 100) because of the bug.
The payload looks like this:
{
"@type": "type.googleapis.com/google.protobuf.Any",
"value": {
"@type": "type.googleapis.com/google.protobuf.Any",
"value": {
"@type": "type.googleapis.com/google.protobuf.Any",
"value": { ... repeat 1000 times ... }
}
}
}When json_format.ParseDict() eats this, it dives deep.
Any.Any (a WKT).Any.Eventually, CPython screams. A RecursionError is raised. If this exception isn't explicitly caught and handled (and most generic web frameworks won't catch a RecursionError gracefully during data binding), the worker process terminates. Do this continuously, and you have a persistent Denial of Service.
While this is "only" a Denial of Service, the context matters. Python Protobuf is widely used in API gateways, backend workers, and data processing pipelines.
Imagine a public-facing API endpoint that accepts JSON and converts it to Protobuf to talk to backend gRPC services. An attacker can hammer this endpoint with a few kilobytes of JSON data. Each request kills a worker process. If you are running a standard WSGI/ASGI server (like Gunicorn or Uvicorn) with a fixed number of workers, the attacker can starve the entire pool with very low bandwidth.
This isn't just about crashing a script; it's about resource exhaustion. The ease of exploitation (Network vector, Low complexity, No auth) gives it a CVSS score of 8.2 for a reason. It's a cheap, effective way to take down a Python-based microservice architecture.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:L| Product | Affected Versions | Fixed Version |
|---|---|---|
protobuf-python Google | >= 33.0 | See Vendor Advisory |
| Attribute | Detail |
|---|---|
| CWE | CWE-674 (Uncontrolled Recursion) |
| CVSS v4.0 | 8.2 (High) |
| Attack Vector | Network |
| Impact | Availability (DoS) |
| Vulnerable Function | _ConvertAnyMessage |
| Exploit Status | PoC Available |
The product does not properly control the amount of recursion that takes place, consuming excessive resources, such as memory or the program stack.
An OS command injection vulnerability in yt-dlp before 2026.06.09 allows unauthenticated remote attackers to execute arbitrary shell commands via crafted media metadata when a user processes media using the --exec post-processing parameter with unsafe string interpolation conversions.
An in-depth technical analysis of multiple security vulnerabilities in the self-hosted Docker API server of Crawl4AI up to version 0.8.7. These flaws include a critical arbitrary file write via symlink traversal and TOCTOU weakness, CRLF log injection, webhook header injection, and SSRF filter gaps. These have been remediated in version 0.8.8.
A technical evaluation of the Crawl4AI open-source web crawling and scraping library revealed a high-severity credential exfiltration vulnerability in its self-hosted Dockerized API server. The flaw arises from an unvalidated base_url parameter in request payloads and a dynamic prefix resolution mechanism that retrieves system environment variables. Unauthenticated remote attackers can leverage these features in tandem to extract host-level secrets or redirect configured LLM API keys to an external listener under their control.
The Crawl4AI Docker API server, in versions 0.8.6 and prior, contains multiple critical vulnerabilities including improper path sanitization, missing authentication on administration routes, hardcoded JWT secrets, and SSRF. These vulnerabilities allow remote, unauthenticated attackers to write arbitrary files, execute arbitrary code, and pivot into private cloud environments.
A local security vulnerability in the Nuxt development server (nuxt dev) allows local unprivileged users to access sensitive configuration files and source code. On Linux environments running Node.js 20+, Nuxt bound its internal vite-node IPC server to an abstract-namespace Unix socket without any peer authentication, enabling co-resident local users to connect and request module code directly.
Mozilla Bleach is an open-source HTML sanitizing library for Python. Versions up to and including 6.3.0 contain an incomplete filtering implementation in the URI validation logic ('sanitize_uri_value'). This logic fails to detect disallowed protocols, such as 'javascript:', if they contain Unicode invisible characters, whitespace characters, or characters with a code point greater than U+00A0. While standard-compliant web browsers do not directly execute invalid URI schemes containing these non-standard characters, downstream systems that normalize Unicode text by stripping invisible or non-ASCII characters can unintentionally reactivate the 'javascript:' prefix, causing Cross-Site Scripting (XSS). Additionally, this behavior violates Bleach's core sanitization contract by outputting URIs that bypass protocol allowlists configured by the caller.