Files
shaka-player/test/util/config_utils_unit.js
T
Sakura501 586ce0636e chore(Demo): demo assetBase64 prototype pollution to DOM XSS (#9906)
## Summary

This PR fixes a demo asset parsing and configuration merge vulnerability
where a
malicious `assetBase64` payload could abuse `__proto__`, `constructor`,
or
`prototype` keys to pollute object prototypes and later reach DOM XSS
gadgets in
Shaka Player Demo.

## Vulnerability details

The vulnerable path was:

1. `demo/main.js` reads attacker-controlled `assetBase64` from the URL
hash.
2. `demo/common/asset.js` copies `extraConfig` into a player config
object with
   `for..in`, which allows dangerous magic keys to be applied.
3. `lib/util/config_utils.js` merges config objects with another
`for..in`
   traversal and no explicit rejection of `__proto__`, `constructor`, or
   `prototype`.
4. The resulting prototype pollution can be turned into DOM XSS when
later demo
   UI rebuild paths consume inherited properties.

## Fix approach

This change hardens both the demo entry point and the shared merge
utility:

- `demo/common/asset.js`
  - filter dangerous keys when copying `extraConfig`
  - restrict `toJSON()` and `fromJSON()` to own properties only
- prevent dangerous keys from being serialized into or restored from
saved demo
    assets
- `lib/util/config_utils.js`
  - switch config merging from `for..in` to `Object.keys()`
  - explicitly reject `__proto__`, `constructor`, and `prototype`

## Regression coverage

Added tests that verify:

- dangerous `extraConfig` keys do not alter the generated player config
- inherited demo asset properties are not serialized into JSON
- dangerous keys are ignored when parsing saved assets back into demo
objects
- inherited magic keys are not traversed during config merges

## Verification

- `python3 build/test.py --quick --filter 'Demo|ConfigUtils' --browsers
ChromeHeadless`
- `python3 build/check.py`
2026-03-30 10:40:10 +02:00

51 lines
1.7 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
describe('ConfigUtils', () => {
it('rejects dangerous keys while merging generic objects', () => {
const destination = shaka.util.PlayerConfiguration.createDefault();
const updates = {
drm: {
advanced: JSON.parse(
'{"__proto__":{"testPolluted":"YES"},' +
'"com.widevine.alpha":{"videoRobustness":["SW_SECURE_DECODE"]}}'),
},
};
const valid = shaka.util.PlayerConfiguration.mergeConfigObjects(
destination, updates,
shaka.util.PlayerConfiguration.createDefault());
expect(valid).toBe(false);
expect(/** @type {!Object} */(destination.drm.advanced)['testPolluted'])
.toBe(undefined);
expect(
Object.getPrototypeOf(/** @type {!Object} */(destination.drm.advanced)))
.toBe(
Object.getPrototypeOf({}));
expect(destination.drm.advanced['com.widevine.alpha'].videoRobustness)
.toEqual(['SW_SECURE_DECODE']);
});
it('does not traverse inherited magic keys during config merges', () => {
const inheritedProto = Object.create(null);
Object.defineProperty(inheritedProto, '__proto__', {
enumerable: true,
value: {testPolluted: 'YES'},
});
const updates = Object.create(inheritedProto);
const destination = shaka.util.PlayerConfiguration.createDefault();
const valid = shaka.util.PlayerConfiguration.mergeConfigObjects(
destination, updates,
shaka.util.PlayerConfiguration.createDefault());
expect(valid).toBe(true);
expect(/** @type {!Object} */({})['testPolluted']).toBe(undefined);
});
});