Cloudanix VulnDB

CVE-2026-32874

High · 7.5

CVSS

7.5

EPSS

0.7%

Published

Mar 20, 2026

CISA due date

—

Cloudanix analysis

Next patch cycle SSVC: Track

A widely used Python JSON library can be forced to keep using more memory until the service slows down or crashes when it reads untrusted data containing huge numbers.

Exploitation
Remote Unauthenticated
Attacker needs
None
Complexity
Low
Impact
Denial Of Service
Component
Library
Class
Denial of service
Remediation
Upgrade
Effort
Medium
Patch risk
Low

What it actually does

UltraJSON versions 5.4.0 through 5.11.0 leak memory while parsing JSON integers whose values fall outside the signed/unsigned 64-bit range. Each parse allocates a copy of the integer’s decimal string plus a terminating NULL and never frees it, whether the value is accepted or rejected for exceeding Python’s int-to-string digit limit. With no overall payload-size cap, a single request can leak an arbitrarily large amount of heap, and repeated parses accumulate until the process is exhausted. Any code path that calls ujson.load(), ujson.loads(), or ujson.decode() on attacker-controlled input is therefore susceptible to a remote, unauthenticated availability failure (C:N/I:N/A:H).

Why it matters to the business

Services that decode untrusted JSON with UltraJSON can be taken offline by memory exhaustion—worker crashes, container OOM kills, elevated cloud spend, and cascading timeouts for dependent APIs. Although this is not listed in CISA KEV, has no public exploit maturity, and carries a still-low EPSS (~0.0068), the score has risen about 43% over 30 days, the issue has been public for roughly 200 days, and UltraJSON is a common drop-in accelerator in Python web and data pipelines, so unpatched internet-facing parsers remain a practical denial-of-service risk.

How an attack unfolds

An unauthenticated attacker locates an interface that feeds client JSON into UltraJSON, then submits documents whose number tokens are extremely large decimal integers. Each decode leaks a copy of that digit string regardless of parse success or rejection. By repeating requests or inflating token size up to any existing body limit, the attacker steadily inflates process RSS until the worker is killed or the host becomes unresponsive. No credentials, user interaction, or memory corruption primitive is required; the condition is purely an accumulating leak on the parse path.

How to fix it

Upgrade the ultrajson (ujson) package to 5.12.0 or later in every environment that parses untrusted JSON. If an immediate upgrade is not possible, stop calling ujson on untrusted input (use the standard library json module or another maintained parser), enforce strict HTTP/body size limits and per-request memory ceilings, reject or truncate oversized numeric tokens before decode, and recycle workers aggressively (max RSS / max requests) so a leak cannot grow unbounded. Pin and verify the upgraded wheel in lockfiles so downstream images and serverless layers cannot silently pull 5.4.0–5.11.0.

How to confirm the fix worked

Confirm installed ujson is 5.12.0+ (pip show ujson, poetry/pipenv/uv lock, container SBOM). Inventory call sites of ujson.load/loads/decode on untrusted data. After upgrade, exercise JSON decode of oversized integers under a memory profiler or RSS watch and verify heap does not grow across iterations. Confirm request-size and worker-memory limits are enforced in staging. Scan images and lockfiles for the vulnerable version range 5.4.0–5.11.0.

Typical attack path

  1. Identify a network-facing or internally reachable service that decodes untrusted JSON with UltraJSON (ujson.load/loads/decode).
  2. Send JSON containing integer tokens outside the 64-bit range and/or longer than Python’s maximum int-string digit limit.
  3. Repeat or enlarge those requests so leaked string copies accumulate in the decoder process.
  4. Continue until workers are OOM-killed, latency spikes, or the service stops answering legitimate clients.

How to detect it

This flaw leaves no distinctive implant; look for availability artifacts around JSON ingest. Review application, reverse-proxy, and container metrics for Python processes (gunicorn, uvicorn, uwsgi, celery, lambda/container runtimes) whose RSS grows in step with JSON request volume and then disappear via OOM. Correlate 5xx/timeouts on JSON Content-Type routes with access logs showing unusually large bodies or long numeric tokens. Inspect kernel/container OOM killer messages naming those workers, APM memory traces on ujson decode spans, and WAF/body-size rejects. There are no named threat actors or ransomware campaigns associated with this CVE; treat unexplained, request-driven heap growth on unpatched ujson as suspicious even without a payload sample.

  • ujson==5.4.0
  • ujson==5.5.0
  • ujson==5.6.0
  • ujson==5.7.0
  • ujson==5.8.0
  • ujson==5.9.0
  • ujson==5.10.0
  • ujson==5.11.0
  • Killed process * python * (oom)
  • Out of memory: Killed process * (gunicorn)
  • Out of memory: Killed process * (uvicorn)
  • Out of memory: Killed process * (uwsgi)
  • Content-Type: application/json

What your security team should do

  • Patch or rebuild all applications and images to ultrajson 5.12.0 or later as the primary control.
  • Prioritize internet-facing and multi-tenant JSON APIs that still import ujson 5.4.0–5.11.0; treat them as DoS-exposed until upgraded.
  • Add SCA/lockfile policy to block the vulnerable range and fail CI on ujson<5.12.0.
  • Apply compensating controls now: payload size limits, worker RSS/request recycling, and avoid ujson on untrusted input where upgrade lags.
  • Hunt for historical and ongoing memory-growth incidents on Python JSON workers even though EPSS is low and KEV is not set.

The record

UltraJSON is a fast JSON encoder and decoder written in pure C with bindings for Python 3.7+. Versions 5.4.0 through 5.11.0 contain an accumulating memory leak in JSON parsing large (outside of the range [-2^63, 2^64 - 1]) integers. The leaked memory is a copy of the string form of the integer plus an additional NULL byte. The leak occurs irrespective of whether the integer parses successfully or is rejected due to having more than sys.get_int_max_str_digits() digits, meaning that any sized leak per malicious JSON can be achieved provided that there is no limit on the overall size of the payload. Any service that calls ujson.load()/ujson.loads()/ujson.decode() on untrusted inputs is affected and vulnerable to denial of service attacks. This issue has been fixed in version 5.12.0.

Affected packages

Ecosystem Package Introduced Fixed

Related vulnerabilities