Skip to content

parseQuery creates objects with {} instead of Object.create(null), allowing __proto__ key corruption #2658

@FredKSchott

Description

@FredKSchott

Reproduction

Any Vue Router app using the default parseQuery implementation. Minimal test case:

import { parseQuery } from 'vue-router'
const q = parseQuery('__proto__=evil&foo=bar')
console.log(q)           // unexpected ghost keys from prototype chain
console.log(q.__proto__)  // 'evil' shadows Object.prototype

Steps to reproduce the bug

  1. Create a Vue app with Vue Router (or Nuxt, which uses Vue Router's default parseQuery)
  2. Send a request with __proto__ or constructor as a query parameter:
    curl 'http://localhost:3000/?__proto__=evil&foo=bar'
  3. Observe the rendered route.fullPath — it contains ghost parameters:
    /?foo=bar&0=[object+Object]&1=evil
    
  4. The constructor variant also leaks internal references:
    curl 'http://localhost:3000/?constructor=test'
    # Renders: /?constructor=function+Object()+{+[native+code]+}&constructor=test

Expected behavior

parseQuery should return a null-prototype object (via Object.create(null)) so that inherited properties like __proto__ and constructor cannot be accidentally read or overwritten. Query keys like __proto__ should be stored as plain data without affecting the object's prototype chain.

Actual behavior

parseQuery creates query objects with const query = {}. When ?__proto__=evil is parsed, "__proto__" in query evaluates to true (inherited property), and the subsequent query[key] = [currentValue] corrupts the object. This causes stringifyQuery to emit ghost parameters from the corrupted prototype chain.

The corruption is scoped to that specific object instance — it does not achieve persistent cross-request Object.prototype pollution. But it does cause unexpected query parameters to appear in route.fullPath, which could interfere with middleware or logic that inspects route.query.

Additional information

The root cause is in parseQuery (around line 1433-1449 in vue-router.cjs). Changing const query = {} to const query = Object.create(null) would fix this. Libraries like ufo already use Object.create(null) in their parseQuery implementation for this reason.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    🆕 Triaging

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions