Oct 6, 2026·6 min read·4 visits
Unauthenticated remote attackers can freeze the Payload CMS Node.js event loop and exhaust heap memory by sending malformed multipart HTTP requests, exploiting a ReDoS flaw in the boundary regex parser and a lack of request size limits in stream processing.
Payload CMS, a popular open-source headless Content Management System, contains a critical Regular Expression Denial of Service (ReDoS) and uncontrolled resource consumption vulnerability in versions prior to 3.90.0 and canary versions prior to 4.0.0-canary.34. Due to nested quantifiers in the multipart boundary regex validation pattern, and the absence of streaming backpressure controls, remote attackers can trigger catastrophic backtracking and memory exhaustion. This blocks the single-threaded Node.js event loop, resulting in a persistent and complete Denial of Service (DoS).
Payload CMS uses multipart request processing to facilitate file uploads and complex object parsing. The application exposes specific endpoints (such as /api/media and registration routes) that allow unauthenticated clients to transmit multipart payloads.
In vulnerable versions of Payload CMS, the initial validation logic evaluates the Content-Type header using an inefficient regular expression designed to confirm the presence of valid multipart parameters. If the header pattern deviates slightly from the expected format at the termination of the string, the underlying Javascript V8 engine falls into catastrophic backtracking.
Simultaneously, the secondary processing stage utilizes an unbounded stream parser that feeds incoming data directly into Busboy. The implementation lacks global size limits and fails to process socket events with proper backpressure. This omission enables concurrent attackers to consume substantial system memory, resulting in process termination or starvation of system resources.
The root cause of the vulnerability resides in two separate software components. First, in isEligibleRequest.ts, a regular expression is executed to parse the client-supplied Content-Type header. The vulnerable regex pattern is defined as:
const ACCEPTABLE_CONTENT_TYPE =
/multipart\/[\w'"()+,./:<=>?@[\\\]^-]+(?:; ?[\w'"()+,./:<=>?@[\\\]^-]*)+$/iThe pattern uses a nested quantifier sequence inside the non-capturing group (?:; ?[\w'"()+,./:<=>?@[\\\]^-]*)+$. Because the inner character class is quantified with * (zero or more matches) and the outer group is quantified with + (one or more matches), a string containing multiple nested semicolons and spaces (for example, ; ; ; ; ;) can be split and evaluated in an exponential number of ways by the engine.
When a remote client supplies a string containing thousands of trailing semicolons followed by an invalid character such as {, the V8 regular expression engine spends exponential time backtracking to try every possible path permutation. Because the Node.js event loop runs on a single execution thread, the catastrophic backtracking block locks the execution context, preventing the application from serving other requests.
Additionally, the multipart parsing routine in processMultipart.ts lacked backpressure handling and overall boundary checks. The logic read from the stream reader inside a standard while loop, invoking busboy.write() synchronously. This pattern ignores the write buffer state, meaning slow clients or extremely large requests can force the application to buffer infinite amounts of data directly in heap memory, causing Out-Of-Memory (OOM) crashes.
In the vulnerable version of the codebase, the stream consumption loop handled chunks without awaiting the write state of the consumer. This allowed the node buffer space to grow unrestricted. Below is the vulnerable stream processing code:
// packages/payload/src/uploads/fetchAPI-multipart/processMultipart.ts
while (parsingRequest) {
const { done, value } = await reader!.read()
if (done) {
parsingRequest = false
busboy.end()
}
if (value && !shouldAbortProccessing) {
// Synchronous write ignores backpressure constraints
busboy.write(value)
}
}In contrast, the patched implementation defines a default maximum request limit (DEFAULT_REQUEST_SIZE_LIMIT) of 50 MiB and wraps the write operations in an asynchronous pump() function. The new logic respects write callbacks and dynamically calculates the current incoming byte length against the configured limit:
// Patched version of processMultipart.ts
const pump = async () => {
try {
while (!failure) {
const { done, value } = await reader.read()
if (failure) {
break
}
if (done) {
busboy.end()
break
}
requestSize += value.byteLength
if (requestSize > requestSizeLimit) {
const err = requestSizeLimitError()
fail(err)
// Terminate and cancel the stream to stop data flow
await reader.cancel(err).catch(() => {})
break
}
// Await callback of busboy.write to ensure backpressure is resolved
await new Promise<void>((resolve, reject) => {
busboy.write(value, (err?: Error | null) => (err ? reject(err) : resolve()))
})
}
} finally {
reader.releaseLock()
}
}This safe fail-closed structure forces immediate stream destruction when the bytes exceed limits, saving both process memory and thread CPU time.
An attacker can exploit this vulnerability without authenticating to the target Payload CMS instance. The attack consists of formulating a crafted HTTP request with a malformed header designed to trigger catastrophic backtracking. Below is the sequence of events during a typical attack vector:
To exploit the ReDoS flaw, an attacker sends an HTTP header resembling the following:
Content-Type: multipart/form-data; boundary=bar; a=1; a=1; a=1; [repeated 4000 times] {The trailing curly brace { prevents the regular expression from matching successfully. The engine is then forced to evaluate every subset permutation of the repeated parameter assignments, consuming 100 percent of the host CPU core.
To exploit the uncontrolled stream processing, the attacker starts writing an endless stream of dummy bytes inside the body of a valid multipart payload. If the application server fails to enforce rate limiting or reverse proxy timeouts, the Payload Node process buffers the stream data globally on the heap, triggering a process crash due to memory exhaustion.
The absolute resolution is to upgrade the application dependency to an official patched release. For environments operating on the major 3.x branch, upgrade to 3.90.0 or higher. For canary development tracks, upgrade to 4.0.0-canary.34 or later.
In cases where immediate package upgrade is not feasible, organizations can enforce mitigations at the edge or ingress proxy layer. Configure the Web Application Firewall (WAF) or ingress controller to filter incoming HTTP request headers. Establish a ceiling constraint on the maximum length of the Content-Type header (for example, limiting it to 256 bytes). Legitimate boundary strings rarely exceed this limit.
Additionally, implement request payload body limitations at the reverse proxy layer (such as Nginx's client_max_body_size directive). This ensures that large streaming uploads are dropped prior to being processed by the downstream Node.js service, preventing stream exhaustion vectors.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
payload payloadcms | >= 3.0.0, < 3.90.0 | 3.90.0 |
payload payloadcms | >= 4.0.0-canary.0, < 4.0.0-canary.34 | 4.0.0-canary.34 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1333, CWE-400 |
| Attack Vector | Network |
| CVSS Score | 8.7 (High) |
| Exploit Status | poc |
| KEV Status | not-listed |
| Remediation Status | patched |
Inefficient Regular Expression Complexity (ReDoS) leading to CPU starvation, coupled with Uncontrolled Resource Consumption (memory heap exhaustion) due to missing backpressure controls in stream parsing.
A sensitive data exposure vulnerability in Payload CMS allows authenticated low-privilege users to retrieve decrypted, plaintext API keys of other users, including administrators, leading to full administrative account takeover and privilege escalation.
CVE-2026-86540 is a high-severity arbitrary code execution vulnerability in knowns, a repository management tool. The vulnerability occurs when the application parses and executes unvalidated language server binary overrides defined within a project's local configuration file.
An Improper Access Control vulnerability (CWE-284) in Payload CMS prior to version 3.90.0 and 4.0.0-canary.34 allows authenticated, low-privileged users to bypass field-level access control restrictions and overwrite the password of other accounts, leading to complete account takeover and privilege escalation.
Payload CMS was discovered to use an insecure default configuration for its password-hashing mechanism. The system requested a 512-byte key from PBKDF2-HMAC-SHA256 with 25,000 iterations, creating a severe cryptographic asymmetry. While the defending server sequentially computed 16 blocks of key material (equivalent to 400,000 internal iterations), an offline attacker only needed to compute the first 32-byte block to verify password guesses. This allowed offline attackers to crack stolen database hashes 16 times faster than intended by the security design.
VectorFreed identifies a critical Use-After-Free (UAF) memory corruption vulnerability in librsvg (CVE-2026-96889), which manifests when parsing structured SVG documents containing nested XML inclusions (XIncludes) and duplicate entity declarations. The flaw results from an entity ownership conflict where librsvg prematurely deallocates an xmlEntity structure still actively referenced by the underlying libxml2 parser context. When transitively compiled into downstream applications such as the high-performance sharp image processing library, this vulnerability facilitates denial of service and unauthenticated remote code execution on the host operating system.
CVE-2026-102275 (GHSA-x33g-cr3x-6449) is a public/private key identity confusion vulnerability in PyJWT versions 2.1.0 through 2.14.0. When importing Octet Key Pair (OKP) JSON Web Keys (JWKs) representing Ed25519 or Ed448 curves, PyJWT fails to verify that the public parameter 'x' matches the private parameter 'd'. An attacker can construct a hybrid JWK combining a victim's public key with the attacker's private key. In protocols like DPoP that bind sessions via public key thumbprints, this allows the attacker to authenticate as the victim while signing proofs with their own private key, fully bypassing sender-constrained security guarantees.