1 | dojo.provide("dojox.secure.capability"); |
---|
2 | |
---|
3 | dojox.secure.badProps = /^__|^(apply|call|callee|caller|constructor|eval|prototype|this|unwatch|valueOf|watch)$|__$/; |
---|
4 | dojox.secure.capability = { |
---|
5 | keywords: ["break", "case", "catch", "const", "continue","debugger", "default", "delete", "do", |
---|
6 | "else", "enum","false", "finally", "for", "function","if", "in", "instanceof", "new", |
---|
7 | "null","yield","return", "switch", |
---|
8 | "throw", "true", "try", "typeof", "var", "void", "while"], |
---|
9 | validate : function(/*string*/script,/*Array*/safeLibraries,/*Object*/safeGlobals) { |
---|
10 | // summary: |
---|
11 | // pass in the text of a script. If it passes and it can be eval'ed, it should be safe. |
---|
12 | // Note that this does not do full syntax checking, it relies on eval to reject invalid scripts. |
---|
13 | // There are also known false rejections: |
---|
14 | // |
---|
15 | // - Nesting vars inside blocks will not declare the variable for the outer block |
---|
16 | // - Named functions are not treated as declaration so they are generally not allowed unless the name is declared with a var. |
---|
17 | // - Var declaration that involve multiple comma delimited variable assignments are not accepted |
---|
18 | // script: |
---|
19 | // the script to execute |
---|
20 | // safeLibraries: |
---|
21 | // The safe libraries that can be called (the functions can not be access/modified by the untrusted code, only called) |
---|
22 | // safeGlobals: |
---|
23 | // These globals can be freely interacted with by the untrusted code |
---|
24 | |
---|
25 | |
---|
26 | var keywords = this.keywords; |
---|
27 | for (var i = 0; i < keywords.length; i++) { |
---|
28 | safeGlobals[keywords[i]]=true; |
---|
29 | } |
---|
30 | var badThis = "|this| keyword in object literal without a Class call"; |
---|
31 | var blocks = []; // keeps track of the outer references from each inner block |
---|
32 | if(script.match(/[\u200c-\u200f\u202a-\u202e\u206a-\u206f\uff00-\uffff]/)){ |
---|
33 | throw new Error("Illegal unicode characters detected"); |
---|
34 | } |
---|
35 | if(script.match(/\/\*@cc_on/)){ |
---|
36 | throw new Error("Conditional compilation token is not allowed"); |
---|
37 | } |
---|
38 | script = script.replace(/\\["'\\\/bfnrtu]/g, '@'). // borrows some tricks from json.js |
---|
39 | // now clear line comments, block comments, regular expressions, and strings. |
---|
40 | // By doing it all at once, the regular expression uses left to right parsing, and the most |
---|
41 | // left token is read first. It is also more compact. |
---|
42 | replace(/\/\/.*|\/\*[\w\W]*?\*\/|("[^"]*")|('[^']*')/g,function(t) { |
---|
43 | return t.match(/^\/\/|^\/\*/) ? ' ' : '0'; // comments are replaced with a space, strings and regex are replaced with a single safe token (0) |
---|
44 | }). |
---|
45 | replace(/\.\s*([a-z\$_A-Z][\w\$_]*)|([;,{])\s*([a-z\$_A-Z][\w\$_]*\s*):/g,function(t,prop,prefix,key) { |
---|
46 | // find all the dot property references, all the object literal keys, and labels |
---|
47 | prop = prop || key; |
---|
48 | if(/^__|^(apply|call|callee|caller|constructor|eval|prototype|this|unwatch|valueOf|watch)$|__$/.test(prop)){ |
---|
49 | throw new Error("Illegal property name " + prop); |
---|
50 | } |
---|
51 | return (prefix && (prefix + "0:")) || '~'; // replace literal keys with 0: and replace properties with the innocuous ~ |
---|
52 | }); |
---|
53 | script.replace(/([^\[][\]\}]\s*=)|((\Wreturn|\S)\s*\[\s*\+?)|([^=!][=!]=[^=])/g,function(oper) {// check for illegal operator usages |
---|
54 | if(!oper.match(/((\Wreturn|[=\&\|\:\?\,])\s*\[)|\[\s*\+$/)){ // the whitelist for [ operator for array initializer context or [+num] syntax |
---|
55 | throw new Error("Illegal operator " + oper.substring(1)); |
---|
56 | } |
---|
57 | }); |
---|
58 | script = script.replace(new RegExp("(" + safeLibraries.join("|") + ")[\\s~]*\\(","g"),function(call) { // find library calls and make them look safe |
---|
59 | return "new("; // turn into a known safe call |
---|
60 | }); |
---|
61 | function findOuterRefs(block,func) { |
---|
62 | var outerRefs = {}; |
---|
63 | block.replace(/#\d+/g,function(b) { // graft in the outer references from the inner scopes |
---|
64 | var refs = blocks[b.substring(1)]; |
---|
65 | for (var i in refs) { |
---|
66 | if(i == badThis) { |
---|
67 | throw i; |
---|
68 | } |
---|
69 | if(i == 'this' && refs[':method'] && refs['this'] == 1) { |
---|
70 | // if we are in an object literal the function may be a bindable method, this must only be in the local scope |
---|
71 | i = badThis; |
---|
72 | } |
---|
73 | if(i != ':method'){ |
---|
74 | outerRefs[i] = 2; // the reference is more than just local |
---|
75 | } |
---|
76 | } |
---|
77 | }); |
---|
78 | block.replace(/(\W|^)([a-z_\$A-Z][\w_\$]*)/g,function(t,a,identifier) { // find all the identifiers |
---|
79 | if(identifier.charAt(0)=='_'){ |
---|
80 | throw new Error("Names may not start with _"); |
---|
81 | } |
---|
82 | outerRefs[identifier] = 1; |
---|
83 | }); |
---|
84 | return outerRefs; |
---|
85 | } |
---|
86 | var newScript,outerRefs; |
---|
87 | function parseBlock(t,func,a,b,params,block) { |
---|
88 | block.replace(/(^|,)0:\s*function#(\d+)/g,function(t,a,b) { // find functions in object literals |
---|
89 | // note that if named functions are allowed, it could be possible to have label: function name() {} which is a security breach |
---|
90 | var refs = blocks[b]; |
---|
91 | refs[':method'] = 1;//mark it as a method |
---|
92 | }); |
---|
93 | block = block.replace(/(^|[^_\w\$])Class\s*\(\s*([_\w\$]+\s*,\s*)*#(\d+)/g,function(t,p,a,b) { // find Class calls |
---|
94 | var refs = blocks[b]; |
---|
95 | delete refs[badThis]; |
---|
96 | return (p||'') + (a||'') + "#" + b; |
---|
97 | }); |
---|
98 | outerRefs = findOuterRefs(block,func); // find the variables in this block |
---|
99 | function parseVars(t,a,b,decl) { // find var decls |
---|
100 | decl.replace(/,?([a-z\$A-Z][_\w\$]*)/g,function(t,identifier) { |
---|
101 | if(identifier == 'Class'){ |
---|
102 | throw new Error("Class is reserved"); |
---|
103 | } |
---|
104 | delete outerRefs[identifier]; // outer reference is safely referenced here |
---|
105 | }); |
---|
106 | } |
---|
107 | |
---|
108 | if(func) { |
---|
109 | parseVars(t,a,a,params); // the parameters are declare variables |
---|
110 | } |
---|
111 | block.replace(/(\W|^)(var) ([ \t,_\w\$]+)/g,parseVars); // and vars declare variables |
---|
112 | // FIXME: Give named functions #name syntax so they can be detected as vars in outer scopes (but be careful of nesting) |
---|
113 | return (a || '') + (b || '') + "#" + (blocks.push(outerRefs)-1); // return a block reference so the outer block can fetch it |
---|
114 | } |
---|
115 | do { |
---|
116 | // get all the blocks, starting with inside and moving out, capturing the parameters of functions and catchs as variables along the way |
---|
117 | newScript = script.replace(/((function|catch)(\s+[_\w\$]+)?\s*\(([^\)]*)\)\s*)?{([^{}]*)}/g, parseBlock); |
---|
118 | } |
---|
119 | while(newScript != script && (script = newScript)); // keep going until we can't find anymore blocks |
---|
120 | parseBlock(0,0,0,0,0,script); //findOuterRefs(script); // find the references in the outside scope |
---|
121 | for (i in outerRefs) { |
---|
122 | if(!(i in safeGlobals)) { |
---|
123 | throw new Error("Illegal reference to " + i); |
---|
124 | } |
---|
125 | } |
---|
126 | |
---|
127 | } |
---|
128 | }; |
---|