VYPR
High severityNVD Advisory· Published Nov 26, 2022· Updated Apr 29, 2025

CVE-2022-24999

CVE-2022-24999

Description

qs before 6.10.3, as used in Express before 4.17.3 and other products, allows attackers to cause a Node process hang for an Express application because an __ proto__ key can be used. In many typical Express use cases, an unauthenticated remote attacker can place the attack payload in the query string of the URL that is used to visit the application, such as a[__proto__]=b&a[__proto__]&a[length]=100000000. The fix was backported to qs 6.9.7, 6.8.3, 6.7.3, 6.6.1, 6.5.3, 6.4.1, 6.3.3, and 6.2.4 (and therefore Express 4.17.3, which has "deps: qs@6.9.7" in its release description, is not vulnerable).

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

qs before 6.10.3 allows unauthenticated remote attackers to cause a Node.js process hang via __proto__ key pollution in query strings.

Vulnerability

Overview

The qs library, a widely used querystring parser for Node.js and the default parser in Express < 4.17.3, is vulnerable to a denial-of-service (DoS) condition identified as CVE-2022-24999 [2]. The root cause is the parser's failure to ignore __proto__ keys when constructing objects from query strings. By default, qs prevents prototype pollution but this flaw allowed specially crafted keys like a[__proto__]=b to bypass the intended protection, leading to the corruption of the parsed object's prototype chain [1][3].

Exploitation and

Attack Surface

An unauthenticated remote attacker can exploit this vulnerability by sending an HTTP request containing a malicious query string, such as a[__proto__]=b&a[__proto__]&a[length]=100000000 [2]. This payload causes the Node process to enter an infinite loop or become unresponsive, effectively hanging the Express application. No authentication or special network access is required; a typical Express web application processing URL query strings is vulnerable [2].

Impact

Successful exploitation results in a complete denial of service for the targeted Node.js/Express application. The attacker can render the application unresponsive, disrupting service availability for legitimate users. This vulnerability does not require any authentication, increasing its potential for widespread, easy exploitation [2].

Mitigation and

Fix

The fix was implemented in qs version 6.10.3 and backported to several earlier releases (6.9.7, 6.8.3, 6.7.3, 6.6.1, 6.5.3, 6.4.1, 6.3.3, and 6.2.4) [2]. Express 4.17.3 incorporates the fixed qs via the dependency [email protected] and is not vulnerable [2]. The code change adds a check to skip processing when the root key is __proto__, as shown in the referenced commits [3][4]. Users should update their qs or Express dependencies to the patched versions immediately.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
qsnpm
>= 6.10.0, < 6.10.36.10.3
qsnpm
>= 6.9.0, < 6.9.76.9.7
qsnpm
>= 6.8.0, < 6.8.36.8.3
qsnpm
>= 6.7.0, < 6.7.36.7.3
qsnpm
>= 6.6.0, < 6.6.16.6.1
qsnpm
>= 6.5.0, < 6.5.36.5.3
qsnpm
>= 6.4.0, < 6.4.16.4.1
qsnpm
>= 6.3.0, < 6.3.36.3.3
qsnpm
< 6.2.46.2.4

Affected products

9

Patches

9
ba24e74dd179

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -68,7 +68,7 @@ var parseObject = function parseObject(chain, val, options) {
             ) {
                 obj = [];
                 obj[index] = parseObject(chain, val, options);
    -        } else {
    +        } else if (cleanRoot !== '__proto__') {
                 obj[cleanRoot] = parseObject(chain, val, options);
             }
         }
    
  • test/parse.js+60 0 modified
    @@ -476,6 +476,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
fc3682776670

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -146,7 +146,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
                 ) {
                     obj = [];
                     obj[index] = leaf;
    -            } else {
    +            } else if (cleanRoot !== '__proto__') {
                     obj[cleanRoot] = leaf;
                 }
             }
    
  • test/parse.js+60 0 modified
    @@ -620,6 +620,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
f945393cfe44

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -146,7 +146,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
                 ) {
                     obj = [];
                     obj[index] = leaf;
    -            } else {
    +            } else if (cleanRoot !== '__proto__') {
                     obj[cleanRoot] = leaf;
                 }
             }
    
  • test/parse.js+60 0 modified
    @@ -632,6 +632,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
