En-tête négligent

CVE-2019-11358: jQuery Prototype Pollution Explained

CVE-2019-11358 is one of the most important examples of how an apparently small implementation detail in a widely deployed JavaScript library can create security consequences far beyond the vulnerable function itself.

The vulnerability affects versions of jQuery before 3.4.0 and involves the deep-copy behavior of jQuery.extend(). When an application performs a recursive merge using attacker-influenced data, an enumerable __proto__ property can cause properties to be written into JavaScript’s native Object.prototype.

That condition is known as prototype pollution.

The official CVE description states that jQuery before version 3.4.0 mishandles jQuery.extend(true, {}, ...) when an unsanitized source object contains an enumerable __proto__ property. The result can be modification of the native Object.prototype. (NVD)

The jQuery project fixed the behavior in version 3.4.0. Its release notes specifically describe the issue as an Object.prototype pollution vulnerability and demonstrate the vulnerable behavior using a JSON object containing __proto__. (jQuery Blog)

Although CVE-2019-11358 was disclosed in 2019, it remains relevant because jQuery has been embedded into an enormous number of older web applications, CMS deployments, administrative interfaces, appliances, vendor dashboards, bundled JavaScript files, Rails applications, WebJars, and long-lived enterprise systems.

More importantly, CVE-2019-11358 is an excellent case study for understanding a larger class of vulnerabilities:

a prototype pollution primitive does not automatically equal XSS or code execution.

The eventual security impact depends on how polluted properties are consumed later in the application.

That distinction is critical when assessing real-world exposure.

CVE-2019-11358 at a Glance

AttributDétails
CVECVE-2019-11358
VulnérabilitéjQuery Prototype Pollution
Primary weaknessImproper modification of Object.prototype
CWECWE-1321
Affected upstream jQueryVersions before 3.4.0
Fixed upstream versionjQuery 3.4.0
Vulnerable API patternjQuery.extend(true, target, source)
Dangerous property__proto__
NVD CVSS v3.16.1 Medium
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Possible impactApplication-dependent; may include DOM XSS, logic manipulation, or denial of service
Disclosure periodApril 2019

NVD currently classifies CVE-2019-11358 as CWE-1321 and assigns it a CVSS v3.1 base score of 6.1. (NVD)

GitHub’s reviewed advisory similarly identifies jQuery releases before 3.4.0 as affected and version 3.4.0 as patched. (GitHub)

The important part is not merely the version number, however.

To understand whether an application is actually exploitable, we first need to understand what prototype pollution does.

What Is JavaScript Prototype Pollution?

JavaScript uses prototype-based inheritance.

When you create an ordinary object:

const user = {};

that object generally inherits properties and methods through a prototype chain that eventually reaches:

Object.prototype

Par exemple :

const user = {};

console.log(user.toString);

utilisateur does not need to define its own toString function. JavaScript finds it through the prototype chain.

Conceptually:

user
  |
  v
Object.prototype
  |
  v
null

This design becomes dangerous when an attacker can modify Object.prototype.

Suppose:

Object.prototype.isAdmin = true;

Now consider:

const alice = {};
const bob = {};

console.log(alice.isAdmin);
console.log(bob.isAdmin);

Both can return:

vrai

even though neither object explicitly contains an isAdmin propriété.

That happens because JavaScript searches their prototype chain.

The danger is therefore systemic: changing the prototype can influence many otherwise unrelated objects in the same JavaScript environment.

Prototype pollution vulnerabilities provide a mechanism through which attacker-controlled input can cause exactly this sort of modification.

PortSwigger describes client-side prototype pollution as a situation where user-controlled data is converted into an object and merged in a way that allows special property names such as __proto__ to modify global prototypes. Critically, PortSwigger also emphasizes that prototype pollution normally needs an exploitable gadget before it becomes a concrete vulnerability such as DOM XSS. (PortSwigger)

That distinction is central to CVE-2019-11358.

Pourquoi __proto__ Matters

__proto__ historically exposes an object’s prototype.

Consider a normal object:

const object = {};

console.log(object.__proto__ === Object.prototype);

In many JavaScript environments this evaluates to:

vrai

Problems arise when a recursive merge implementation treats __proto__ as though it were just another ordinary property.

Imagine receiving:

{
  "__proto__": {
    "polluted": "yes"
  }
}

A safe implementation should treat this key carefully.

An unsafe recursive merge might instead descend through the prototype reference and begin writing properties into Object.prototype.

Afterwards:

({}).polluted

could return:

yes

That is the essence of the vulnerability.

The Vulnerable jQuery Pattern

The jQuery API at the center of CVE-2019-11358 is:

jQuery.extend()

or its common shorthand:

$.extend()

The method was widely used to merge configuration objects.

Par exemple :

