Skip to content

Commit ee6efa3

Browse files
committed
Only allow accessing/assigning properties on plain objects. Only allow calling known methods.
1 parent 4201268 commit ee6efa3

File tree

22 files changed

+918
-134
lines changed

22 files changed

+918
-134
lines changed

HISTORY.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
# History
22

33

4+
## not yet released, version 3.11.5
5+
6+
- More security measures in the expression parser.
7+
WARNING: more strict behavior of the expression parser introduces
8+
a small chance that existing functionality breaks when using
9+
undocumented methods or features. Sorry, but it's necessary to stay
10+
secure. Measures:
11+
- Accessing and assigning properties is now only allowed on plain
12+
objects, not on classes, arrays, and functions anymore.
13+
- Accessing methods is restricted to a set of known, safe methods.
14+
15+
416
## 2017-04-03, version 3.11.4
517

618
- Fixed a security vulnerability in the expression parser. Thanks @xfix.

docs/expressions/security.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ When running a node.js server, it's good to be aware of the different
2424
types of security risks. The risk whe running inside a browser may be
2525
limited though it's good to be aware of [Cross side scripting (XSS)](https://www.wikiwand.com/en/Cross-site_scripting) vulnerabilities. A nice overview of
2626
security risks of a node.js servers is listed in an article [Node.js security checklist](https://blog.risingstack.com/node-js-security-checklist/) by Gergely Nemeth.
27-
Lastly, one could look into running server side code in a sandboxed
28-
[node.js VM](https://nodejs.org/api/vm.html).
2927

3028
### Found a security vulnerability? Please report in private!
3129

lib/constants.js

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,55 +12,67 @@ function factory (type, config, load, typed, math) {
1212
}
1313
});
1414

15-
math['true'] = true;
16-
math['false'] = false;
17-
math['null'] = null;
18-
math['uninitialized'] = require('./utils/array').UNINITIALIZED;
15+
setConstant(math, 'true', true);
16+
setConstant(math, 'false', false);
17+
setConstant(math, 'null', null);
18+
setConstant(math, 'uninitialized', require('./utils/array').UNINITIALIZED);
1919

2020
if (config.number === 'BigNumber') {
21-
math['Infinity'] = new type.BigNumber(Infinity);
22-
math['NaN'] = new type.BigNumber(NaN);
21+
setConstant(math, 'Infinity', new type.BigNumber(Infinity));
22+
setConstant(math, 'NaN', new type.BigNumber(NaN));
2323

24-
object.lazy(math, 'pi', function () {return bigConstants.pi(type.BigNumber)});
25-
object.lazy(math, 'tau', function () {return bigConstants.tau(type.BigNumber)});
26-
object.lazy(math, 'e', function () {return bigConstants.e(type.BigNumber)});
27-
object.lazy(math, 'phi', function () {return bigConstants.phi(type.BigNumber)}); // golden ratio, (1+sqrt(5))/2
24+
setLazyConstant(math, 'pi', function () {return bigConstants.pi(type.BigNumber)});
25+
setLazyConstant(math, 'tau', function () {return bigConstants.tau(type.BigNumber)});
26+
setLazyConstant(math, 'e', function () {return bigConstants.e(type.BigNumber)});
27+
setLazyConstant(math, 'phi', function () {return bigConstants.phi(type.BigNumber)}); // golden ratio, (1+sqrt(5))/2
2828

2929
// uppercase constants (for compatibility with built-in Math)
30-
object.lazy(math, 'E', function () {return math.e;});
31-
object.lazy(math, 'LN2', function () {return new type.BigNumber(2).ln();});
32-
object.lazy(math, 'LN10', function () {return new type.BigNumber(10).ln()});
33-
object.lazy(math, 'LOG2E', function () {return new type.BigNumber(1).div(new type.BigNumber(2).ln());});
34-
object.lazy(math, 'LOG10E', function () {return new type.BigNumber(1).div(new type.BigNumber(10).ln())});
35-
object.lazy(math, 'PI', function () {return math.pi});
36-
object.lazy(math, 'SQRT1_2', function () {return new type.BigNumber('0.5').sqrt()});
37-
object.lazy(math, 'SQRT2', function () {return new type.BigNumber(2).sqrt()});
30+
setLazyConstant(math, 'E', function () {return math.e;});
31+
setLazyConstant(math, 'LN2', function () {return new type.BigNumber(2).ln();});
32+
setLazyConstant(math, 'LN10', function () {return new type.BigNumber(10).ln()});
33+
setLazyConstant(math, 'LOG2E', function () {return new type.BigNumber(1).div(new type.BigNumber(2).ln());});
34+
setLazyConstant(math, 'LOG10E', function () {return new type.BigNumber(1).div(new type.BigNumber(10).ln())});
35+
setLazyConstant(math, 'PI', function () {return math.pi});
36+
setLazyConstant(math, 'SQRT1_2', function () {return new type.BigNumber('0.5').sqrt()});
37+
setLazyConstant(math, 'SQRT2', function () {return new type.BigNumber(2).sqrt()});
3838
}
3939
else {
40-
math['Infinity'] = Infinity;
41-
math['NaN'] = NaN;
40+
setConstant(math, 'Infinity', Infinity);
41+
setConstant(math, 'NaN', NaN);
4242

43-
math.pi = Math.PI;
44-
math.tau = Math.PI * 2;
45-
math.e = Math.E;
46-
math.phi = 1.61803398874989484820458683436563811772030917980576286213545; // golden ratio, (1+sqrt(5))/2
43+
setConstant(math, 'pi', Math.PI);
44+
setConstant(math, 'tau', Math.PI * 2);
45+
setConstant(math, 'e', Math.E);
46+
setConstant(math, 'phi', 1.61803398874989484820458683436563811772030917980576286213545); // golden ratio, (1+sqrt(5))/2
4747

4848
// uppercase constants (for compatibility with built-in Math)
49-
math.E = math.e;
50-
math.LN2 = Math.LN2;
51-
math.LN10 = Math.LN10;
52-
math.LOG2E = Math.LOG2E;
53-
math.LOG10E = Math.LOG10E;
54-
math.PI = math.pi;
55-
math.SQRT1_2 = Math.SQRT1_2;
56-
math.SQRT2 = Math.SQRT2;
49+
setConstant(math, 'E', math.e);
50+
setConstant(math, 'LN2', Math.LN2);
51+
setConstant(math, 'LN10', Math.LN10);
52+
setConstant(math, 'LOG2E', Math.LOG2E);
53+
setConstant(math, 'LOG10E', Math.LOG10E);
54+
setConstant(math, 'PI', math.pi);
55+
setConstant(math, 'SQRT1_2', Math.SQRT1_2);
56+
setConstant(math, 'SQRT2', Math.SQRT2);
5757
}
5858

