debug("Initializing InjectionChecker"); XSS.InjectionChecker = (async () => { await include([ "/lib/Base64.js", "/xss/FlashIdiocy.js", "/xss/ASPIdiocy.js"] ); var {FlashIdiocy, ASPIdiocy} = XSS; const wordCharRx = /\w/g; function fuzzify(s) { return s.replace(wordCharRx, '\\W*(?:/[*/][^]*)?$&'); } const IC_COMMENT_PATTERN = '\\s*(?:\\/[\\/\\*][^]+)?'; const IC_WINDOW_OPENER_PATTERN = fuzzify("alert|confirm|prompt|open(?:URL)?|print|show") + "\\w*" + fuzzify("Dialog"); const IC_EVAL_PATTERN = "\\b(?:" + fuzzify('eval|import|set(?:Timeout|Interval)|(?:f|F)unction|Script|toString|Worker|document|constructor|generateCRMFRequest|jQuery|fetch|write(?:ln)?|__(?:define(?:S|G)etter|noSuchMethod)__|definePropert(?:y|ies)') + "|\\$|" + IC_WINDOW_OPENER_PATTERN + ")\\b"; const IC_EVENT_PATTERN = "on(?:p(?:o(?:inter(?:l(?:ock(?:change|error)|eave)|o(?:ver|ut)|cancel|enter|down|move|up)|p(?:up(?:hid(?:den|ing)|show(?:ing|n)|positioned)|state))|a(?:(?:ymentmethodchang|st|us)e|ge(?:hide|show))|ush(?:subscriptionchange)?|ro(?:cessorerror|gress)|lay(?:ing)?|hoto)|Moz(?:S(?:wipeGesture(?:(?:May)?Start|Update|End)?|crolledAreaChanged)|M(?:agnifyGesture(?:Update|Start)?|ouse(?:PixelScroll|Hittest))|EdgeUI(?:C(?:omplet|ancel)|Start)ed|RotateGesture(?:Update|Start)?|(?:Press)?TapGesture|AfterPaint)|m(?:o(?:z(?:pointerlock(?:change|error)|fullscreen(?:change|error)|key(?:down|up)onplugin|accesskeynotfound|orientationchange)|use(?:l(?:ongtap|eave)|o(?:ver|ut)|enter|wheel|down|move|up))|(?:idimessag|ut)e|essage(?:error)?|ark)|c(?:o(?:m(?:p(?:osition(?:update|start|end)|lete)|mand(?:update)?)|n(?:t(?:rollerchange|extmenu)|nect(?:ionavailable)?)|py)|h(?:(?:arging(?:time)?ch)?ange|ecking)|a(?:n(?:play(?:through)?|cel)|ched)|u(?:echange|t)|l(?:ick|ose))|s(?:ou(?:rce(?:(?:clos|end)ed|open)|nd(?:start|end))|e(?:lect(?:ionchange|start)?|ek(?:ing|ed)|t)|h(?:ipping(?:address|option)change|ow)|t(?:a(?:techange|lled|rt)|o(?:rage|p))|u(?:ccess|spend|bmit)|peech(?:start|end)|croll)|d(?:r(?:a(?:g(?:e(?:n(?:ter|d)|xit)|leave|start|drop|over)?|in)|op)|evice(?:(?:orienta|mo)tion|proximity|change|light)|(?:ischargingtime|uration)change|ata(?:available)?|ownloading|blclick)|a(?:nimation(?:iteration|cancel|start|end)|u(?:dio(?:process|start|end)|xclick)|b(?:solutedeviceorientation|ort)|fter(?:scriptexecute|print)|dd(?:sourcebuffer|track)|ppinstalled|ctivate)|DOM(?:Node(?:Inserted(?:IntoDocument)?|Removed(?:FromDocument)?)|(?:CharacterData|Subtree)Modified|A(?:ttrModified|ctivate)|Focus(?:Out|In)|MouseScroll)|r(?:e(?:s(?:ourcetimingbufferfull|ponseprogress|u(?:lt|me)|ize|et)|move(?:sourcebuffer|track)|adystatechange|pea(?:tEven)?t|questprogress)|atechange)|w(?:ebkit(?:Animation(?:Iteration|Start|End)|animation(?:iteration|start|end)|(?:TransitionE|transitione)nd)|a(?:iting(?:forkey)?|rning)|heel)|v(?:rdisplay(?:(?:presentchang|activat)e|d(?:eactivate|isconnect)|connect)|o(?:iceschanged|lumechange)|(?:isibility|ersion)change)|b(?:e(?:fore(?:p(?:aste|rint)|scriptexecute|c(?:opy|ut)|unload)|gin(?:Event)?)|ufferedamountlow|l(?:ocked|ur)|roadcast|oundary)|t(?:o(?:uch(?:cancel|start|move|end)|ggle)|ransition(?:cancel|start|end|run)|ime(?:update|out)|e(?:rminate|xt)|ypechange)|l(?:o(?:ad(?:e(?:d(?:meta)?data|nd)|ing(?:error|done)?|start)?|stpointercapture)|(?:anguage|evel)change|y)|u(?:p(?:date(?:(?:fou|e)nd|ready|start)?|gradeneeded)|n(?:derflow|load|mute)|serproximity)|g(?:amepad(?:(?:dis)?connected|button(?:down|up)|axismove)|otpointercapture|et)|o(?:(?:rientationchang|(?:ff|n)lin|bsolet)e|verflow|pen)|e(?:n(?:d(?:Event|ed)?|crypted|ter)|mptied|rror|xit)|f(?:ullscreen(?:change|error)|ocus(?:out|in)?|inish)|no(?:tificationcl(?:ick|ose)|update|match)|SVG(?:(?:Unl|L)oad|Resize|Scroll|Zoom)|key(?:statuseschange|press|down|up)|(?:CheckboxStateC|hashc)hange|R(?:adioStateChange|equest)|in(?:stall|valid|put)|AppCommand|zoom)" // autogenerated from nsGkAtomList.h ; const IC_EVENT_DOS_PATTERN = "\\b(?:" + IC_EVENT_PATTERN + ")[^]*=[^]*\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b" + "|\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b[^]+\\b(?:" + IC_EVENT_PATTERN + ")[^]*="; var InjectionChecker = { reset: function() { this.isPost = this.base64 = this.nameAssignment = false; this.base64tested = []; }, fuzzify: fuzzify, syntax: new SyntaxChecker(), _log: function(msg, t, i) { if (msg) msg = this._printable(msg); if (t) msg += " - TIME: " + (Date.now() - t); if (i) msg += " - ITER: " + i; debug("[InjectionChecker]", msg); }, _printable: function(msg) { return msg.toString().replace(/[^\u0020-\u007e]/g, function(s) { return "{" + s.charCodeAt(0).toString(16) + "}"; }); }, log: function() {}, get logEnabled() { return this.log == this._log; }, set logEnabled(v) { this.log = v ? this._log : function() {}; }, escalate: function(msg) { this.log(msg); log("[InjectionChecker] ", msg); }, bb: function(brac, s, kets) { for (var j = 3; j-- > 0;) { s = brac + s + kets; if (this.checkJSSyntax(s)) return true; } return false; }, checkJSSyntax(s) { // bracket balancing for micro injections like "''), e v a l (name,''" if (/^(?:''|"")?[^\('"]*\)/.test(s)) return this.bb("x(\n", s, "\n)"); if (/^(?:''|"")?[^\['"]*\\]/.test(s)) return this.bb("y[\n", s, "\n]"); if (/^(?:''|"")?[^\{'"]*\}/.test(s)) return this.bb("function z() {\n", s, "\n}"); let syntax = this.syntax; s += " /* COMMENT_TERMINATOR */\nDUMMY_EXPR"; if (syntax.check(s)) { this.log("Valid fragment " + s); return true; } return false; }, checkTemplates(script) { let templateExpressions = script.replace(/[[\]{}]/g, ";"); return templateExpressions !== script && (this.maybeMavo(script) || (this.maybeJS(templateExpressions, true) && (this.syntax.check(templateExpressions) || /[^><=]=[^=]/.test(templateExpressions) && this.syntax.check( templateExpressions.replace(/([^><=])=(?=[^=])/g, '$1==')) ))); }, maybeMavo(s) { return /\[[^]*\([^]*\)[^]*\]/.test(s) && /\b(?:and|or|mod|\$url\b)/.test(s) && this.maybeJS(s.replace(/\b(?:and|or|mod|[[\]])/g, ',').replace(/\$url\b/g, 'location'), true); }, get breakStops() { var def = "\\/\\?&#;\\s\\x00}<>"; // we stop on URL, JS and HTML delimiters var bs = { nq: new RegExp("[" + def + "]") }; Array.forEach("'\"`", // special treatment for quotes function(c) { bs[c] = new RegExp("[" + def + c + "]"); } ); delete this.breakStops; return (this.breakStops = bs); }, collapseChars: (s) => s.replace(/\;+/g, ';').replace(/\/{4,}/g, '////') .replace(/\s+/g, (s) => /\n/g.test(s) ? '\n' : ' '), _reduceBackslashes: (bs) => bs.length % 2 ? "\\" : "", reduceQuotes: function(s) { if (s[0] == '/') { // reduce common leading path fragment resembling a regular expression or a comment s = s.replace(/^\/[^\/\n\r]+\//, '_RX_').replace(/^\/\/[^\r\n]*/, '//_COMMENT_'); } if (/\/\*/.test(s) || // C-style comments, would make everything really tricky /\w\s*(\/\/[\s\S]*)?\[[\s\S]*\w[\s\S]*\]/.test(s)) { // property accessors, risky return s; } if (/['"\/]/.test(s)) { // drop noisy backslashes s = s.replace(/\\{2,}/g, this._reduceBackslashes); // drop escaped quotes s = s.replace(/\\["'\/]/g, " EQ "); var expr; for (;;) { expr = s.replace(/(^[^'"\/]*[;,\+\-=\(\[]\s*)\/[^\/]+\//g, "$1 _RX_ ") .replace(/(^[^'"\/]*)(["']).*?\2/g, "$1 _QS_ "); if (expr == s) break; s = expr; } } // remove c++ style comments return s.replace(/^([^'"`\\]*?)\/\/[^\r\n]*/g, "$1//_COMMENT_"); }, reduceURLs: function(s) { // nested URLs with protocol are parsed as C++ style comments, and since // they're potentially very expensive, we preemptively remove them if possible while (/^[^'"]*?:\/\//.test(s)) { s = s.replace(/:\/\/[^*\s]*/, ':'); } s = s.replace(/:\/\/[^'"*\n]*/g, ':'); return (/\bhttps?:$/.test(s) && !/\bh\W*t\W*t\W*p\W*s?.*=/.test(s)) ? s.replace(/\b(?:[\w.]+=)?https?:$/, '') : s; }, reduceJSON(s) { const REPL = 'J'; const toStringRx = /^function\s*toString\(\)\s*{\s*\[native code\]\s*\}$/; // optimistic case first, one big JSON block let m = s.match(/{[^]+}|\[[^]*{[^]+}[^]*\]/); if (!m) return s; // semicolon-separated JSON chunks, like on syndication.twitter.com if (/}\s*;\s*{/.test(s)) s = s.split(";").map(chunk => this.reduceJSON(chunk)).join(";"); let [expr] = m; try { if (toStringRx.test(JSON.parse(expr).toString)) { this.log("Reducing big JSON " + expr); return this.reduceJSON(s.replace(expr, REPL)); } } catch (e) {} let iterations = 0; for (;;) { let prev = s; let start = s.indexOf("{"); let end = s.lastIndexOf("}"); let prevExpr = ""; while (start > -1 && end - start > 1) { expr = s.substring(start, end + 1); if (expr === prevExpr) break; end = s.lastIndexOf("}", end - 1); if (end === -1) { start = s.indexOf("{", start + 1); end = s.lastIndexOf("}"); } try { if (!toStringRx.test(JSON.parse(expr).toString)) continue; this.log("Reducing JSON " + expr); s = s.replace(expr, REPL); break; } catch (e) {} if (/\btoString\b[\s\S]*:/.test(expr)) { continue; } let qred = this.reduceQuotes(expr); if (/\{(?:\s*(?:(?:\w+:)+\w+)+;\s*)+\}/.test(qred)) { this.log("Reducing pseudo-JSON " + expr); s = s.replace(expr, REPL); break; } if (!/[(=.]|[^:\s]\s*\[|:\s*(?:location|document|set(?:Timeout|Interval)|eval|open|show\w*Dialog|alert|confirm|prompt)\b|(?:\]|set)\s*:/.test(qred) && this.checkJSSyntax("JSON = " + qred) // no-assignment JSON fails with "invalid label" ) { this.log("Reducing slow JSON " + expr); s = s.replace(expr, REPL); break; } prevExpr = expr; } if (s === prev) break; } return s; }, reduceXML: function reduceXML(s) { var res; for (let pos = s.indexOf("<"); pos !== -1; pos = s.indexOf("<", 1)) { let head = s.substring(0, pos); let tail = s.substring(pos); let qnum = 0; for (pos = -1; (pos = head.indexOf('"', ++pos)) > -1;) { if (pos === 0 || head[pos - 1] != '\\') qnum++; } if (qnum % 2) break; // odd quotes let t = tail.replace(/^<(\??\s*\/?[a-zA-Z][\w:-]*)(?:[\s+]+[\w:-]+="[^"]*")*[\s+]*(\/?\??)>/, '<$1$2>'); (res || (res = [])).push(head); s = t; } if (res) { res.push(s); s = res.join(''); } return s; }, _singleAssignmentRx: new RegExp( "(?:\\b" + fuzzify('document') + "\\b[^]*\\.|\\s" + fuzzify('setter') + "\\b[^]*=)|/.*/[^]*(?:\\.(?:" + "\\b" + fuzzify("onerror") + "\\b[^]*=|" + +fuzzify('source|toString') + ")|\\[)|" + IC_EVENT_DOS_PATTERN ), _riskyAssignmentRx: new RegExp( "\\b(?:" + fuzzify('location|innerHTML|outerHTML') + ")\\b[^]*=" ), _nameRx: new RegExp( "=[^]*\\b" + fuzzify('name') + "\\b|" + fuzzify("hostname") + "[^]*=[^]*(?:\\b\\d|[\"'{}~^|<*/+-])" ), _evalAliasingRx: new RegExp( "=[^]+\\[" + IC_EVAL_PATTERN + "\\W*\\]" // TODO: check if it can be coalesced into _maybeJSRx ), _maybeJSRx: new RegExp( '(?:(?:\\[[^]+\\]|\\.\\D)(?:[^]*\\([^]*\\)|[^*]`[^]+`|[^=]*=[^=][^]*\\S)' + // double function call '|\\([^]*\\([^]*\\)' + ')|(?:^|\\W)(?:' + IC_EVAL_PATTERN + ')(?:\\W+[^]*|)[(`]|(?:[=(]|\\{[^]+:)[^]*(?:' + // calling eval-like functions directly or... IC_EVAL_PATTERN + // ... assigning them to another function possibly called by the victim later ')[^]*[\\n,;:|]|\\b(?:' + fuzzify('setter|location|innerHTML|outerHTML') + // eval-like assignments ')\\b[^]*=|' + '.' + IC_COMMENT_PATTERN + "src" + IC_COMMENT_PATTERN + '=' + IC_EVENT_DOS_PATTERN + "|\\b" + fuzzify("onerror") + "\\b[^]*=" + "|=[s\\\\[ux]?\d{2}" + // escape (unicode/ascii/octal) "|\\b(?:toString|valueOf)\\b" + IC_COMMENT_PATTERN + "=[^]*(?:" + IC_EVAL_PATTERN + ")" + "|(?:\\)|(?:[^\\w$]|^)[$a-zA-Z_\\u0ff-\\uffff][$\\w\\u0ff-\\uffff]*)" + IC_COMMENT_PATTERN + '=>' + // concise function definition "|(?:[^\\w$]|^)" + IC_EVENT_PATTERN + IC_COMMENT_PATTERN + "=" ), _riskyParensRx: new RegExp( "(?:^|\\W)(?:(?:" + IC_EVAL_PATTERN + "|on\\w+)\\s*[(`]|" + fuzzify("with") + "\\b[^]*\\(|" + fuzzify("for") + "\\b[^]*\\([^]*[\\w$\\u0080-\\uffff]+[^]*\\b(?:" + fuzzify("in|of") + ")\\b)" ), _dotRx: /\./g, _removeDotsRx: /^openid\.[\w.-]+(?==)|(?:[?&#\/]|^)[\w.-]+(?=[\/\?&#]|$)|[\w\.]*\.(?:\b[A-Z]+|\w*\d|[a-z][$_])[\w.-]*|=[a-z.-]+\.(?:com|net|org|biz|info|xxx|[a-z]{2})(?:[;&/]|$)/g, _removeDots: (p) => p.replace(InjectionChecker._dotRx, '|'), _arrayAccessRx: /\s*\[\d+\]/g, _riskyOperatorsRx: /[+-]{2}\s*(?:\/[*/][\s\S]+)?(?:\w+(?:\/[*/][\s\S]+)?[[.]|location)|(?:\]|\.\s*(?:\/[*/][\s\S]+)?\w+|location)\s*(?:\/[*/][\s\S]+)?([+-]{2}|[+*\/<>~-]+\s*(?:\/[*/][\s\S]+)?=)/, // inc/dec/self-modifying assignments on DOM props _assignmentRx: /^(?:[^()="'\s]+=(?:[^(='"\[+]+|[?a-zA-Z_0-9;,&=/]+|[\d.|]+))$/, _badRightHandRx: /=[\s\S]*(?:_QS_\b|[|.][\s\S]*source\b|<[\s\S]*\/[^>]*>)/, _wikiParensRx: /^(?:[\w.|-]+\/)*\(*[\w\s-]+\([\w\s-]+\)[\w\s-]*\)*$/, _neutralDotsRx: /(?:^|[\/;&#])[\w-]+\.[\w-]+[\?;\&#]/g, _openIdRx: /^scope=(?:\w+\+)\w/, // OpenID authentication scope parameter, see http://forums.informaction.com/viewtopic.php?p=69851#p69851 _gmxRx: /\$\(clientName\)-\$\(dataCenter\)\.(\w+\.)+\w+/, // GMX webmail, see http://forums.informaction.com/viewtopic.php?p=69700#p69700 maybeJS(expr, mavoChecked = false) { if (!mavoChecked && this.maybeMavo(expr)) return true; if (/`[\s\S]*`/.test(expr) || // ES6 templates, extremely insidious!!! this._evalAliasingRx.test(expr) || this._riskyOperatorsRx.test(expr) // this must be checked before removing dots... ) return true; expr = // dotted URL components can lead to false positives, let's remove them expr.replace(this._removeDotsRx, this._removeDots) .replace(this._arrayAccessRx, '_ARRAY_ACCESS_') .replace(/<([\w:]+)>[^/g, '<$1/>') // reduce XML text nodes .replace(/