The __proto__ Path to Object.prototype

Maor Caplan
In this writeup we’ll explore how a vulnerability in graphql-upload-minimal allowed attackers to pollute Object.prototype through the GraphQL file upload mechanism.
What is Prototype Pollution?
In JavaScript, almost everything is an object, and every object has a hidden link to another object, its prototype. When you access a property, JavaScript first checks the object itself then walks up this prototype chain looking for it.
Prototype pollution happens when an attacker injects properties into Object.prototype. Once polluted every object in the application inherits the malicious property
This flaw has a broad impact because it affects all objects created after the pollution, persists until the process restarts, and can bypass authentication and authorization checks, all from a single request.
Every JavaScript object has a hidden [[Prototype]] slot. JavaScript exposes this via the __proto__ property, if an attacker can control a property path, they can do
How GraphQL File Uploads Work
Before we see the exploit, let’s dig into GraphQL multipart uploads.
When uploading files via GraphQL, clients send a multipart/form-data request with three parts, wherever a file will go, you put null as a placeholder.
Why `null`? Because you can’t put a file directly into JSON.
sidenote: GraphQL wasn’t originally designed for file uploads. Because GraphQL operations are JSON-based, uploads are handled through a multipart convention layered on top of the protocol.
The map tells the server where each file should be plugged into the JSON. It maps file indexes to the JSON paths that should receive them.
This means: File #0 from the multipart request should be inserted into variables.file
These are the actual uploaded files, each file is attached as a normal multipart field with a key like `”0"` or `”1"` (matching the map).
The Vulnerable Code:
The server uses a function called deepSet to walk the path from the map and place the file.
The map field is completely user controlled. Whatever path you specify, deepSet will walk it.
A deepSet function lets you set a value deep inside a nested object using a dot-separated path string. For example
This is useful when you need to dynamically set properties without knowing the structure at compile time, JavaScript allows property access via bracket notation with strings:
A typical deepSet splits the path string and walks through the object:
The danger is that JavaScript doesn’t distinguish between normal properties and special ones like __proto__. If you do obj[“__proto__”], you’re accessing the object’s prototype chain directly.
Custom deepSet implementations are a common source of prototype pollution vulnerabilities. Note that this pattern appears under many names setPath, setDeep, setValue, setByPath, setNestedProperty, or even just set. The problem is developers focus on the happy path and forget that property names like __proto__ have special meaning in JavaScript.
Battle tested libraries have addressed these issues. For example, lodash.set includes prototype pollution protection, when you need dynamic property access prefer established libraries over custom implementations.
The original graphql-upload-minimal code:
No validation of path segments. The code blindly walks whatever path the user provides, including __proto__
The attacker can send a malicious multipart GraphQL upload request that looks normal on the surface, but the map is crafted to exploit prototype pollution. Let’s break down each part.
The Operation
This is a normal GraphQL upload mutation. The server expects one variable file, which is currently null (placeholder). Nothing malicious here yet.
The Map
This is the dangerous part. Normally the map would look something like {“0”: [“variables.file”]} but here the attacker tells the server to take file zero and put it into a property like __proto__.isAdmin
This means the attacker is trying to write into JavaScript’s global prototype object, not into variables.file, If the server blindly trusts the map it does
Which effectively becomes:
Then every object in the system inherits isAdmin = true
The File
This is the entire actual content of the uploaded “file.” It’s not a real file, it’s simply the text true. Because the map says this file goes tod __proto__.isAdmin, the attacker is setting __proto__.isAdmin = true
Defensive Measures
Never trust user input for property paths, always validate and block dangerous keys like __proto__. The GraphQL multipart map field is an often-overlooked attack surface that provides direct control over object paths. The maintainers of graphql-upload-minimal responded quickly to fix the issue, but the lesson remains: whenever you dynamically access object properties based on user input, validate those paths carefully.


