Permalink
Browse files

Only allow accessing/assigning properties on plain objects. Only allo…

…w calling known methods.
  • Loading branch information...
josdejong committed Apr 8, 2017
1 parent 4201268 commit ee6efa3768a38f5c8153d886d9197d5f85cd6f86
@@ -1,6 +1,18 @@
# History
## not yet released, version 3.11.5
- More security measures in the expression parser.
WARNING: more strict behavior of the expression parser introduces
a small chance that existing functionality breaks when using
undocumented methods or features. Sorry, but it's necessary to stay
secure. Measures:
- Accessing and assigning properties is now only allowed on plain
objects, not on classes, arrays, and functions anymore.
- Accessing methods is restricted to a set of known, safe methods.
## 2017-04-03, version 3.11.4
- Fixed a security vulnerability in the expression parser. Thanks @xfix.
@@ -24,8 +24,6 @@ When running a node.js server, it's good to be aware of the different
types of security risks. The risk whe running inside a browser may be
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
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.
Lastly, one could look into running server side code in a sandboxed
[node.js VM](https://nodejs.org/api/vm.html).
### Found a security vulnerability? Please report in private!
@@ -12,55 +12,67 @@ function factory (type, config, load, typed, math) {
}
});
math['true'] = true;
math['false'] = false;
math['null'] = null;
math['uninitialized'] = require('./utils/array').UNINITIALIZED;
setConstant(math, 'true', true);
setConstant(math, 'false', false);
setConstant(math, 'null', null);
setConstant(math, 'uninitialized', require('./utils/array').UNINITIALIZED);
if (config.number === 'BigNumber') {
math['Infinity'] = new type.BigNumber(Infinity);
math['NaN'] = new type.BigNumber(NaN);
setConstant(math, 'Infinity', new type.BigNumber(Infinity));
setConstant(math, 'NaN', new type.BigNumber(NaN));
object.lazy(math, 'pi', function () {return bigConstants.pi(type.BigNumber)});
object.lazy(math, 'tau', function () {return bigConstants.tau(type.BigNumber)});
object.lazy(math, 'e', function () {return bigConstants.e(type.BigNumber)});
object.lazy(math, 'phi', function () {return bigConstants.phi(type.BigNumber)}); // golden ratio, (1+sqrt(5))/2
setLazyConstant(math, 'pi', function () {return bigConstants.pi(type.BigNumber)});
setLazyConstant(math, 'tau', function () {return bigConstants.tau(type.BigNumber)});
setLazyConstant(math, 'e', function () {return bigConstants.e(type.BigNumber)});
setLazyConstant(math, 'phi', function () {return bigConstants.phi(type.BigNumber)}); // golden ratio, (1+sqrt(5))/2
// uppercase constants (for compatibility with built-in Math)
object.lazy(math, 'E', function () {return math.e;});
object.lazy(math, 'LN2', function () {return new type.BigNumber(2).ln();});
object.lazy(math, 'LN10', function () {return new type.BigNumber(10).ln()});
object.lazy(math, 'LOG2E', function () {return new type.BigNumber(1).div(new type.BigNumber(2).ln());});
object.lazy(math, 'LOG10E', function () {return new type.BigNumber(1).div(new type.BigNumber(10).ln())});
object.lazy(math, 'PI', function () {return math.pi});
object.lazy(math, 'SQRT1_2', function () {return new type.BigNumber('0.5').sqrt()});
object.lazy(math, 'SQRT2', function () {return new type.BigNumber(2).sqrt()});
setLazyConstant(math, 'E', function () {return math.e;});
setLazyConstant(math, 'LN2', function () {return new type.BigNumber(2).ln();});
setLazyConstant(math, 'LN10', function () {return new type.BigNumber(10).ln()});
setLazyConstant(math, 'LOG2E', function () {return new type.BigNumber(1).div(new type.BigNumber(2).ln());});
setLazyConstant(math, 'LOG10E', function () {return new type.BigNumber(1).div(new type.BigNumber(10).ln())});
setLazyConstant(math, 'PI', function () {return math.pi});
setLazyConstant(math, 'SQRT1_2', function () {return new type.BigNumber('0.5').sqrt()});
setLazyConstant(math, 'SQRT2', function () {return new type.BigNumber(2).sqrt()});
}
else {
math['Infinity'] = Infinity;
math['NaN'] = NaN;
setConstant(math, 'Infinity', Infinity);
setConstant(math, 'NaN', NaN);
math.pi = Math.PI;
math.tau = Math.PI * 2;
math.e = Math.E;
math.phi = 1.61803398874989484820458683436563811772030917980576286213545; // golden ratio, (1+sqrt(5))/2
setConstant(math, 'pi', Math.PI);
setConstant(math, 'tau', Math.PI * 2);
setConstant(math, 'e', Math.E);
setConstant(math, 'phi', 1.61803398874989484820458683436563811772030917980576286213545); // golden ratio, (1+sqrt(5))/2
// uppercase constants (for compatibility with built-in Math)
math.E = math.e;
math.LN2 = Math.LN2;
math.LN10 = Math.LN10;
math.LOG2E = Math.LOG2E;
math.LOG10E = Math.LOG10E;
math.PI = math.pi;
math.SQRT1_2 = Math.SQRT1_2;
math.SQRT2 = Math.SQRT2;
setConstant(math, 'E', math.e);
setConstant(math, 'LN2', Math.LN2);
setConstant(math, 'LN10', Math.LN10);
setConstant(math, 'LOG2E', Math.LOG2E);
setConstant(math, 'LOG10E', Math.LOG10E);
setConstant(math, 'PI', math.pi);
setConstant(math, 'SQRT1_2', Math.SQRT1_2);
setConstant(math, 'SQRT2', Math.SQRT2);
}
// complex i
math.i = type.Complex.I;
setConstant(math, 'i', type.Complex.I);
// meta information
math.version = require('./version');
setConstant(math, 'version', require('./version'));
}
// create a constant in both math and mathWithTransform
function setConstant(math, name, value) {
math[name] = value;
math.expression.mathWithTransform[name] = value;
}
// create a lazy constant in both math and mathWithTransform
function setLazyConstant (math, name, resolver) {
object.lazy(math, name, resolver);
object.lazy(math.expression.mathWithTransform, name, resolver);
}
exports.factory = factory;
@@ -1,5 +1,4 @@
var isFactory = require('./../utils/object').isFactory;
var deepExtend = require('./../utils/object').deepExtend;
var typedFactory = require('./typed');
var emitter = require('./../utils/emitter');
@@ -50,9 +49,9 @@ exports.create = function create (options) {
var math = emitter.mixin({});
math.type = {};
math.expression = {
transform: Object.create(math)
transform: {},
mathWithTransform: {}
};
math.algebra = {};
// create a new typed instance
math.typed = typedFactory.create(math.type);
@@ -122,6 +121,7 @@ exports.create = function create (options) {
// load the import and config functions
math['import'] = load(importFactory);
math['config'] = load(configFactory);
math.expression.mathWithTransform['config'] = math['config']
// apply options
if (options) {
@@ -3,7 +3,6 @@
var lazy = require('../../utils/object').lazy;
var isFactory = require('../../utils/object').isFactory;
var traverse = require('../../utils/object').traverse;
var extend = require('../../utils/object').extend;
var ArgumentsError = require('../../error/ArgumentsError');
function factory (type, config, load, typed, math) {
@@ -56,7 +55,7 @@ function factory (type, config, load, typed, math) {
*/
function math_import(object, options) {
var num = arguments.length;
if (num != 1 && num != 2) {
if (num !== 1 && num !== 2) {
throw new ArgumentsError('import', num, 1, 2);
}
@@ -105,6 +104,7 @@ function factory (type, config, load, typed, math) {
* @private
*/
function _import(name, value, options) {
// TODO: refactor this function, it's to complicated and contains duplicate code
if (options.wrap && typeof value === 'function') {
// create a wrapper around the function
value = _wrap(value);
@@ -145,10 +145,16 @@ function factory (type, config, load, typed, math) {
function _importTransform (name, value) {
if (value && typeof value.transform === 'function') {
math.expression.transform[name] = value.transform;
if (!unsafe[name]) {
math.expression.mathWithTransform[name] = value.transform
}
}
else {
// remove existing transform
delete math.expression.transform[name]
if (!unsafe[name]) {
math.expression.mathWithTransform[name] = value
}
}
}
@@ -185,6 +191,7 @@ function factory (type, config, load, typed, math) {
function _importFactory(factory, options) {
if (typeof factory.name === 'string') {
var name = factory.name;
var existingTransform = name in math.expression.transform
var namespace = factory.path ? traverse(math, factory.path) : math;
var existing = namespace.hasOwnProperty(name) ? namespace[name] : undefined;
@@ -218,9 +225,20 @@ function factory (type, config, load, typed, math) {
if (factory.lazy !== false) {
lazy(namespace, name, resolver);
if (!existingTransform) {
if (!unsafe[name]) {
lazy(math.expression.mathWithTransform, name, resolver);
}
}
}
else {
namespace[name] = resolver();
if (!existingTransform) {
if (!unsafe[name]) {
math.expression.mathWithTransform[name] = resolver();
}
}
}
math.emit('import', name, resolver, factory.path);
@@ -239,7 +257,7 @@ function factory (type, config, load, typed, math) {
* @private
*/
function isSupportedType(object) {
return typeof object == 'function'
return typeof object === 'function'
|| typeof object === 'number'
|| typeof object === 'string'
|| typeof object === 'boolean'
@@ -261,6 +279,13 @@ function factory (type, config, load, typed, math) {
return typeof fn === 'function' && typeof fn.signatures === 'object';
}
// namespaces not available in the parser for safety reasons
var unsafe = {
'expression': true,
'type': true,
'error': true
};
return math_import;
}
@@ -4,6 +4,7 @@ var extend = require('../utils/object').extend;
function factory (type, config, load, typed, math) {
var _parse = load(require('./parse'));
var customs = load(require('./node/utils/customs'));
/**
* @constructor Parser
@@ -111,7 +112,9 @@ function factory (type, config, load, typed, math) {
*/
Parser.prototype.get = function (name) {
// TODO: validate arguments
return this.scope[name];
return name in this.scope
? customs.getSafeProperty(this.scope, name)
: undefined;
};
/**
@@ -129,7 +132,7 @@ function factory (type, config, load, typed, math) {
*/
Parser.prototype.set = function (name, value) {
// TODO: validate arguments
return this.scope[name] = value;
return customs.setSafeProperty(this.scope, name, value);
};
/**
@@ -1,10 +1,9 @@
'use strict';
var getSafeProperty = require('../../utils/customs').getSafeProperty;
function factory (type, config, load, typed) {
var Node = load(require('./Node'));
var access = load(require('./utils/access'));
var getSafeProperty = load(require('./utils/customs')).getSafeProperty;
/**
* @constructor AccessorNode
@@ -8,6 +8,8 @@ function factory (type, config, load, typed) {
var matrix = load(require('../../type/matrix/function/matrix'));
var assign = load(require('./utils/assign'));
var access = load(require('./utils/access'));
var getSafeProperty = load(require('./utils/customs')).getSafeProperty;
var setSafeProperty = load(require('./utils/customs')).setSafeProperty;
var keywords = require('../keywords');
var operators = require('../operators');
@@ -99,6 +101,8 @@ function factory (type, config, load, typed) {
AssignmentNode.prototype._compile = function (defs, args) {
defs.assign = assign;
defs.access = access;
defs.getSafeProperty = getSafeProperty;
defs.setSafeProperty = setSafeProperty;
var size;
var object = this.object._compile(defs, args);
@@ -111,11 +115,11 @@ function factory (type, config, load, typed) {
throw new TypeError('SymbolNode expected as object');
}
return 'scope["' + this.object.name + '"] = ' + value;
return 'setSafeProperty(scope, "' + this.object.name + '", ' + value + ')';
}
else if (this.index.isObjectProperty()) {
// apply an object property for example `a.b=2`
return object + '["' + this.index.getObjectProperty() + '"] = ' + value;
return 'setSafeProperty(' + object + ', "' + this.index.getObjectProperty() + '", ' + value + ')';
}
else if (this.object.isSymbolNode) {
// update a matrix subset, for example `a[2]=3`
@@ -126,7 +130,7 @@ function factory (type, config, load, typed) {
' var object = ' + object + ';' +
' var value = ' + value + ';' +
' ' + size +
' scope["' + this.object.name + '"] = assign(object, ' + index + ', value);' +
' setSafeProperty(scope, "' + this.object.name + '", assign(object, ' + index + ', value));' +
' return value;' +
'})()';
}
@@ -140,13 +144,13 @@ function factory (type, config, load, typed) {
var parentObject = this.object.object._compile(defs, args);
if (this.object.index.isObjectProperty()) {
var parentProperty = '["' + this.object.index.getObjectProperty() + '"]';
var parentProperty = '"' + this.object.index.getObjectProperty() + '"';
return '(function () {' +
' var parent = ' + parentObject + ';' +
' var object = parent' + parentProperty + ';' + // parentIndex is a property
' var object = getSafeProperty(parent, ' + parentProperty + ');' + // parentIndex is a property
' var value = ' + value + ';' +
size +
' parent' + parentProperty + ' = assign(object, ' + index + ', value);' +
' setSafeProperty(parent, ' + parentProperty + ', assign(object, ' + index + ', value));' +
' return value;' +
'})()';
}
@@ -4,12 +4,9 @@ var keywords = require('../keywords');
var latex = require('../../utils/latex');
var operators = require('../operators');
function isString (x) {
return typeof x === 'string';
}
function factory (type, config, load, typed) {
var Node = load(require('./Node'));
var setSafeProperty = load(require('./utils/customs')).setSafeProperty;
/**
* @constructor FunctionAssignmentNode
@@ -64,6 +61,7 @@ function factory (type, config, load, typed) {
*/
FunctionAssignmentNode.prototype._compile = function (defs, args) {
defs.typed = typed;
defs.setSafeProperty = setSafeProperty;
// we extend the original args and add the args to the child object
var childArgs = Object.create(args);
@@ -74,7 +72,7 @@ function factory (type, config, load, typed) {
// compile the function expression with the child args
var jsExpr = this.expr._compile(defs, childArgs);
return 'scope["' + this.name + '"] = ' +
return 'setSafeProperty(scope, "' + this.name + '", ' +
' (function () {' +
' var fn = typed("' + this.name + '", {' +
' "' + this.types.join(',') + '": function (' + this.params.join(',') + ') {' +
@@ -83,7 +81,7 @@ function factory (type, config, load, typed) {
' });' +
' fn.syntax = "' + this.name + '(' + this.params.join(', ') + ')";' +
' return fn;' +
' })()';
' })())';
};
/**
Oops, something went wrong.

0 comments on commit ee6efa3

Please sign in to comment.