Skip to content

Commit

Permalink
json8-merge-patch: Prevent prototype pollution 2 (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
sonnyp authored Sep 13, 2020
1 parent d3d0ab1 commit 2e89026
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 19 deletions.
12 changes: 12 additions & 0 deletions packages/merge-patch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ person = mergePatch.apply(person, patch)

[](#json8-merge-patch)

### object creation

When needed, `apply` creates objects with `null` prototype, you can choose the prototype to use with `{proto: Object}` as a third argument.

[](#json8-merge-patch)

### prototype pollution

`apply` will throw with an error if [prototype pollution](https://github.com/HoLyVieR/prototype-pollution-nsec18) is attempted. You can allow for prototype pollution by passing `{pollute: true}` as a third argument.

[](#json8-merge-patch)

### patch

Alias for [apply](#apply) method.
Expand Down
18 changes: 13 additions & 5 deletions packages/merge-patch/lib/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@ const OBJECT = "object";
/**
* Apply a JSON merge patch onto a document
* https://tools.ietf.org/html/rfc7396
* @param {Object} doc - JSON object document
* @param {Object} patch - JSON object patch
* @return {Object} - JSON object document
* @param {Object} doc - JSON object document
* @param {Object} patch - JSON object patch
* @param {Object} [options] - options
* @param {Boolean} [options.pollute=false] - Allow prototype pollution - throw otherwise
* @param {Object} [options.proto=null] - Prototype to use for object creation
* @return {Object} - JSON object document
*/
module.exports = function apply(doc, patch) {
module.exports = function apply(doc, patch, options) {
if (typeof patch !== OBJECT || patch === null || Array.isArray(patch)) {
return patch;
}

options = options || Object.create(null);

if (typeof doc !== OBJECT || doc === null || Array.isArray(doc)) {
doc = Object.create(null);
doc = Object.create(options.proto || null);
}

const keys = Object.keys(patch);
for (const key of keys) {
if (options.pollute !== true && key === "__proto__") {
throw new Error("Prototype pollution attempt");
}
const v = patch[key];
if (v === null) {
delete doc[key];
Expand Down
26 changes: 12 additions & 14 deletions packages/merge-patch/test/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,21 @@ describe("apply", () => {
assert.deepEqual(doc, {});
});

// https://github.com/lodash/lodash/pull/4337
// https://github.com/sonnyp/JSON8/issues/113
// https://github.com/HoLyVieR/prototype-pollution-nsec18
it("prevents prototype pollution", () => {
let doc = {};
const patch = { __proto__: { foobar: true } };
doc = apply(doc, patch);
const patch = JSON.parse('{ "__proto__": { "isAdmin": true }}');

assert.deepEqual(doc, {});
});
assert.throws(
() => {
doc = apply(doc, patch);
},
Error,
"Prototype pollution attempt"
);

// https://github.com/lodash/lodash/pull/4336
it("prevents constructor pollution", () => {
let doc = {};

const patch = { constructor: { foo: "bar" } };
doc = apply(doc, patch);
assert.equal("foo" in Object, false);
assert.equal(Object.foo, undefined);
assert.deepEqual(doc, patch);
assert.equal(doc.isAdmin, undefined);
assert.equal("isAdmin" in doc, false);
});
});

1 comment on commit 2e89026

@abergmann
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CVE-2020-7770 was assigned to this commit.

Please # to comment.