5959
// complex i
60-
math.i = type.Complex.I;
60+
setConstant(math, 'i', type.Complex.I);
6161

6262
// meta information
63-
math.version = require('./version');
63+
setConstant(math, 'version', require('./version'));
64+
}
65+
66+
// create a constant in both math and mathWithTransform
67+
function setConstant(math, name, value) {
68+
math[name] = value;
69+
math.expression.mathWithTransform[name] = value;
70+
}
71+
72+
// create a lazy constant in both math and mathWithTransform
73+
function setLazyConstant (math, name, resolver) {
74+
object.lazy(math, name, resolver);
75+
object.lazy(math.expression.mathWithTransform, name, resolver);
6476
}
6577

6678
exports.factory = factory;

lib/core/core.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
var isFactory = require('./../utils/object').isFactory;
2-
var deepExtend = require('./../utils/object').deepExtend;
32
var typedFactory = require('./typed');
43
var emitter = require('./../utils/emitter');
54

@@ -50,9 +49,9 @@ exports.create = function create (options) {
5049
var math = emitter.mixin({});
5150
math.type = {};
5251
math.expression = {
53-
transform: Object.create(math)
52+
transform: {},
53+
mathWithTransform: {}
5454
};
55-
math.algebra = {};
5655

5756
// create a new typed instance
5857
math.typed = typedFactory.create(math.type);
@@ -122,6 +121,7 @@ exports.create = function create (options) {
122121
// load the import and config functions
123122
math['import'] = load(importFactory);
124123
math['config'] = load(configFactory);
124+
math.expression.mathWithTransform['config'] = math['config']
125125

126126
// apply options
127127
if (options) {

lib/core/function/import.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
var lazy = require('../../utils/object').lazy;
44
var isFactory = require('../../utils/object').isFactory;
55
var traverse = require('../../utils/object').traverse;
6-
var extend = require('../../utils/object').extend;
76
var ArgumentsError = require('../../error/ArgumentsError');
87

98
function factory (type, config, load, typed, math) {
@@ -56,7 +55,7 @@ function factory (type, config, load, typed, math) {
5655
*/
5756
function math_import(object, options) {
5857
var num = arguments.length;
59-
if (num != 1 && num != 2) {
58+
if (num !== 1 && num !== 2) {
6059
throw new ArgumentsError('import', num, 1, 2);
6160
}
6261

@@ -105,6 +104,7 @@ function factory (type, config, load, typed, math) {
105104
* @private
106105
*/
107106
function _import(name, value, options) {
107+
// TODO: refactor this function, it's to complicated and contains duplicate code
108108
if (options.wrap && typeof value === 'function') {
109109
// create a wrapper around the function
110110
value = _wrap(value);
@@ -145,10 +145,16 @@ function factory (type, config, load, typed, math) {
145145
function _importTransform (name, value) {
146146
if (value && typeof value.transform === 'function') {
147147
math.expression.transform[name] = value.transform;
148+
if (!unsafe[name]) {
149+
math.expression.mathWithTransform[name] = value.transform
150+
}
148151
}
149152
else {
150153
// remove existing transform
151154
delete math.expression.transform[name]
155+
if (!unsafe[name]) {
156+
math.expression.mathWithTransform[name] = value
157+
}
152158
}
153159
}
154160

@@ -185,6 +191,7 @@ function factory (type, config, load, typed, math) {
185191
function _importFactory(factory, options) {
186192
if (typeof factory.name === 'string') {
187193
var name = factory.name;
194+
var existingTransform = name in math.expression.transform
188195
var namespace = factory.path ? traverse(math, factory.path) : math;
189196
var existing = namespace.hasOwnProperty(name) ? namespace[name] : undefined;
190197

@@ -218,9 +225,20 @@ function factory (type, config, load, typed, math) {
218225

219226
if (factory.lazy !== false) {
220227
lazy(namespace, name, resolver);
228+
229+
if (!existingTransform) {
230+
if (!unsafe[name]) {
231+
lazy(math.expression.mathWithTransform, name, resolver);
232+
}
233+
}
221234
}
222235
else {
223236
namespace[name] = resolver();
237+
if (!existingTransform) {
238+
if (!unsafe[name]) {
239+
math.expression.mathWithTransform[name] = resolver();
240+
}
241+
}
224242
}
225243

226244
math.emit('import', name, resolver, factory.path);
@@ -239,7 +257,7 @@ function factory (type, config, load, typed, math) {
239257
* @private
240258
*/
241259
function isSupportedType(object) {
242-
return typeof object == 'function'
260+
return typeof object === 'function'
243261
|| typeof object === 'number'
244262
|| typeof object === 'string'
245263
|| typeof object === 'boolean'
@@ -261,6 +279,13 @@ function factory (type, config, load, typed, math) {
261279
return typeof fn === 'function' && typeof fn.signatures === 'object';
262280
}
263281

282+
// namespaces not available in the parser for safety reasons
283+
var unsafe = {
284+
'expression': true,
285+
'type': true,
286+
'error': true
287+
};
288+
264289
return math_import;
265290
}
266291

lib/expression/Parser.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var extend = require('../utils/object').extend;
44

55
function factory (type, config, load, typed, math) {
66
var _parse = load(require('./parse'));
7+
var customs = load(require('./node/utils/customs'));
78

89
/**
910
* @constructor Parser
@@ -111,7 +112,9 @@ function factory (type, config, load, typed, math) {
111112
*/
112113
Parser.prototype.get = function (name) {
113114
// TODO: validate arguments
114-
return this.scope[name];
115+
return name in this.scope
116+
? customs.getSafeProperty(this.scope, name)
117+
: undefined;
115118
};
116119

117120
/**
@@ -129,7 +132,7 @@ function factory (type, config, load, typed, math) {
129132
*/
130133
Parser.prototype.set = function (name, value) {
131134
// TODO: validate arguments
132-
return this.scope[name] = value;
135+
return customs.setSafeProperty(this.scope, name, value);
133136
};
134137

135138
/**

lib/expression/node/AccessorNode.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
'use strict';
22

3-
var getSafeProperty = require('../../utils/customs').getSafeProperty;
4-
53
function factory (type, config, load, typed) {
64
var Node = load(require('./Node'));
75
var access = load(require('./utils/access'));
6+
var getSafeProperty = load(require('./utils/customs')).getSafeProperty;
87

98
/**
109
* @constructor AccessorNode

lib/expression/node/AssignmentNode.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ function factory (type, config, load, typed) {
88
var matrix = load(require('../../type/matrix/function/matrix'));
99
var assign = load(require('./utils/assign'));
1010
var access = load(require('./utils/access'));
11+
var getSafeProperty = load(require('./utils/customs')).getSafeProperty;
12+
var setSafeProperty = load(require('./utils/customs')).setSafeProperty;
1113

1214
var keywords = require('../keywords');
1315
var operators = require('../operators');
@@ -99,6 +101,8 @@ function factory (type, config, load, typed) {
99101
AssignmentNode.prototype._compile = function (defs, args) {
100102
defs.assign = assign;
101103
defs.access = access;
104+
defs.getSafeProperty = getSafeProperty;
105+
defs.setSafeProperty = setSafeProperty;
102106

103107
var size;
104108
var object = this.object._compile(defs, args);
@@ -111,11 +115,11 @@ function factory (type, config, load, typed) {
111115
throw new TypeError('SymbolNode expected as object');
112116
}
113117

114-
return 'scope["' + this.object.name + '"] = ' + value;
118+
return 'setSafeProperty(scope, "' + this.object.name + '", ' + value + ')';
115119
}
116120
else if (this.index.isObjectProperty()) {
117121
// apply an object property for example `a.b=2`
118-
return object + '["' + this.index.getObjectProperty() + '"] = ' + value;
122+
return 'setSafeProperty(' + object + ', "' + this.index.getObjectProperty() + '", ' + value + ')';
119123
}
120124
else if (this.object.isSymbolNode) {
121125
// update a matrix subset, for example `a[2]=3`
@@ -126,7 +130,7 @@ function factory (type, config, load, typed) {
126130
' var object = ' + object + ';' +
127131
' var value = ' + value + ';' +
128132
' ' + size +
129-
' scope["' + this.object.name + '"] = assign(object, ' + index + ', value);' +
133+
' setSafeProperty(scope, "' + this.object.name + '", assign(object, ' + index + ', value));' +
130134
' return value;' +
131135
'})()';
132136
}
@@ -140,13 +144,13 @@ function factory (type, config, load, typed) {
140144
var parentObject = this.object.object._compile(defs, args);
141145

142146
if (this.object.index.isObjectProperty()) {
143-
var parentProperty = '["' + this.object.index.getObjectProperty() + '"]';
147+
var parentProperty = '"' + this.object.index.getObjectProperty() + '"';
144148
return '(function () {' +
145149
' var parent = ' + parentObject + ';' +
146-
' var object = parent' + parentProperty + ';' + // parentIndex is a property
150+
' var object = getSafeProperty(parent, ' + parentProperty + ');' + // parentIndex is a property
147151
' var value = ' + value + ';' +
148152
size +
149-
' parent' + parentProperty + ' = assign(object, ' + index + ', value);' +
153+
' setSafeProperty(parent, ' + parentProperty + ', assign(object, ' + index + ', value));' +
150154
' return value;' +
151155
'})()';
152156
}

lib/expression/node/FunctionAssignmentNode.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@ var keywords = require('../keywords');
44
var latex = require('../../utils/latex');
55
var operators = require('../operators');
66

7-
function isString (x) {
8-
return typeof x === 'string';
9-
}
10-
117
function factory (type, config, load, typed) {
128
var Node = load(require('./Node'));
9+
var setSafeProperty = load(require('./utils/customs')).setSafeProperty;
1310

1411
/**
1512
* @constructor FunctionAssignmentNode
@@ -64,6 +61,7 @@ function factory (type, config, load, typed) {
6461
*/
6562
FunctionAssignmentNode.prototype._compile = function (defs, args) {
6663
defs.typed = typed;
64+
defs.setSafeProperty = setSafeProperty;
6765

6866
// we extend the original args and add the args to the child object
6967
var childArgs = Object.create(args);
@@ -74,7 +72,7 @@ function factory (type, config, load, typed) {
7472
// compile the function expression with the child args
7573
var jsExpr = this.expr._compile(defs, childArgs);
7674

77-
return 'scope["' + this.name + '"] = ' +
75+
return 'setSafeProperty(scope, "' + this.name + '", ' +
7876
' (function () {' +
7977
' var fn = typed("' + this.name + '", {' +
8078
' "' + this.types.join(',') + '": function (' + this.params.join(',') + ') {' +
@@ -83,7 +81,7 @@ function factory (type, config, load, typed) {
8381
' });' +
8482
' fn.syntax = "' + this.name + '(' + this.params.join(', ') + ')";' +
8583
' return fn;' +
86-
' })()';
84+
' })())';
8785
};
8886

8987
/**

0 commit comments

Comments
 (0)