UNPKG

20.1 kBJavaScriptView Raw
1var assert = require('assert');
2var math = require('../../index');
3
4describe('security', function () {
5
6 it ('should not allow calling Function via constructor', function () {
7 assert.throws(function () {
8 math.eval('[].map.constructor("console.log(\'hacked...\')")()');
9 }, /Error: Cannot access method "map" as a property/);
10 })
11
12 it ('should not allow calling Function via constructor (2)', function () {
13 assert.throws(function () {
14 math.eval('sqrt.constructor("console.log(\'hacked...\')")()');
15 }, /Error: No access to method "constructor"/);
16 })
17
18 it ('should not allow calling Function via call/apply', function () {
19 assert.throws(function () {
20 math.eval('[].map.constructor.call(null, "console.log(\'hacked...\')")()');
21 }, /Error: Cannot access method "map" as a property/);
22
23 assert.throws(function () {
24 math.eval('[].map.constructor.apply(null, ["console.log(\'hacked...\')"])()');
25 }, /Error: Cannot access method "map" as a property/);
26 })
27
28 it ('should not allow calling constructor of a class', function () {
29 assert.throws(function () {
30 math.eval('[].constructor()');
31 }, /Error: No access to method "constructor"/);
32 })
33
34 it ('should not allow calling constructor', function () {
35 assert.throws(function () {
36 math.eval('constructor');
37 }, /Error: No access to property "constructor"/);
38
39 assert.throws(function () {
40 math.eval('toString');
41 }, /Cannot access method "toString" as a property/);
42 })
43
44 it ('should not allow calling Function via constructor', function () {
45 assert.throws(function () {
46 math.eval('[].map.constructor("console.log(\'hacked...\')")()');
47 }, /Error: Cannot access method "map" as a property/);
48
49 assert.throws(function () {
50 math.eval('[].map["constructor"]("console.log(\'hacked...\')")()');
51 }, /Error: Cannot access method "map" as a property/);
52 })
53
54 it ('should not allow calling Function via a disguised constructor', function () {
55 assert.throws(function () {
56 math.eval('prop="constructor"; [].map[prop]("console.log(\'hacked...\')")()');
57 }, /Error: Cannot access method "map" as a property/);
58
59 assert.throws(function () {
60 math.eval('[].map[concat("constr", "uctor")]("console.log(\'hacked...\')")()');
61 }, /Error: Cannot access method "map" as a property/);
62 })
63
64 it ('should not allow calling Function via bind', function () {
65 assert.throws(function () {
66 math.eval('[].map.constructor.bind()("console.log(\'hacked...\')")()');
67 }, /Error: Cannot access method "map" as a property/);
68 })
69
70 it ('should not allow calling Function via map/forEach', function () {
71 // TODO: simplify this test case, let it output console.log('hacked...')
72 assert.throws(function () {
73 math.eval('["//","a/*\\nreturn process.mainModule.require"]._data.map(cos.constructor)[1]()("child_process").execSync("ps >&2")');
74 }, /Error: No access to property "_data/);
75 })
76
77 it ('should not allow calling Function via Object.assign', function () {
78 // TODO: simplify this test case, let it output console.log('hacked...')
79 assert.throws(function () {
80 math.eval('{}.constructor.assign(cos.constructor, {binding: cos.bind})\n' +
81 '{}.constructor.assign(cos.constructor, {bind: null})\n' +
82 'cos.constructor.binding()("console.log(\'hacked...\')")()');
83 }, /Error: No access to property "bind/);
84 })
85
86 it ('should not allow disguising forbidden properties with unicode characters', function () {
87 var scope = {
88 a: {}
89 };
90
91 assert.throws(function () { math.eval('a.co\u006Estructor', scope); }, /Error: No access to property "constructor"/);
92 assert.throws(function () { math.eval('a["co\\u006Estructor"]', scope); }, /Error: No access to property "constructor"/);
93 assert.throws(function () { math.eval('a.constructor', scope); }, /Error: No access to property "constructor"/);
94 assert.throws(function () { math.eval('a.constructor = 2', scope); }, /Error: No access to property "constructor"/);
95 assert.throws(function () { math.eval('a["constructor"] = 2', scope); }, /Error: No access to property "constructor"/);
96 assert.throws(function () { math.eval('a["co\\u006Estructor"] = 2', scope); }, /Error: No access to property "constructor"/);
97 assert.throws(function () { math.eval('a = {"constructor": 2}', scope); }, /Error: No access to property "constructor"/);
98 assert.throws(function () { math.eval('a = {constructor: 2}', scope); }, /Error: No access to property "constructor"/);
99 assert.throws(function () { math.eval('a = {"co\\u006Estructor": 2}', scope); }, /Error: No access to property "constructor"/);
100 assert.throws(function () { math.eval('a = {co\u006Estructor: 2}', scope); }, /Error: No access to property "constructor"/);
101 })
102
103 it ('should not allow calling Function via imported, overridden function', function () {
104 assert.throws(function () {
105 var math2 = math.create();
106 math2.eval('import({matrix:cos.constructor},{override:1});x=["console.log(\'hacked...\')"];x()');
107 }, /Error: Undefined symbol import/);
108 })
109
110 it ('should not allow calling Function via index retrieval', function () {
111 assert.throws(function () {
112 math.eval('a=["console.log(\'hacked...\')"]._data;a.isRange=true;x={subset:cos.constructor}[a];x()');
113 }, /Error: No access to property "_data/);
114 })
115
116 it ('should not allow calling Function via getOwnPropertyDescriptor', function () {
117 assert.throws(function () {
118 math.eval('p = parser()\n' +
119 'p.eval("", [])\n' +
120 'o = p.get("constructor")\n' +
121 'c = o.getOwnPropertyDescriptor(o.__proto__, "constructor")\n' +
122 'c.value("console.log(\'hacked...\')")()');
123 }, /Error: No access to property "constructor"/);
124 })
125
126 it ('should not allow calling Function via a symbol', function () {
127 assert.throws(function () {
128 math.eval('O = {}.constructor\n' +
129 'd = O.getOwnPropertyDescriptor(O.__proto__, "constructor")\n' +
130 'eval("value", d)("console.log(\'hacked...\')")()');
131 }, /Error: No access to property "constructor/);
132 })
133
134 it ('should not allow calling Function via a specially encoded constructor property name', function () {
135 assert.throws(function () {
136 math.eval('[].map["\\x63onstructor"]("console.log(\'hacked...\')")()');
137 }, /Error: Cannot access method "map" as a property/);
138 })
139
140 it ('should not allow creating an Object with forbidden properties', function () {
141 assert.throws(function () {
142 math.eval('{hasOwnProperty: 2}');
143 }, /Error: No access to property "hasOwnProperty/);
144
145 assert.throws(function () {
146 math.eval('{constructor: 2}');
147 }, /Error: No access to property "constructor/);
148
149 assert.throws(function () {
150 math.eval('{toString: 2}');
151 }, /Error: No access to property "toString/);
152 })
153
154 it ('should not allow calling Object via a an object constructor', function () {
155 assert.throws(function () {
156 math.eval('{}.constructor.assign(expression.node.AssignmentNode.prototype, ' +
157 '{_compile: "".toString.bind("console.log(\'hacked...\')")})\n' +
158 'eval("a = 2")');
159 }, /Error: No access to property "constructor/);
160 })
161
162 it ('should not allow calling Object via a __defineGetter__', function () {
163 assert.throws(function () {
164 math.eval('expression.node.AssignmentNode.prototype.__defineGetter__("_compile", ' +
165 '{}.valueOf.bind("".toString.bind("console.log(\'hacked...\')"))); eval("a = 2")')
166 }, /Error: Undefined symbol expression/);
167 })
168
169 it ('should not allow calling eval via a custom compiled SymbolNode', function () {
170 assert.throws(function () {
171 math.eval("s={};s.__proto__=expression.node.SymbolNode[\"prototype\"];expression.node.SymbolNode.apply(s,[\"\\\");},\\\"exec\\\":function(a){return global.eval}};//\"]._data);s.compile().exec()(\"console.log(\'hacked...\')\")")
172 }, /Error: Undefined symbol expression/);
173 })
174
175 it ('should not allow calling eval via parse', function () {
176 assert.throws(function () {
177 math.eval('x=parse(\"cos\");x.name = \"\\\");},\\\"eval\\\": function(a) {return global.eval}};\/\/a\"; x.compile().eval()(\"console.log(\'hacked...\')\")')
178 }, /No access to property "name"/);
179 })
180
181 it ('should not allow calling eval via parse (2)', function () {
182 assert.throws(function () {
183 math.eval('p = parse("{}[\\"console.log(\'hacked...\')\\"]"); p.index.dimensions["0"].valueType = "boolean"; p.eval()')
184 }, /No access to property "index"/);
185 })
186
187 it ('should not allow calling eval via function.syntax', function () {
188 assert.throws(function () {
189 math.eval('cos.syntax="global.eval";s=unit("5 cm");s.units=[]._data;s.value=cos;s._compile=s.toString;expression.node.Node.prototype.compile.call(s).eval()("console.log(\'hacked...\')")')
190 }, /Error: No access to property "syntax"/);
191 })
192
193 it ('should not allow calling eval via clone', function () {
194 assert.throws(function () {
195 math.eval('expression.node.ConstantNode.prototype.clone.call({"value":"eval", "valueType":"null"}).eval()("console.log(\'hacked...\')")')
196 }, /Error: Undefined symbol expression/);
197 })
198
199 it ('should not allow replacing _compile', function () {
200 assert.throws(function () {
201 math.eval('c(x,y)="console.log(\'hacked...\')";expression.node.Node.prototype.compile.apply({_compile:c}).eval()')
202 }, /Error: Undefined symbol expression/);
203 })
204
205 it ('should not allow using restricted properties via subset (1)', function () {
206 assert.throws(function () {
207 math.eval('f()=false;' +
208 'g()={length:3};' +
209 'h()={"0":0,"1":0,"2":0};' +
210 'j(x)=[x("constructor")];' +
211 'k(x)={map:j};' +
212 'i={isIndex:true,isScalar:f,size:g,min:h,max:h,dimension:k};' +
213 'subset(subset([[[0]]],i),index(1,1,1))("console.log(\'hacked...\')")()')
214 }, /TypeError: Unexpected type of argument in function subset \(expected: Index, actual: Object, index: 1\)/);
215 })
216
217 it ('should not allow using restricted properties via subset (2)', function () {
218 assert.throws(function () {
219 math.eval("scope={}; setter = eval(\"f(obj, name, newValue, assign) = (obj[name] = newValue)\", scope); o = parse(\"1\"); setter(o, \"value\", \"eval\", subset); scope.obj.compile().eval()(\"console.log('hacked...')\")")
220 }, /Error: Undefined symbol name/);
221 })
222
223 it ('should not allow using restricted properties via subset (3)', function () {
224 assert.throws(function () {
225 math.eval('subset(parse("1"), index("value"), "eval").compile().eval()("console.log(\'hacked...\')")')
226 }, /Error: No access to property "value/);
227 })
228
229 it ('should not allow inserting fake nodes with bad code via node.map or node.transform', function () {
230 assert.throws(function () {
231 math.eval("badValue = {\"isNode\": true, \"_compile\": eval(\"f(a, b) = \\\"eval\\\"\")}; x = eval(\"f(child, path, parent) = path ==\\\"value\\\" ? newChild : child\", {\"newChild\": badValue}); parse(\"x = 1\").map(x).compile().eval()(\"console.log(\'hacked\')\")")
232 }, /TypeError: Callback function must return a Node/);
233
234 assert.throws(function () {
235 math.eval("badValue = {\"isNode\": true, \"type\": \"ConstantNode\", \"valueType\": \"string\", \"_compile\": eval(\"f(a, b) = \\\"eval\\\"\")}; x = eval(\"f(child, path, parent) = path ==\\\"value\\\" ? newChild : child\", {\"newChild\": badValue}); parse(\"x = 1\").map(x).compile().eval()(\"console.log(\'hacked...\')\")")
236 }); // The error message is vague but well...
237 })
238
239 it ('should not allow replacing validateSafeMethod with a local variant', function () {
240 assert.throws(function () {
241 math.eval("eval(\"f(validateSafeMethod)=cos.constructor(\\\"return eval\\\")()\")(eval(\"f(x,y)=0\"))(\"console.log('hacked...')\")")
242 }, /Error: No access to method "constructor"/);
243 })
244
245 it ('should not allow abusing toString', function () {
246 assert.throws(function () {
247 math.eval("badToString = eval(\"f() = 1\"); badReplace = eval(\"f(a, b) = \\\"eval\\\"\"); badNumber = {toString:badToString, replace:badReplace}; badNode = {\"isNode\": true, \"type\": \"ConstantNode\", \"valueType\": \"number\", \"value\": badNumber}; x = eval(\"f(child, path, parent) = badNode\", {badNode:badNode}); parse(\"(1)\").map(x).compile().eval()(\"console.log('hacked...')\")")
248 }, /Error: No access to property "toString"/);
249 })
250
251 it ('should not allow creating a bad FunctionAssignmentNode', function () {
252 assert.throws(function () {
253 math.eval("badNode={isNode:true,type:\"FunctionAssignmentNode\",expr:parse(\"1\"),types:{join:eval(\"f(a)=\\\"\\\"\")},params:{\"forEach\":eval(\"f(x)=1\"),\"join\":eval(\"f(x)=\\\"){return eval;}});return fn;})())}});return fn;})());}};//\\\"\")}};parse(\"f()=x\").map(eval(\"f(a,b,c)=badNode\",{\"badNode\":badNode})).compile().eval()()()(\"console.log('hacked...')\")")
254 }, /TypeError: Callback function must return a Node/);
255 })
256
257 it ('should not allow creating a bad OperatorNode (1)', function () {
258 assert.throws(function () {
259 math.eval("badNode={isNode:true,type:\"FunctionAssignmentNode\",expr:parse(\"1\"),types:{join:eval(\"f(a)=\\\"\\\"\")},params:{\"forEach\":eval(\"f(x)=1\"),\"join\":eval(\"f(x)=\\\"){return eval;}});return fn;})())}});return fn;})());}};//\\\"\")}};parse(\"f()=x\").map(eval(\"f(a,b,c)=badNode\",{\"badNode\":badNode})).compile().eval()()()(\"console.log('hacked...')\")")
260 }, /TypeError: Callback function must return a Node/);
261 })
262
263 it ('should not allow creating a bad OperatorNode (2)', function () {
264 assert.throws(function () {
265 math.eval("parse(\"(0)\").map(eval(\"f(a,b,c)=d\",{d:{isNode:true,type:\"OperatorNode\",fn:\"__lookupGetter__\",args:{map:eval(\"f(a)=b\",{b:{join:eval(\"f(a)=\\\"1)||eval;}};//\\\"\")}})}}})).compile().eval()(\"console.log('hacked...')\")")
266 }, /TypeError: Node expected for parameter "content"/);
267 })
268
269 it ('should not allow creating a bad ConstantNode', function () {
270 assert.throws(function () {
271 math.eval('f(x,y)="eval";g()=3;fakeConstantNode={"isNode": true, "type": "ConstantNode", "valueType": "number", "value": {"replace": f, "toString": g}};injectFakeConstantNode(child,path,parent)=path=="value"?fakeConstantNode:child;parse("a=3").map(injectFakeConstantNode).compile().eval()("console.log(\'hacked...\')")')
272 }, /Error: No access to property "toString"/);
273 })
274
275 it ('should not allow creating a bad ArrayNode', function () {
276 assert.throws(function () {
277 math.eval('g(x)="eval";f(x)=({join: g});fakeArrayNode={isNode: true, type: "ArrayNode", items: {map: f}};injectFakeArrayNode(child,path,parent)=path=="value"?fakeArrayNode:child;parse("a=3").map(injectFakeArrayNode).compile().eval()[1]("console.log(\'hacked...\')")')
278 }, /TypeError: Callback function must return a Node/);
279 })
280
281 it ('should not allow unescaping escaped double quotes', function () {
282 // exploits:
283 // 1) A bug in validateSafeMethod which allows to call any method in Object.prototype
284 // 2) A bug in stringify
285 assert.throws(function () {
286 math.eval("x=parse(\"\\\"a\\\"\");x.__defineGetter__(\"value\",eval(\"f()=\\\"false\\\\\\\\\\\\\\\\\\\\\\\"&&eval;}};\\\/\\\/\\\"\")); x.compile().eval()(\"console.log('hacked...')\")")
287 }, /Error: No access to method "__defineGetter__"/);
288 })
289
290 it ('should not allow using method chain', function () {
291 assert.throws(function () {
292 math.eval("chain(\"a(){return eval;};function b\").typed({\"\":f()=0}).done()()(\"console.log(\'hacked...\')\")")
293 }, /is not a function/);
294 })
295
296 it ('should not allow using method chain (2)', function () {
297 assert.throws(function () {
298 math.eval("evilMath=chain().create().done();evilMath.import({\"_compile\":f(a,b,c)=\"eval\",\"isNode\":f()=true}); parse(\"(1)\").map(g(a,b,c)=evilMath.chain()).compile().eval()(\"console.log(\'hacked...\')\")")
299 }, /is not a function/);
300 })
301
302 it ('should not allow using method Chain', function () {
303 assert.throws(function () {
304 math.eval("x=parse(\"a\",{nodes:{a:Chain}});Chain.bind(x,{})();evilMath=x.create().done();evilMath.import({\"_compile\":f(a,b,c)=\"eval\",\"isNode\":f()=true}); parse(\"(1)\").map(g(a,b,c)=evilMath.chain()).compile().eval()(\"console.log(\'hacked...\')\")");
305 }, /Undefined symbol Chain/);
306 })
307
308 it ('should not allow passing a function name containg bad contents', function () {
309 // underlying issues where:
310 // the input '[]["fn"]()=0'
311 // - defines a function in the root scope, but this shouldn't be allowed syntax
312 // - there is a typed function created which unsecurely evaluates JS code with the function name in it
313 // -> when the function name contains JS code it can be executed, example:
314 //
315 // var fn = typed("(){}+console.log(`hacked...`);function a", { "": function () { } })
316
317 assert.throws(function () {
318 math.eval('[]["(){}+console.log(`hacked...`);function a"]()=0')
319 }, /SyntaxError: Invalid left hand side of assignment operator =/);
320
321 assert.throws(function () {
322 math.eval('{}["(){}+console.log(`hacked...`);function a"]()=0')
323 }, /SyntaxError: Invalid left hand side of assignment operator =/);
324 })
325
326 it ('should allow calling functions on math', function () {
327 assert.equal(math.eval('sqrt(4)'), 2);
328 })
329
330 it ('should allow invoking methods on complex numbers', function () {
331 assert.deepEqual(math.eval('complex(4, 0).sqrt(2)'), math.complex(2, 0));
332 })
333
334 it ('should allow accessing properties on an object', function () {
335 assert.deepEqual(math.eval('obj.a', {obj: {a:42}}), 42);
336 })
337
338 it ('should not allow accessing inherited properties on an object', function () {
339 assert.throws(function () {
340 math.eval('obj.constructor', {obj: {a:42}});
341 }, /Error: No access to property "constructor"/)
342 })
343
344 it ('should not allow accessing __proto__', function () {
345 assert.throws(function () {
346 math.eval('{}.__proto__');
347 }, /Error: No access to property "__proto__"/)
348 })
349
350 it ('should not allow getting properties from non plain objects', function () {
351 assert.throws(function () {math.eval('[]._data')}, /No access to property "_data"/)
352 assert.throws(function () {math.eval('unit("5cm").valueOf')}, /Cannot access method "valueOf" as a property/);
353 });
354
355 it ('should not have access to specific namespaces', function () {
356 Object.keys(math.expression.mathWithTransform).forEach (function (name) {
357 var value = math.expression.mathWithTransform[name];
358
359 // only plain functions allowed, no constructor functions
360 if (typeof value === 'function') {
361 assert.strictEqual(isPlainFunction(value), true,
362 'only plain functions expected, constructor functions not allowed (name: ' + name + ')');
363 }
364 else {
365 // plain objects not allowed, only class instances like units and complex numbers
366 if (value && typeof value === 'object') {
367 if (isPlainObject(value) && (name !== 'uninitialized' )) {
368 throw new Error('plain objects are not allowed, only class instances (object name: ' + name + ')');
369 }
370 }
371 }
372
373 });
374
375 assert.throws(function () {math.eval('expression')}, /Undefined symbol/);
376 assert.throws(function () {math.eval('type')}, /Undefined symbol/);
377 assert.throws(function () {math.eval('error')}, /Undefined symbol/);
378 assert.throws(function () {math.eval('json')}, /Undefined symbol/);
379
380 assert.strictEqual(math.expression.mathWithTransform.Matrix, undefined);
381 assert.strictEqual(math.expression.mathWithTransform.Node, undefined);
382 assert.strictEqual(math.expression.mathWithTransform.chain, undefined);
383 assert.deepEqual(math.eval('chain'), math.unit('chain'));
384 });
385
386});
387
388function isPlainObject (object) {
389 return typeof object === 'object' && object &&
390 object.constructor === Object &&
391 object.__proto__ === Object.prototype;
392}
393
394function isPlainFunction (fn) {
395 return typeof fn === 'function' && fn.prototype.constructor === fn;
396}