const defaults = {
    theme: "light",
    language: "en"
};

const userOptions = {
    theme: "dark"
};

const options = $.extend({}, defaults, userOptions);

The resulting object becomes:

{
    theme: "dark",
    language: "en"
}

This is normal behavior.

jQuery also supports recursive merging when the first argument is vrai:

$.extend(true, {}, defaults, userOptions);

Les vrai tells jQuery to perform a deep merge.

Consider:

const defaults = {
    ui: {
        theme: "light",
        animations: true
    }
};

const custom = {
    ui: {
        theme: "dark"
    }
};

const config = $.extend(true, {}, defaults, custom);

Instead of replacing the whole nested ui object, jQuery recursively combines it.

That convenience created the vulnerable path.

The Root Cause of CVE-2019-11358

How CVE-2019-11358 Pollutes Object.prototype

The vulnerable implementation iterated through properties contained in the source object.

Simplifying the old behavior:

for (name in options) {
    copy = options[name];

    if (deep && copy && isPlainObject(copy)) {
        src = target[name];

        target[name] = jQuery.extend(
            deep,
            src,
            copy
        );
    } else {
        target[name] = copy;
    }
}

For ordinary properties such as:

theme
language
timeout
headers

this is exactly what developers expect.

But __proto__ is not an ordinary property.

Suppose the source comes from:

JSON.parse(
    '{"__proto__":{"polluted":true}}'
);

The recursive merge begins processing:

__proto__

Instead of treating it as dangerous metadata, vulnerable versions of jQuery.extend() could recursively merge its contents into the object’s inherited prototype.

The official jQuery disclosure illustrated essentially this behavior:

jQuery.extend(
    true,
    {},
    JSON.parse(
        '{"__proto__":{"test":true}}'
    )
);

On a vulnerable release:

console.log("test" in {});

could return:

vrai

The jQuery team documented this exact behavior when announcing version 3.4.0. (jQuery Blog)

Safe Proof of Concept for CVE-2019-11358

A minimal validation test does not need to execute JavaScript payloads or trigger XSS.

For an application you are authorized to test, a harmless marker is enough.

Using an affected jQuery build:

delete Object.prototype.polluted;

const attackerControlledObject = JSON.parse(
    '{"__proto__":{"polluted":"yes"}}'
);

$.extend(
    true,
    {},
    attackerControlledObject
);

console.log(({}).polluted);

A vulnerable implementation may produce:

yes

This proves that the merge modified an inherited property.

Clean up immediately afterward:

delete Object.prototype.polluted;

On a patched version, the expected result is:

undefined

This style of verification is much safer and more informative than immediately attempting an XSS payload.

It answers the first question:

Can attacker-controlled data reach a prototype pollution source?

C'est le cas pas yet answer the second question:

Can the polluted property reach a security-sensitive gadget?

Those are separate stages.

Prototype Pollution Is Not Automatically XSS

This distinction is frequently lost in vulnerability scanner output.

Imagine an attacker successfully causes:

Object.prototype.test = "hello";

That is undesirable.

But nothing necessarily executes.

For XSS to occur, application code must later take an attacker-controlled inherited property and use it in a dangerous browser operation.

Consider:

const settings = {};

element.innerHTML = settings.template;

Normally:

settings.template

might be undefined.

But suppose prototype pollution previously caused:

Object.prototype.template =
    "<attacker-controlled HTML>";

Now:

settings.template

resolves through the prototype chain.

The application may effectively behave as though the malicious value had been explicitly supplied.

This second piece is usually described as a prototype pollution gadget.

PortSwigger’s prototype pollution methodology separates exploitation into roughly the same model:

Attacker input
      ↓
Prototype pollution source
      ↓
Object.prototype modified
      ↓
Application reads inherited property
      ↓
Security-sensitive gadget
      ↓
DOM XSS or another impact

(PortSwigger)

Therefore, finding CVE-2019-11358 in an asset should start an investigation rather than end one.

A Typical jQuery Prototype Pollution Attack Chain

A realistic attack path can be represented as:

User-controlled data
        |
        v
JSON / query parameters / application state
        |
        v
Object construction
        |
        v
$.extend(true, defaults, input)
        |
        v
__proto__ processed recursively
        |
        v
Object.prototype polluted
        |
        v
Unrelated object inherits property
        |
        v
Application gadget consumes property
        |
        v
HTML / URL / script / logic sink
        |
        v
Security impact

The most important insight is that the source and sink can be far apart.

The unsafe $.extend() operation could occur while initializing a configuration object.

The dangerous sink might execute hundreds of lines later.

It may even exist inside another third-party library.

This makes prototype pollution substantially harder to reason about than conventional reflected XSS.

How Prototype Pollution Can Become DOM XSS

