Feb 8, 2026·6 min read·34 visits
The AdonisJS body parser initialized form field storage as a plain object (`{}`). This allowed attackers to inject properties into `Object.prototype` by sending form fields named `__proto__.key`. This pollutes the entire application runtime, affecting every object in the process.
A critical Prototype Pollution vulnerability in the AdonisJS `@adonisjs/bodyparser` package allows unauthenticated attackers to corrupt the global object prototype via crafted multipart form-data requests. By manipulating the `__proto__` property during field accumulation, attackers can trigger Denial of Service, authentication bypasses, or potentially Remote Code Execution depending on the application's gadget chain.
Web frameworks have a tough job. They stand as the bouncer at the club door, patting down every request that tries to get in. One of the messiest parts of that job is handling multipart/form-data. It's a protocol designed in a simpler time, used primarily for file uploads but often tasked with carrying complex metadata alongside those files.
AdonisJS, the "Laravel of Node.js," prides itself on being robust and opinionated. But in versions of @adonisjs/bodyparser prior to 10.1.3, the bouncer missed a spot. The component responsible for aggregating those metadata fields had a classic JavaScript blindness: it trusted that a "new object" was actually empty.
This isn't just about reading a bad file; it's about memory corruption in a language that technically doesn't let you touch memory. By slipping a specific key into a form upload, an attacker can modify the DNA of every object in the running application. It's the digital equivalent of sneezing in the salad bar—suddenly, everyone has your cold.
To understand this bug, you have to understand the quirk at the heart of JavaScript: the Prototype Chain. When you create a simple object in JS using const obj = {}, it isn't empty. It comes pre-loaded with a hidden link (__proto__) to Object.prototype. This is why you can call .toString() on an empty object and it works—it inherits that method.
The vulnerability lived in the FormFields class. The parser's job is to take a stream of form parts and organize them. If I send a field named username with value admin, the parser adds username: 'admin' to its internal storage.
The fatal flaw was how that storage was initialized:
export class FormFields {
// The root of all evil
#fields: any = {}
}By initializing #fields as {} (a plain object), the developer inadvertently exposed the __proto__ accessor. When the parser logic blindly assigns values based on input keys (#fields[key] = value), an attacker supplying a key like __proto__ isn't setting a property on the #fields object itself—they are traversing the chain and modifying the global Object.prototype. This is Prototype Pollution 101, but seeing it in a major framework's core parsing logic is a stark reminder that const x = {} is rarely safe for user input.
The fix is elegantly simple, yet it highlights exactly why the bug existed. The maintainers didn't need to rewrite the parsing logic or add complex sanitization regexes (which are often bypassed anyway). They just needed to change the nature of the storage container.
Here is the critical diff from commit 40e1c71f958cffb74f6b91bed6630dca979062ed:
--- a/src/form_fields.ts
+++ b/src/form_fields.ts
@@ -17,7 +17,7 @@ export class FormFields {
/**
* Internal storage for form fields
*/
- #fields: any = {}
+ #fields: any = Object.create(null)> [!NOTE]
> Why does this work?
> Object.create(null) creates a dictionary object that has no prototype. It doesn't inherit from Object.prototype. It has no __proto__, no constructor, and no toString. If an attacker tries to access __proto__ on this object, the result is undefined, and the assignment simply creates a literal property named "__proto__" on that specific object, rather than traversing up to the global scope.
This is the definitive defense against prototype pollution for key-value stores in JavaScript. If you are storing user input in an object, and you aren't using a Map or Object.create(null), you are likely vulnerable.
Let's construct a Proof of Concept (PoC). We don't need authentication; we just need an endpoint that accepts multipart/form-data. This could be a profile picture upload, a contact form with attachments, or a resume drop.
The goal is to set a property on Object.prototype that the application relies on later. A classic target is isAdmin (for poor authorization checks) or gadgets in other libraries.
Here is how we formulate the request using curl:
curl -X POST http://localhost:3333/upload \
-H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" \
--data-binary $'------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name="__proto__[polluted]"\r\n\r\ntrue\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--\r\n'What happens inside the engine:
__proto__[polluted].#fields.__proto__['polluted'] = 'true'.#fields was a plain object, __proto__ points to the global Object.prototype.polluted set to true.If the application has code like if (user.isAdmin) { grantAccess() }, and the user object doesn't explicitly define isAdmin, it will look up the prototype chain. If we managed to pollute isAdmin to true, we just bypassed authentication for the entire platform.
While "Authentication Bypass" sounds scary, it relies on the application having loose logic. However, Denial of Service (DoS) is almost guaranteed with this vulnerability.
An attacker can overwrite standard methods like toString or valueOf. Imagine if every time the application tried to convert an object to a string (for logging, for database queries, for API responses), it crashed because toString was no longer a function but a string containing "pwned".
// The DoS Payload
name="__proto__[toString]"
value="hacked"One request with this payload brings the Node.js process to its knees. Since AdonisJS (like most Node apps) is single-threaded (or clustered), crashing the process repeatedly leads to total service unavailability.
Furthermore, if the application uses template engines or other libraries that merge objects deeply, this can lead to Remote Code Execution (RCE). This usually requires a "gadget chain"—code that takes our polluted property and passes it into eval() or child_process.exec(). While not inherent to the bug itself, the potential is always there in a complex dependency tree.
If you are running AdonisJS, check your @adonisjs/bodyparser version immediately. You are vulnerable if you are below 10.1.3 (v10 branch) or 11.0.0-next.9 (v11 branch).
Remediation Steps:
npm update @adonisjs/bodyparser or npm install @adonisjs/core@latest.package-lock.json to ensure the installed version matches the patched release.__proto__, constructor, or prototype in the body content. This catches the low-hanging fruit before it even hits your app logic.Developers should learn the lesson here: Never trust the default prototype. When building hashmaps or dictionaries in JavaScript that accept user keys, always use new Map() or Object.create(null).
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
@adonisjs/bodyparser AdonisJS | < 10.1.3 | 10.1.3 |
@adonisjs/bodyparser AdonisJS | < 11.0.0-next.9 | 11.0.0-next.9 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1321 (Prototype Pollution) |
| CVSS v3.1 | 7.2 (High) |
| Attack Vector | Network (AV:N) |
| Authentication | None (PR:N) |
| EPSS Score | 0.00036 (10.29%) |
| Exploit Status | PoC Available |
| Patch Commit | 40e1c71f958cffb74f6b91bed6630dca979062ed |
The product receives input from an upstream component, but it does not restrict or incorrectly restricts the input from creating, modifying, or modifying the attributes of a prototype of the Base Object.
A vulnerability in the Slack and Mattermost platform adapters for NousResearch hermes-agent permits an unauthenticated remote attacker to execute arbitrary mass mentions. By leveraging prompt injection, an attacker can bypass output sanitization logic and trigger workspace-wide notification exhaustion.
CVE-2026-9306 is a critical unauthenticated Insecure Direct Object Reference (IDOR) vulnerability located in the QuantumNous new-api application, affecting versions up to and including 0.12.1. The flaw is caused by improper middleware ordering combined with a lack of object-level authorization checks. This allows remote, unauthenticated attackers to retrieve sensitive Midjourney images belonging to other users by supplying a valid task identifier.
The instagrapi library prior to version 2.6.9 contains an improper input validation vulnerability within its challenge handling mechanism. Maliciously crafted server responses can manipulate the client into forwarding session cookies and credentials to an external attacker-controlled domain.
GHSA-QQQM-5547-774X is a critical path traversal vulnerability in the FileBrowser Quantum application, specifically within the Go backend package. The vulnerability resides in the HTTP handler responsible for processing bulk file modifications via the public API. Unauthenticated attackers can exploit an order-of-operations flaw in the path sanitization logic to bypass intended directory restrictions. This allows adversaries to arbitrarily read, move, and overwrite files on the underlying filesystem by supplying specially crafted HTTP PATCH requests.
The qs query string parsing and serialization library for Node.js is vulnerable to a synchronous Denial of Service (DoS) attack. The vulnerability manifests as a process-terminating TypeError when processing arrays with null or undefined elements under specific configuration parameters.
The aiosend library prior to version 3.0.6 contains a pre-authentication Denial of Service (DoS) vulnerability in its webhook handling mechanism. The software processes and deserializes incoming JSON payloads before verifying the cryptographic signature, allowing unauthenticated attackers to exhaust server CPU and memory resources by sending large, complex payloads.