Skip to content

Commit b2f9e88

Browse files
author
Vitaly Puzrin
authored
Merge pull request #480 from nodeca/toString
Fix possible code execution in (already unsafe) load()
2 parents 9d4ce5e + e18afbf commit b2f9e88

File tree

6 files changed

+58
-2
lines changed

6 files changed

+58
-2
lines changed

lib/js-yaml/loader.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i;
3030
var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;
3131

3232

33+
function _class(obj) { return Object.prototype.toString.call(obj); }
34+
3335
function is_EOL(c) {
3436
return (c === 0x0A/* LF */) || (c === 0x0D/* CR */);
3537
}
@@ -287,16 +289,29 @@ function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valu
287289

288290
// The output is a plain object here, so keys can only be strings.
289291
// We need to convert keyNode to a string, but doing so can hang the process
290-
// (deeply nested arrays that explode exponentially using aliases) or execute
291-
// code via toString.
292+
// (deeply nested arrays that explode exponentially using aliases).
292293
if (Array.isArray(keyNode)) {
294+
keyNode = Array.prototype.slice.call(keyNode);
295+
293296
for (index = 0, quantity = keyNode.length; index < quantity; index += 1) {
294297
if (Array.isArray(keyNode[index])) {
295298
throwError(state, 'nested arrays are not supported inside keys');
296299
}
300+
301+
if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') {
302+
keyNode[index] = '[object Object]';
303+
}
297304
}
298305
}
299306

307+
// Avoid code execution in load() via toString property
308+
// (still use its own toString for arrays, timestamps,
309+
// and whatever user schema extensions happen to have @@toStringTag)
310+
if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') {
311+
keyNode = '[object Object]';
312+
}
313+
314+
300315
keyNode = String(keyNode);
301316

302317
if (_result === null) {

test/issues/0480-date.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ !<tag:yaml.org,2002:timestamp> '2019-04-05T12:00:43.467Z': 123 }

test/issues/0480-fn-array.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
? [
2+
123,
3+
{ toString: !<tag:yaml.org,2002:js/function> 'function (){throw new Error("code execution")}' }
4+
] : key

test/issues/0480-fn.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ toString: !<tag:yaml.org,2002:js/function> 'function (){throw new Error("code execution")}' } : key

test/issues/0480-fn2.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ __proto__: { toString: !<tag:yaml.org,2002:js/function> 'function(){throw new Error("code execution")}' } } : key

test/issues/0480.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
4+
var assert = require('assert');
5+
var yaml = require('../../');
6+
var readFileSync = require('fs').readFileSync;
7+
8+
9+
test('Should not execute code when object with toString property is used as a key', function () {
10+
var data = yaml.load(readFileSync(require('path').join(__dirname, '/0480-fn.yml'), 'utf8'));
11+
12+
assert.deepEqual(data, { '[object Object]': 'key' });
13+
});
14+
15+
test('Should not execute code when object with __proto__ property is used as a key', function () {
16+
var data = yaml.load(readFileSync(require('path').join(__dirname, '/0480-fn2.yml'), 'utf8'));
17+
18+
assert.deepEqual(data, { '[object Object]': 'key' });
19+
});
20+
21+
test('Should not execute code when object inside array is used as a key', function () {
22+
var data = yaml.load(readFileSync(require('path').join(__dirname, '/0480-fn-array.yml'), 'utf8'));
23+
24+
assert.deepEqual(data, { '123,[object Object]': 'key' });
25+
});
26+
27+
// this test does not guarantee in any way proper handling of date objects,
28+
// it just keeps old behavior whenever possible
29+
test('Should leave non-plain objects as is', function () {
30+
var data = yaml.load(readFileSync(require('path').join(__dirname, '/0480-date.yml'), 'utf8'));
31+
32+
assert.deepEqual(Object.keys(data).length, 1);
33+
assert(/2019/.test(Object.keys(data)[0]));
34+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy