Skip to content

Commit 430f880

Browse files
author
Dervus Grim
committed
Close security issue in !!js/function constructor.
Prevent it from code execution on parsing. Thanks to @nealpoole for report.
1 parent 6853fa1 commit 430f880

File tree

6 files changed

+60
-5
lines changed

6 files changed

+60
-5
lines changed

HISTORY.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
2.0.5 / --
2+
------------------
3+
4+
* Close security issue in !!js/function constructor.
5+
Big thanks to @nealpoole for security audit.
6+
7+
18
2.0.4 / 2013-04-08
29
------------------
310

lib/js-yaml/type/js/function.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
11
'use strict';
22

33

4+
var esprima = require('esprima');
5+
6+
47
var NIL = require('../../common').NIL;
58
var Type = require('../../type');
69

710

811
function resolveJavascriptFunction(object /*, explicit*/) {
912
/*jslint evil:true*/
10-
var func;
1113

1214
try {
13-
func = new Function('return ' + object);
14-
return func();
15-
} catch (error) {
15+
var source = '(' + object + ')',
16+
ast = esprima.parse(source, { range: true }),
17+
params = [],
18+
body;
19+
20+
if ('Program' !== ast.type ||
21+
1 !== ast.body.length ||
22+
'ExpressionStatement' !== ast.body[0].type ||
23+
'FunctionExpression' !== ast.body[0].expression.type) {
24+
return NIL;
25+
}
26+
27+
ast.body[0].expression.params.forEach(function (param) {
28+
params.push(param.name);
29+
});
30+
31+
body = ast.body[0].expression.body.range;
32+
33+
// Esprima's ranges include the first '{' and the last '}' characters on
34+
// function expressions. So cut them out.
35+
return new Function(params, source.slice(body[0]+1, body[1]-1));
36+
} catch (err) {
1637
return NIL;
1738
}
1839
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"bin" : { "js-yaml": "bin/js-yaml.js" },
3333
"scripts" : { "test": "make test" },
3434

35-
"dependencies" : { "argparse": "~ 0.1.11" },
35+
"dependencies" : { "argparse": "~ 0.1.11",
36+
"esprima": "~ 1.0.2" },
3637
"devDependencies" : { "mocha": "*" },
3738
"engines" : { "node": ">= 0.6.0" }
3839
}

test/issues.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ describe('Issues.', function () {
1111
require('./issues/issue-46.js');
1212
require('./issues/issue-54.js');
1313
require('./issues/issue-64.js');
14+
require('./issues/issue-parse-function-security.js');
1415
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
tests:
2+
- !!js/function 'makeBadThing("BAD THING 1")'
3+
- !!js/function 'function () { makeBadThing("BAD THING 2") }.call(this)'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
/*global it */
3+
4+
5+
var assert = require('assert');
6+
7+
8+
var badThings = [];
9+
10+
11+
global.makeBadThing = function (thing) {
12+
badThings.push(thing);
13+
};
14+
15+
16+
it('Function constructor must not allow to execute any code while parsing.', function () {
17+
assert.throws(function () {
18+
require('./data/issue-parse-function-security.yml');
19+
});
20+
21+
assert.deepEqual(badThings, []);
22+
});

0 commit comments

Comments
 (0)