Consider an application that constructs defaults:

const defaultConfig = {
    showHeader: true
};

Then merges user-provided configuration:

const config = $.extend(
    true,
    defaultConfig,
    userOptions
);

Elsewhere, the application contains:

const widget = {};

if (widget.banner) {
    document.querySelector("#banner").innerHTML =
        widget.banner;
}

The developers may think widget.banner can only exist if their own application explicitly sets it.

But JavaScript inheritance changes that assumption.

If:

Object.prototype.banner

has been polluted, then:

widget.banner

can resolve to the polluted value.

The vulnerability chain becomes:

Prototype pollution
        +
Inherited property lookup
        +
Dangerous DOM sink
        =
Potential DOM XSS

This is why GitHub’s reviewed advisory describes CVE-2019-11358 in an XSS context while also identifying prototype pollution as the underlying weakness. (GitHub)

Drupal likewise treated the issue as a possible cross-site scripting problem in affected integrations. Its advisory specifically noted that exploitation depended on how Drupal modules used jQuery, and the Drupal team backported the jQuery.extend() protection as a precaution. (Drupal.org)

What Conditions Are Required for Exploitation?

Real-world CVE-2019-11358 exploitation generally requires multiple conditions.

1. A vulnerable jQuery implementation must exist

Upstream jQuery releases before 3.4.0 contain the affected behavior.

You can inspect the runtime version using:

jQuery.fn.jquery

or:

$.fn.jquery

Par exemple :

3.3.1

deserves further investigation.

But version detection alone is not conclusive because vendors sometimes backported the security fix without changing the visible jQuery version.

Drupal is an important example.

Its security release backported the relevant protection into older bundled jQuery versions rather than simply replacing every deployment with jQuery 3.4.0. (Drupal.org)

Therefore:

jQuery version < 3.4.0

means:

potential exposure

not necessarily:

confirmed exploitable CVE

2. Deep $.extend() behavior must be reachable

The vulnerable path centers on a deep recursive merge such as:

$.extend(true, {}, source);

A website may load jQuery 3.3.1 but never call the vulnerable API with attacker-controllable data.

That dramatically changes practical risk.

3. The attacker must influence the object being merged

Common sources could include application-generated JavaScript objects derived from:

URL parameters
JSON responses
postMessage data
user preferences
API responses
stored application configuration
fragment identifiers
plugin configuration
server-rendered JSON

Exactly how data becomes an object is application-specific.

4. __proto__ must survive preprocessing

An application might already sanitize suspicious property names.

The framework or parser producing the object may also behave differently.

The question is not simply whether an attacker can send the literal characters:

__proto__

The question is whether a dangerous own property eventually reaches the vulnerable merge operation.

5. A useful gadget must exist for significant exploitation

Successful prototype pollution demonstrates a security primitive.

Turning that primitive into XSS or another severe outcome usually requires code that trusts inherited properties.

Examples of potentially sensitive property usage include:

element.innerHTML = options.html;

or:

script.src = options.scriptUrl;

or flawed access logic such as:

if (user.isAdmin) {
    enableAdministrativeFeatures();
}

These examples illustrate why exploitation is application-specific.

Pourquoi Object.prototype Pollution Has Such a Large Blast Radius

Suppose the attacker changes:

Object.prototype.debug = true;

Now many ordinary objects can appear to contain:

debug

even when they do not.

Consider:

const a = {};
const b = {};
const c = {};

console.log(a.debug);
console.log(b.debug);
console.log(c.debug);

All can inherit the property.

This can break a surprisingly large class of assumptions made by JavaScript applications.

Developers often write:

if (config.someOption) {
    // special behavior
}

They may assume:

property exists
=
application explicitly configured it

That assumption is wrong if inherited properties are permitted.

Safer checks often distinguish between own and inherited properties:

Object.hasOwn(config, "someOption");

Prototype pollution is therefore not merely about manipulating one object.

It can modify the environment in which many objects are interpreted.

Why Deep Merge Functions Are Frequent Prototype Pollution Sources

Recursive merge utilities have repeatedly been associated with prototype pollution because they combine three dangerous characteristics:

Dynamic property names
+
Recursive traversal
+
Assignment into destination objects

A simplified deep merge might contain:

for (const key in source) {

    if (typeof source[key] === "object") {

        target[key] = merge(
            target[key] || {},
            source[key]
        );

    } else {

        target[key] = source[key];

    }
}

This appears reasonable until:

key === "__proto__"

or related prototype-navigation patterns become possible.

The general security lesson extends far beyond jQuery.

Any generic object merger should consider dangerous property names, prototype semantics, own-property behavior, and whether recursive traversal can escape the intended data structure.

The jQuery 3.4.0 Patch

One reason CVE-2019-11358 is especially educational is that the upstream patch was remarkably small.

The jQuery commit changed the relevant condition from roughly:

if (target === copy) {
    continue;
}

à :

if (
    name === "__proto__" ||
    target === copy
) {
    continue;
}

The commit explicitly labels the new check:

// Prevent Object.prototype pollution

The official commit modified two files and added a regression test ensuring a malicious JSON object’s __proto__ member did not introduce a property into ordinary objects. (GitHub)

Conceptually, the fix is:

When recursively iterating properties:

if property name == "__proto__":
    ignore it

This prevents jQuery.extend() from traversing through the dangerous property.

The jQuery project’s current implementation still contains an explicit check for:

name === "__proto__"

before recursively merging properties. (GitHub)

The Official Regression Test

The patch also introduced a regression test.

Conceptually:

jQuery.extend(
    true,
    {},
    JSON.parse(
        '{"__proto__":{"devMode":true}}'
    )
);

assert(
    !("devMode" in {})
);

This test is especially useful because it captures the vulnerability’s actual security invariant.

Before the fix:

"devMode" in {}
→ true

After the fix:

"devMode" in {}
→ false

The official commit confirms this regression test was added together with the __proto__ check. (GitHub)

Affected jQuery Versions

The safest upstream version statement is straightforward:

jQuery before 3.4.0 is affected by CVE-2019-11358.

jQuery 3.4.0 contains the upstream fix. (NVD)

GitHub’s reviewed advisory records affected npm and NuGet jQuery versions before 3.4.0 and identifies 3.4.0 as patched. (GitHub)

However, dependency ecosystems complicate the picture.

The affected code appeared through packages and frameworks including:

jQuery npm distributions
jQuery NuGet packages
jquery-rails
WebJars
Drupal
Backdrop CMS
other products bundling jQuery

This means security engineers should not search only package.json.

The vulnerable code could be buried inside:

vendor.js
bundle.js
legacy.js
admin.js
static assets
CMS distributions
server-side package wrappers
application plugins
vendor appliances

Drupal and CVE-2019-11358

Drupal provides a particularly useful example of why version-only vulnerability detection can produce misleading conclusions.

Drupal’s April 17, 2019 security advisory explained that its existing jQuery releases could be affected by the prototype pollution behavior. Rather than universally upgrading the embedded jQuery package itself, Drupal backported the relevant jQuery.extend() protection.

Drupal published patched releases including:

Drupal 7.66
Drupal 8.5.15
Drupal 8.6.15

for the affected branches at that time. (Drupal.org)

This creates an important security-scanning lesson.

Suppose a scanner observes:

jQuery 1.4.4

inside an old Drupal deployment.

A simplistic scanner might immediately report:

CVE-2019-11358 confirmed

But the relevant $.extend() implementation may already contain Drupal’s backported security patch.

The correct process is:

Version fingerprint
      ↓
Distribution identification
      ↓
Backport check
      ↓
Behavior verification
      ↓
Reachability analysis
      ↓
Exploitability analysis

Not:

old version
      ↓
critical vulnerability

This distinction becomes particularly important in enterprise appliances, Linux distributions, and long-lived frameworks that routinely backport patches.

How to Detect CVE-2019-11358 in a Web Application

Testing should distinguish several progressively stronger levels of evidence.

Level 1: Identify jQuery

Open the browser console:

typeof jQuery

If loaded:

jQuery.fn.jquery

may reveal the version.

Alternatively:

$.fn.jquery

Common JavaScript bundles can also be searched for jQuery version banners.

This establishes the software fingerprint.

It does not establish exploitation.

Level 2: Inspect dependency manifests

For npm projects:

npm ls jquery

You can also inspect:

package.json
package-lock.json
yarn.lock
pnpm-lock.yaml

A source tree search may help:

grep -R "\"jquery\"" .

For Rails applications, inspect dependencies relating to:

jquery-rails

GitHub’s advisory identifies jquery-rails releases before 4.3.4 among affected package distributions. (GitHub)

Level 3: Search for vulnerable API usage

Look for:

$.extend(true,

and:

jQuery.extend(true,

A static search could be:

grep -R "\.extend(true" .

Minified bundles obviously make this harder, but source maps or beautification can help.

Then inspect where the source object originates.

Par exemple :

const options = $.extend(
    true,
    {},
    defaults,
    userConfig
);

Ask:

Where does userConfig come from?

If it comes entirely from hard-coded application code, remote exploitation may not be possible.

If it derives from attacker-controlled data, the path becomes more interesting.

Level 4: Perform a harmless behavioral test

In an authorized testing environment:

delete Object.prototype.cve201911358;

$.extend(
    true,
    {},
    JSON.parse(
        '{"__proto__":{"cve201911358":"marker"}}'
    )
);

console.log(
    ({}).cve201911358
);

delete Object.prototype.cve201911358;

A vulnerable implementation may return:

marker

A patched implementation should return:

undefined

This establishes whether the vulnerable behavior exists.

Level 5: Determine attacker reachability

Next answer:

Can an external attacker control the object that reaches the vulnerable merge?

Trace:

HTTP input
        ↓
Application parser
        ↓
JavaScript object
        ↓
$.extend(true, ...)

The mere existence of vulnerable behavior in a library does not establish external reachability.

Level 6: Search for gadgets

Finally inspect whether polluted values can influence sensitive operations.

Typical areas worth reviewing include:

HTML rendering
DOM insertion
script construction
URL handling
iframe creation
event configuration
authorization flags
security configuration
callback selection
template processing

This final step determines the practical impact.

Vulnerable, Reachable, Exploitable

Security teams can greatly improve CVE-2019-11358 triage by separating three states.

StateSignification
VulnérableThe vulnerable jQuery behavior exists
ReachableAttacker-controlled input can reach that behavior
ExploitableThe resulting pollution can influence a useful gadget or security decision

This is an important distinction.

A page loading jQuery 3.3.1 might technically contain vulnerable code.

But if:

no attacker-controlled object reaches $.extend(true)

then the vulnerability may not be remotely reachable.

Alternatively, attacker-controlled input may successfully pollute:

Object.prototype.foo

but there may be no useful gadget.

That gives:

reachable prototype pollution

without necessarily producing:

DOM XSS

The strongest report therefore explains the entire chain rather than reporting only a library fingerprint.

Why Automated Scanners Often Overstate CVE-2019-11358

Traditional vulnerability scanners frequently operate through software composition analysis.

They see:

jquery-3.3.1.min.js

and map it to:

CVE-2019-11358

This is useful for inventory.

It is much less useful for answering exploitability.

A high-quality assessment needs several additional questions:

Is this really upstream jQuery or a patched vendor copy?

Does the vulnerable $.extend implementation remain present?

Does the application perform deep merges?

Can attacker-controlled objects reach those merges?

Does dangerous prototype data survive parsing?

Can Object.prototype actually be polluted?

Does the application expose a useful gadget?

What concrete security consequence follows?

This is why security validation increasingly needs to move beyond static CVE matching.

A vulnerability scanner tells you:

potentially vulnerable component

A penetration test should tell you:

whether the vulnerability is reachable and what it actually does

Code Review Patterns to Search For

During source-assisted analysis, search for recursive merges involving potentially untrusted objects.

Par exemple :

$.extend(
    true,
    {},
    defaultSettings,
    requestData
);

Then determine whether:

requestData

originates from something like:

JSON.parse(...)

or application-controlled request data.

Another interesting pattern is:

$.extend(
    true,
    applicationState,
    incomingData
);

This is potentially more dangerous because the merge modifies an existing application object.

Security review should also look for subsequent code that performs property checks such as:

if (options.admin) {
    ...
}

rather than:

if (
    Object.hasOwn(
        options,
        "admin"
    )
) {
    ...
}

The former allows inherited prototype properties to participate in the decision.

Remediation for CVE-2019-11358

The direct upstream fix is simple:

Upgrade jQuery to version 3.4.0 or later.

That is the minimum upstream version containing the CVE-2019-11358 fix. (jQuery Blog)

In 2026, however, organizations should distinguish between:

minimum version fixing this CVE

and:

appropriate modern jQuery version

The current official jQuery download page lists jQuery 4.0.0 as the latest release. jQuery 4.0.0 was released in January 2026 and contains substantial modernization and some breaking changes. (jQuery)

Therefore, the upgrade decision may look like:

Legacy application requiring minimal change
        ↓
Use a supported 3.x release containing the fix

Application undergoing modernization
        ↓
Evaluate migration to jQuery 4.x

Do not interpret:

3.4.0 fixes CVE-2019-11358

as:

3.4.0 is automatically the ideal version to deploy in 2026

Those are different questions.

From jQuery Prototype Pollution to DOM XSS

What If You Cannot Upgrade jQuery?

Some legacy systems cannot immediately replace jQuery due to plugin compatibility, old browser support, vendor constraints, or tightly coupled frontend code.

In those environments, the best approach is to use an official or vendor-supported backport whenever possible.

The fundamental protection introduced by jQuery 3.4.0 is to reject the dangerous property during recursive merging:

if (
    name === "__proto__" ||
    target === copy
) {
    continue;
}

(GitHub)

However, manually editing third-party libraries creates maintenance risk.

Prefer:

official upgrade
>
vendor security update
>
official backport
>
carefully maintained local mitigation

rather than maintaining an undocumented custom patch indefinitely.

Filter Dangerous Object Keys

Applications that merge untrusted object structures should consider rejecting prototype-navigation keys before merging.

Typical defensive deny-list candidates include:

__proto__
prototype
constructor

A simplified recursive validator might conceptually reject them:

const forbiddenKeys = new Set([
    "__proto__",
    "prototype",
    "constructor"
]);

function validateObject(value) {

    if (
        value === null ||
        typeof value !== "object"
    ) {
        return;
    }

    for (const key of Object.keys(value)) {

        if (forbiddenKeys.has(key)) {
            throw new Error(
                "Unsafe object key"
            );
        }

        validateObject(value[key]);
    }
}

This should be considered defense in depth rather than a substitute for patching the vulnerable library.

Prefer Schema Validation

One of the strongest defenses against prototype pollution is to stop accepting arbitrary object structures in the first place.

Suppose an endpoint expects:

{
  "theme": "dark",
  "pageSize": 20
}

There is little reason to accept:

{
  "__proto__": {},
  "constructor": {},
  "random": {},
  "arbitraryNestedKey": {}
}

A strict schema can allow:

theme
pageSize

while rejecting everything else.

This reduces the attack surface considerably.

The jQuery team itself emphasized that its library-level fix was not a replacement for sanitizing untrusted input. (jQuery Blog)

Consider Objects Without Prototypes

For dictionary-style data structures where inheritance is unnecessary, JavaScript allows:

const dictionary =
    Object.create(null);

This object does not inherit from:

Object.prototype

That removes an entire class of accidental inherited-property behavior.

Instead of:

const cache = {};

some applications can safely use:

const cache =
    Object.create(null);

Whether that change is appropriate depends on the application because methods normally inherited from Object.prototype will no longer exist.

Prefer Own-Property Checks for Security Decisions

Consider:

if (user.isAdmin) {
    openAdminPanel();
}

This accesses inherited properties.

For security-sensitive checks, code should generally distinguish explicitly owned state:

if (
    Object.hasOwn(user, "isAdmin") &&
    user.isAdmin === true
) {
    openAdminPanel();
}

This alone does not solve prototype pollution globally.

But it prevents some polluted inherited properties from becoming useful authorization gadgets.

Peut Object.freeze(Object.prototype) Prevent Prototype Pollution?

You may encounter recommendations such as:

Object.freeze(Object.prototype);

Freezing the prototype can stop subsequent modification of that object.

However, using this as a universal mitigation can break applications or third-party libraries that legitimately modify prototypes.

It also does not address every variation of prototype-based attacks.

Treat prototype freezing as an environment-specific hardening technique rather than a universal replacement for:

patching
schema validation
safe merging
secure object design

Content Security Policy Helps, but Does Not Fix the Root Cause

A strong Content Security Policy can reduce the impact of some DOM XSS chains.

For example, modern CSP deployments can restrict script execution and dangerous script sources.

But CSP does not prevent:

Object.prototype pollution

itself.

Nor does it necessarily prevent logic-manipulation consequences unrelated to script execution.

The correct hierarchy is:

Fix prototype pollution
        +
Remove dangerous gadgets
        +
Deploy CSP

rather than:

Keep prototype pollution
        +
Hope CSP catches everything

Defense in depth works best when controls overlap rather than replace one another.

CVE-2019-11358 Versus Ordinary DOM XSS

Traditional DOM XSS often looks like:

URL parameter
     ↓
JavaScript source
     ↓
innerHTML
     ↓
script execution

Prototype-pollution-based DOM XSS is more indirect:

URL / JSON / attacker data
           ↓
Object parser
           ↓
Deep merge
           ↓
Object.prototype pollution
           ↓
Unrelated object
           ↓
Inherited property
           ↓
Gadget
           ↓
DOM sink

That additional indirection makes the vulnerability easy to miss during conventional source-to-sink analysis.

The tainted value does not always appear to flow directly into the sink.

Instead, it travels through JavaScript’s inheritance model.

CVE-2019-11358 Versus CVE-2020-11022 and CVE-2020-11023

These jQuery vulnerabilities are frequently grouped together by scanners but they represent different problems.

CVE-2019-11358 concerns:

Object.prototype pollution through $.extend()

CVE-2020-11022 and CVE-2020-11023 concern dangerous behavior involving HTML passed to jQuery DOM manipulation functionality.

The distinction matters during remediation and validation.

For CVE-2019-11358, think:

objects
deep merge
__proto__
prototype chain
gadgets

For the later jQuery HTML-processing vulnerabilities, think:

HTML
DOM parsing
DOM manipulation
script execution

A site can therefore require investigation for multiple jQuery CVEs even though they share the same library.

Why CVE-2019-11358 Still Matters in 2026

A vulnerability disclosed in 2019 might appear irrelevant seven years later.

That assumption is dangerous for frontend dependencies.

Unlike backend services that may be regularly rebuilt, JavaScript libraries often remain frozen inside applications for extremely long periods.

An organization might modernize its public website while retaining:

legacy administration portals
internal dashboards
network appliance interfaces
embedded management consoles
old CMS installations
archived customer portals
vendor applications
industrial interfaces

The frontend may continue shipping exactly the same JavaScript bundle for a decade.

jQuery’s continued relevance is also obvious from the project’s own evolution: the project released jQuery 4.0.0 in January 2026, twenty years after jQuery’s original introduction. (jQuery Blog)

Legacy jQuery therefore remains a realistic attack-surface concern.

How Security Engineers Should Validate CVE-2019-11358

A useful validation methodology is:

Phase 1
Asset discovery

Phase 2
jQuery fingerprinting

Phase 3
Patch/backport verification

Phase 4
$.extend(true) discovery

Phase 5
Untrusted data-flow analysis

Phase 6
Harmless pollution probe

Phase 7
Gadget discovery

Phase 8
Impact confirmation

Phase 9
Evidence capture

Phase 10
Remediation verification

This workflow is significantly more accurate than simply matching:

jquery-x.y.z.js

against an SCA database.

Phase 1: Asset Discovery

Identify JavaScript assets:

first-party bundles
vendor bundles
CDN libraries
CMS modules
plugin assets
embedded application resources

Do not assume the primary public page contains every dependency.

Administrative and legacy routes frequently load different JavaScript stacks.

Phase 2: Version Detection

Look for:

jQuery.fn.jquery

package manifests, CDN URLs, source banners, source maps, and bundled dependency metadata.

Phase 3: Determine Whether the Fix Was Backported

Search the relevant extend() implementation.

A strong indicator of the upstream mitigation is:

name === "__proto__"

inside the merge loop.

The official patch confirms that this exact comparison was introduced for CVE-2019-11358. (GitHub)

Phase 4: Locate Deep Merges

Search application code for:

$.extend(true,

and:

jQuery.extend(true,

Then trace each source object.

Phase 5: Trace Untrusted Input

Determine whether attacker data can influence the merged structure.

This is where automated scanners frequently stop too early.

Phase 6: Confirm Pollution Safely

Use a harmless marker such as:

cve201911358_test

rather than executable content.

Confirm whether:

({}).cve201911358_test

changes.

Phase 7: Find Gadgets

Search for properties that are read from ordinary objects and eventually reach security-sensitive operations.

Phase 8: Confirm Real Impact

Only once a safe, controlled path exists should the assessment classify the practical consequence.

The final finding might be:

vulnerable dependency only

or:

reachable prototype pollution

or:

prototype pollution leading to DOM XSS

These are materially different findings.

What Evidence Should a Pentest Report Include?

A useful CVE-2019-11358 finding should include more than a screenshot of:

jQuery 3.3.1

Good evidence includes:

Observed jQuery version
Exact affected JavaScript asset
Relevant $.extend(true) call
Attacker-controlled source
Prototype pollution marker
Before/after Object.prototype state
Relevant gadget
Concrete security impact
Patch validation

Par exemple :

Source:
user-controlled JSON object

Merge:
$.extend(true, {}, defaults, input)

Polluted property:
Object.prototype.exampleMarker

Observed consequence:
new empty objects inherited exampleMarker

Gadget:
application-specific configuration property

Impact:
confirmed DOM manipulation or logic change

This produces a reproducible finding rather than a speculative CVE match.

Remediation Verification

After upgrading or applying a trusted backport, rerun the harmless test.

Before remediation:

$.extend(
    true,
    {},
    JSON.parse(
        '{"__proto__":{"marker":"polluted"}}'
    )
);

console.log(({}).marker);

Possible result:

polluted

After remediation:

undefined

You should also confirm that legitimate functionality still works.

Parce que $.extend(true, ...) is frequently used for nested configuration merging, regression testing should include the application areas relying on those configurations.

Security Lessons Beyond jQuery

CVE-2019-11358 teaches several principles that remain important for modern JavaScript development.

Data keys are not always just data

Properties such as:

__proto__
constructor
prototype

can interact with language internals.

Generic object-processing code must understand that distinction.

Recursive merge code is security-sensitive

A helper that looks like harmless utility code can become a security boundary when it processes untrusted structures.

Inherited properties can undermine application assumptions

Code like:

if (settings.enabled)

does not necessarily mean:

settings owns an enabled property

It can mean:

enabled exists somewhere in the prototype chain

That difference matters.

Dependency CVEs require reachability analysis

Finding an old package is useful.

Proving an attack path is better.

Security impact often comes from composition

The source alone may be low impact.

The gadget alone may be harmless.

Together:

source + gadget

can produce a critical attack chain.

Frequently Asked Questions

What is CVE-2019-11358?

CVE-2019-11358 is a prototype pollution vulnerability affecting jQuery versions before 3.4.0. A recursively merged object containing an enumerable __proto__ property can modify Object.prototype. (NVD)

What function causes CVE-2019-11358?

The vulnerable behavior is associated with deep merges using:

jQuery.extend(true, ...)

or:

$.extend(true, ...)

The first Boolean argument enables recursive merging.

Which jQuery version fixes CVE-2019-11358?

The upstream vulnerability was fixed in:

jQuery 3.4.0

The fix was announced in the official jQuery 3.4.0 release notes. (jQuery Blog)

Is jQuery 3.3.1 vulnerable?

The upstream jQuery 3.3.1 implementation predates the 3.4.0 fix and falls within the affected upstream version range.

However, vendor distributions may backport security patches, so version detection should be followed by implementation or behavior verification.

Does CVE-2019-11358 automatically cause XSS?

No.

Prototype pollution provides a mechanism to modify inherited JavaScript properties.

A second application-specific gadget is generally necessary for pollution to become DOM XSS or another concrete security impact. PortSwigger explicitly distinguishes prototype-pollution sources from the gadgets needed to turn them into exploitable vulnerabilities. (PortSwigger)

Can CVE-2019-11358 cause remote code execution?

You should not describe RCE as an inherent impact of CVE-2019-11358.

The vulnerability creates a prototype pollution primitive. What happens afterward depends on the environment and available gadgets.

For typical browser deployments, DOM XSS is a more relevant potential consequence when a suitable gadget exists.

Is CVE-2019-11358 server-side or client-side?

jQuery is primarily a browser-side library, so client-side exploitation is the most natural context.

Prototype pollution as a vulnerability class can also occur in server-side JavaScript environments, but that broader category should not be confused with the specific deployment conditions of this jQuery CVE.

How can I quickly check whether a page loads jQuery?

In the browser console:

typeof jQuery

followed by:

jQuery.fn.jquery

can often identify the runtime version.

Is detecting jQuery before 3.4.0 enough to confirm the vulnerability?

No.

It is strong evidence that the upstream library version falls into the affected range, but vendors such as Drupal have historically backported security patches into older versions. (Drupal.org)

Behavioral verification is stronger evidence.

What is the safest proof of concept?

Use a harmless marker:

delete Object.prototype.testMarker;

$.extend(
    true,
    {},
    JSON.parse(
        '{"__proto__":{"testMarker":"yes"}}'
    )
);

console.log(({}).testMarker);

delete Object.prototype.testMarker;

Do this only in an environment you are authorized to test.

There is no need to execute an XSS payload simply to prove the underlying jQuery Prototype Pollution vulnerability.

Final Assessment

CVE-2019-11358 demonstrates why jQuery Prototype Pollution is more subtle than a traditional vulnerable-library finding.

At the code level, the problem is remarkably small.

A recursive jQuery.extend(true, ...) operation processed:

__proto__

like an ordinary property.

That behavior allowed attacker-controlled properties to reach:

Object.prototype

The jQuery 3.4.0 patch added an explicit check to prevent exactly this behavior. (GitHub)

But the true security story extends beyond that one line of code.

A robust assessment should model CVE-2019-11358 as:

Untrusted structured input
        ↓
Vulnerable jQuery deep merge
        ↓
Object.prototype pollution
        ↓
Inherited attacker-controlled property
        ↓
Application-specific gadget
        ↓
Concrete security impact

This explains why one deployment containing an old jQuery version may have little practical exposure while another can turn the same vulnerable primitive into DOM XSS.

For defenders, the immediate remediation remains straightforward: eliminate vulnerable upstream jQuery versions or apply a trusted vendor backport, validate untrusted structured input, avoid unsafe recursive object merging, and ensure security-sensitive logic does not blindly trust inherited properties.

For penetration testers and security engineers, however, the more important lesson is methodological:

do not stop at dependency detection.

Find the source. Prove the pollution. Trace the inherited property. Identify the gadget. Demonstrate the actual consequence.

That is the difference between reporting an old JavaScript library and understanding the real attack path behind CVE-2019-11358 jQuery Prototype Pollution.

The primary technical references for this vulnerability are the official jQuery 3.4.0 security release, le official jQuery patch commit, le NVD CVE-2019-11358 recordet le Drupal SA-CORE-2019-006 advisory. As of September 2026, jQuery’s official download page lists jQuery 4.0.0 as the current release, although upgrading from legacy applications should account for the breaking changes introduced in the 4.x generation. (jQuery)

Partager l'article :
Articles connexes
fr_FRFrench