732052599363

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -115,7 +115,7 @@ var parseObject = function (chain, val, options) {
                 ) {
                     obj = [];
                     obj[index] = leaf;
    -            } else {
    +            } else if (cleanRoot !== '__proto__') {
                     obj[cleanRoot] = leaf;
                 }
             }
    
  • test/parse.js+60 0 modified
    @@ -530,6 +530,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
727ef5d34605

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -68,7 +68,7 @@ var parseObject = function parseObjectRecursive(chain, val, options) {
             ) {
                 obj = [];
                 obj[index] = parseObject(chain, val, options);
    -        } else {
    +        } else if (cleanRoot !== '__proto__') {
                 obj[cleanRoot] = parseObject(chain, val, options);
             }
         }
    
  • test/parse.js+60 0 modified
    @@ -487,6 +487,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
4310742efbd8

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -68,7 +68,7 @@ var parseObject = function parseObjectRecursive(chain, val, options) {
             ) {
                 obj = [];
                 obj[index] = parseObject(chain, val, options);
    -        } else {
    +        } else if (cleanRoot !== '__proto__') {
                 obj[cleanRoot] = parseObject(chain, val, options);
             }
         }
    
  • test/parse.js+60 0 modified
    @@ -487,6 +487,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
8b4cc14cda94

[Fix] `parse`: ignore `__proto__` keys

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -136,7 +136,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
                 ) {
                     obj = [];
                     obj[index] = leaf;
    -            } else {
    +            } else if (cleanRoot !== '__proto__') {
                     obj[cleanRoot] = leaf;
                 }
             }
    
  • test/parse.js+60 0 modified
    @@ -629,6 +629,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
e799ba57e573

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -135,7 +135,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
                 ) {
                     obj = [];
                     obj[index] = leaf;
    -            } else {
    +            } else if (cleanRoot !== '__proto__') {
                     obj[cleanRoot] = leaf;
                 }
             }
    
  • test/parse.js+60 0 modified
    @@ -620,6 +620,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    
ed0f5dcbef4b

[Fix] `parse`: ignore `__proto__` keys (#428)

https://github.com/ljharb/qsJordan HarbandDec 28, 2021via ghsa
2 files changed · +61 1
  • lib/parse.js+1 1 modified
    @@ -70,7 +70,7 @@ var parseObject = function (chain, val, options) {
                 ) {
                     obj = [];
                     obj[index] = leaf;
    -            } else {
    +            } else if (cleanRoot !== '__proto__') {
                     obj[cleanRoot] = leaf;
                 }
             }
    
  • test/parse.js+60 0 modified
    @@ -530,6 +530,66 @@ test('parse()', function (t) {
             st.end();
         });
     
    +    t.test('dunder proto is ignored', function (st) {
    +        var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
    +        var result = qs.parse(payload, { allowPrototypes: true });
    +
    +        st.deepEqual(
    +            result,
    +            {
    +                categories: {
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload'
    +        );
    +
    +        var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
    +
    +        st.deepEqual(
    +            plainResult,
    +            {
    +                __proto__: null,
    +                categories: {
    +                    __proto__: null,
    +                    length: '42'
    +                }
    +            },
    +            'silent [[Prototype]] payload: plain objects'
    +        );
    +
    +        var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
    +
    +        st.notOk(Array.isArray(query.categories), 'is not an array');
    +        st.notOk(query.categories instanceof Array, 'is not instanceof an array');
    +        st.deepEqual(query.categories, { some: { json: 'toInject' } });
    +        st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
    +            {
    +                foo: {
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values'
    +        );
    +
    +        st.deepEqual(
    +            qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
    +            {
    +                __proto__: null,
    +                foo: {
    +                    __proto__: null,
    +                    bar: 'stuffs'
    +                }
    +            },
    +            'hidden values: plain objects'
    +        );
    +
    +        st.end();
    +    });
    +
         t.test('can return null objects', { skip: !Object.create }, function (st) {
             var expected = Object.create(null);
             expected.a = Object.create(null);
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

16

News mentions

0

No linked articles in our index yet.