GHSA-82jv-9wjw-pqh6

Suggest an improvement
Source
https://github.com/advisories/GHSA-82jv-9wjw-pqh6
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/04/GHSA-82jv-9wjw-pqh6/GHSA-82jv-9wjw-pqh6.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-82jv-9wjw-pqh6
Published
2024-04-17T22:26:37Z
Modified
2024-04-17T22:26:37Z
Summary
Prototype pollution in emit function
Details

Summary

A prototype pollution in derby can crash the application, if the application author has atypical HTML templates that feed user input into an object key.

Attribute keys are almost always developer-controlled, not end-user-controlled, so this shouldn't be an issue in practice for most applications.

Details

emit(context: Context, target: T) {
  const node = traverseAndCreate(context.controller, this.segments);
    node[this.lastSegment] = target;
    this.addListeners(target, node, this.lastSegment);
}

The emit() function in src/templates/templates.ts is called without sanitizing the variable this.lastSegment. The variable this.lastSegment can be set to __proto__, and this will pollute the prototype of Javascipt Object (node['__proto__'] = target).

PoC

To reproduce this vulnerability, you can adjust the test case ignores DOM mutations in components\' create() in test/dom/ComponentHarness.mocha.js.

it('ignores DOM mutations in components\' create()', function() {
      function Box() {}
      Box.view = {
        is: 'box',
-        source: '<index:><div class="box" as="boxElement"></div>'
+        source: '<index:><div class="box" as="__proto__"></div>'
      };
      Box.prototype.create = function() {
        this.boxElement.className = 'box-changed-in-create';
      };
      var harness = runner.createHarness('<view is="box" />', Box);
      expect(harness).to.render('<div class="box"></div>');
});

When as attribute is controlled by attackers, the variable in this.lastSegment will exactly take value__proto__ and prototype pollution happens.

Patch

Add a check on this.lastSegment can prevent this attack.

emit(context: Context, target: T) {
  const node = traverseAndCreate(context.controller, this.segments);
+  if (this.lastSegment.includes('__proto__') || this.lastSegment.includes('prototype')) {
+    throw new Error('Unsafe code detected');
+  }
    node[this.lastSegment] = target;
    this.addListeners(target, node, this.lastSegment);
}
References

Affected packages

npm / derby

Package

Affected ranges

Type
SEMVER
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
2.3.2

Database specific

{
    "last_known_affected_version_range": "<= 2.3.1"
}

npm / derby

Package

Affected ranges

Type
SEMVER
Events
Introduced
3.0.0
Fixed
3.0.2

Database specific

{
    "last_known_affected_version_range": "<= 3.0.1"
}

npm / derby

Package

Affected ranges

Type
SEMVER
Events
Introduced
4.0.0-beta1
Fixed
4.0.0-beta.11

Database specific

{
    "last_known_affected_version_range": "<= 4.0.0-beta.10"
}