1 | var assert = require('assert');
|
2 | var math = require('../../index');
|
3 |
|
4 | describe('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 |
|
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 |
|
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 | });
|
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 |
|
283 |
|
284 |
|
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 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
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 |
|
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 |
|
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 |
|
388 | function isPlainObject (object) {
|
389 | return typeof object === 'object' && object &&
|
390 | object.constructor === Object &&
|
391 | object.__proto__ === Object.prototype;
|
392 | }
|
393 |
|
394 | function isPlainFunction (fn) {
|
395 | return typeof fn === 'function' && fn.prototype.constructor === fn;
|
396 | }
|