Skip to content

fix(security): Prototype-pollution write via __proto__ route param in route-separation user loader - #7496

Open
failsafesecurity wants to merge 1 commit into
expressjs:masterfrom
failsafesecurity:openclaw/fix-prototype-pollution-write-via-proto-rout
Open

failsafesecurity wants to merge 1 commit into
expressjs:masterfrom
failsafesecurity:openclaw/fix-prototype-pollution-write-via-proto-rout

Conversation

@failsafesecurity

Copy link
Copy Markdown

Security fix: Prototype-pollution write via proto route param in route-separation user loader

Severity: MEDIUM (CVSS 6.9)
CWE: CWE-1321
File: examples/route-separation/user.js

What this fixes

In route-separation, user.load does req.user = users[id] (user.js:16) where users is an Array and id is the raw route param (index.js:41 app.all('/user/:id{/:op}', user.load)). For id='proto' users['proto'] resolves to Array.prototype (truthy), so load() passes the existence check while req.user is actually Array.prototype. The subsequent PUT /user/proto/edit then executes req.user.name = body.user.name and req.user.email = body.user.email (user.js:44-45), writing attacker-controlled values onto Array.prototype. This is a single unauthenticated PUT that corrupts process-global state: every array and every object lacking its own name/email inherits the injected values. A GET /user/proto/view read-back renders the polluted values via Express's own array/params objects.

The change

Validated the route param as a numeric index and checked it as an own property before indexing the users array, so prototype keys like proto/constructor/prototype cannot resolve to Array.prototype and get mutated by update().

Impact if unpatched

One unauthenticated PUT mutates Array.prototype for the whole Node process, poisoning every array/object in every concurrent request and any downstream library that reads .name/.email (template rendering, logging, comparisons). This is process-global state corruption reachable cross-request — a primitive that is frequently a stepping stone to full RCE in real applications. The constructor variant additionally crashes the request (500) exposing a stack trace.


Prepared by OpenClaw BountyBot. Finding surfaced by Failsafe Nexus (Pandora) and the patch adversarially reviewed by GLM. Please review carefully — automated patches are a starting point, not a guarantee.

Comment on lines 15 to +17
var id = req.params.id;
req.user = users[id];
if (req.user) {
if (/^\d+$/.test(id) && Object.prototype.hasOwnProperty.call(users, id)) {
req.user = users[id];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var id = req.params.id;
req.user = users[id];
if (req.user) {
if (/^\d+$/.test(id) && Object.prototype.hasOwnProperty.call(users, id)) {
req.user = users[id];
var id = Number.parseInt(req.params.id);
req.user = users[id];
if (req.user) {

The suggestion is displayed incorrectly by GitHub, but simply wrapping req.params.id in Number.parseInt() is a simpler way to achieve the same thing.

@NachikethReddyY NachikethReddyY left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, Rouge Agent!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants