fix(encoding/yaml): avoid prototype pollution in Node.js and Browser (#3173)

This commit is contained in:
ayame113 2023-02-10 19:12:48 +09:00 committed by GitHub
parent 64d2544048
commit dba98165cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 8 deletions

View File

@ -246,7 +246,7 @@ const directiveHandlers: DirectiveHandlers = {
}
if (typeof state.tagMap === "undefined") {
state.tagMap = {};
state.tagMap = Object.create(null) as common.ArrayObject;
}
state.tagMap[handle] = prefix;
},
@ -300,7 +300,12 @@ function mergeMappings(
for (let i = 0, len = keys.length; i < len; i++) {
const key = keys[i];
if (!hasOwn(destination, key)) {
destination[key] = (source as ArrayObject)[key];
Object.defineProperty(destination, key, {
value: source[key],
writable: true,
enumerable: true,
configurable: true,
});
overridableKeys[key] = true;
}
}
@ -371,7 +376,12 @@ function storeMappingPair(
state.position = startPos || state.position;
return throwError(state, "duplicated mapping key");
}
result[keyNode] = valueNode;
Object.defineProperty(result, keyNode, {
value: valueNode,
writable: true,
enumerable: true,
configurable: true,
});
delete overridableKeys[keyNode];
}
@ -750,7 +760,7 @@ function readFlowCollection(state: LoaderState, nodeIndent: number): boolean {
isPair = (isExplicitPair = false);
let following = 0,
line = 0;
const overridableKeys: ArrayObject<boolean> = {};
const overridableKeys: ArrayObject<boolean> = Object.create(null);
while (ch !== 0) {
skipSeparationSpace(state, true, nodeIndent);
@ -1067,7 +1077,7 @@ function readBlockMapping(
const tag = state.tag,
anchor = state.anchor,
result = {},
overridableKeys = {};
overridableKeys = Object.create(null);
let following: number,
allowCompact = false,
line: number,
@ -1609,8 +1619,8 @@ function readDocument(state: LoaderState) {
state.version = null;
state.checkLineBreaks = state.legacy;
state.tagMap = {};
state.anchorMap = {};
state.tagMap = Object.create(null);
state.anchorMap = Object.create(null);
while ((ch = state.input.charCodeAt(state.position)) !== 0) {
skipSeparationSpace(state, true, -1);

View File

@ -4,7 +4,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { parse, parseAll } from "./parse.ts";
import { assertEquals, assertThrows } from "../../testing/asserts.ts";
import { assert, assertEquals, assertThrows } from "../../testing/asserts.ts";
import { DEFAULT_SCHEMA, EXTENDED_SCHEMA } from "./schema/mod.ts";
import { YAMLError } from "./error.ts";
import { Type } from "./type.ts";
@ -172,3 +172,47 @@ regexp: !!js/regexp bar
assertEquals(callback.calls(), 2);
},
});
Deno.test({
name: "parse __proto__",
async fn() {
// Tests if the value is set using `Object.defineProperty(target, key, {value})`
// instead of `target[key] = value` when parsing the object.
// This makes a difference in behavior when __proto__ is set in Node.js and browsers.
// Using `Object.defineProperty` avoids prototype pollution in Node.js and browsers.
// reference: https://github.com/advisories/GHSA-9c47-m6qq-7p4h (CVE-2022-46175)
const yaml1 = `
__proto__:
isAdmin: true
`;
const yaml2 = `
anchor: &__proto__
__proto__: 1111
alias_test:
aaa: *__proto__
merge_test:
bbb: 2222
<<: *__proto__
`;
const testCode = `
Object.defineProperty(Object.prototype, "__proto__", {
set() {
throw new Error("Don't try to set the value directly to the key __proto__.")
}
});
import { parse } from "${import.meta.resolve("./parse.ts")}";
parse(\`${yaml1}\`);
parse(\`${yaml2}\`);
`;
const command = new Deno.Command(Deno.execPath(), {
stdout: "inherit",
stderr: "inherit",
args: ["eval", testCode],
});
const { success } = await command.output();
assert(success);
},
});