CVE-2026-32874
High · 7.5CVSS
7.5
EPSS
0.7%
Published
Mar 20, 2026
CISA due date
—
Cloudanix analysis
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
- Identify a network-facing or internally reachable service that decodes untrusted JSON with UltraJSON (ujson.load/loads/decode).
- Send JSON containing integer tokens outside the 64-bit range and/or longer than Python’s maximum int-string digit limit.
- Repeat or enlarge those requests so leaked string copies accumulate in the decoder process.
- 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
- CVE-2026-109278 ImageMagick before 6.9.13-57 and 7.x before 7.1.2-32 contains a memory leak in the… CWE-401
- CVE-2026-109270 ImageMagick before 7.1.2-32 contains a memory leak vulnerability in the -process option… CWE-401
- CVE-2026-109262 ImageMagick before 6.9.13-57 and 7.x before 7.1.2-32 contains a memory leak in the SVG… CWE-401
- CVE-2026-109263 ImageMagick before 6.9.13-57 and 7.x before 7.1.2-32 contains a memory leak in the YUV… CWE-401
- CVE-2026-109261 ImageMagick before 6.9.13-57 and 7.x before 7.1.2-32 contains a memory leak in the GIF… CWE-401
- CVE-2026-109260 ImageMagick before 6.9.13-57 and 7.x before 7.1.2-32 contains a memory leak in the PNG… CWE-401
- CVE-2026-104874 Multidict is an implementation of a multidict data structure. CWE-401
- CVE-2026-98162 In the Linux kernel, the following vulnerability has been resolved: smb/server: fix tree… CWE-401