@@ -22,6 +22,7 @@ XSS.InjectionChecker = (async () => {
2222 await include ( [
2323 "/nscl/common/SyntaxChecker.js" ,
2424 "/nscl/common/Base64.js" ,
25+ "/nscl/common/AsyncRegExp.js" ,
2526 "/nscl/common/DebuggableRegExp.js" ,
2627 "/nscl/common/Timing.js" ,
2728 "/xss/ASPIdiocy.js" ,
@@ -89,9 +90,17 @@ XSS.InjectionChecker = (async () => {
8990 } ,
9091 set debugging ( b ) {
9192 this . logEnabled = b ;
93+
9294 for ( const rx of [ "_maybeJSRx" , "_riskyOperatorsRx" ] ) {
9395 if ( this [ rx ] . originalRx ) this [ rx ] = this [ rx ] . originalRx ;
94- if ( b ) this [ rx ] = new DebuggableRegExp ( this [ rx ] ) ;
96+ if ( b ) {
97+ this [ rx ] = new DebuggableRegExp ( this [ rx ] , rx => {
98+ rx = new AsyncRegExp ( rx ) ;
99+ // uncomment the following to unconditionally offload to the shared worker
100+ // rx.forceRemote = true;
101+ return rx ;
102+ } ) ;
103+ }
95104 }
96105 } ,
97106
@@ -123,20 +132,20 @@ XSS.InjectionChecker = (async () => {
123132 return false ;
124133 } ,
125134
126- checkTemplates ( script ) {
135+ async checkTemplates ( script ) {
127136 let templateExpressions = script . replace ( / [ [ \] { } ] / g, ";" ) ;
128137 return templateExpressions !== script &&
129- ( this . maybeMavo ( script ) ||
130- ( this . maybeJS ( templateExpressions , true ) &&
138+ ( await this . maybeMavo ( script ) ||
139+ ( await this . maybeJS ( templateExpressions , true ) &&
131140 ( this . syntax . check ( templateExpressions ) ||
132141 / [ ^ > < = ] = [ ^ = ] / . test ( templateExpressions ) && this . syntax . check (
133142 templateExpressions . replace ( / ( [ ^ > < = ] ) = (? = [ ^ = ] ) / g, '$1==' ) )
134143 ) ) ) ;
135144 } ,
136145
137- maybeMavo ( s ) {
146+ async maybeMavo ( s ) {
138147 return / \[ [ ^ ] * \( [ ^ ] * \) [ ^ ] * \] / . test ( s ) && / \b (?: a n d | o r | m o d | \$ u r l \b ) / . test ( s ) &&
139- this . maybeJS ( s . replace ( / \b (?: a n d | o r | m o d | [ [ \] ] ) / g, ',' ) . replace ( / \$ u r l \b / g, 'location' ) , true ) ;
148+ await this . maybeJS ( s . replace ( / \b (?: a n d | o r | m o d | [ [ \] ] ) / g, ',' ) . replace ( / \$ u r l \b / g, 'location' ) , true ) ;
140149 } ,
141150 get breakStops ( ) {
142151 var def = "\\/\\?&#;\\s\\x00}<>" ; // we stop on URL, JS and HTML delimiters
@@ -325,7 +334,7 @@ XSS.InjectionChecker = (async () => {
325334 "=[^]+\\[" + IC_EVAL_PATTERN + "\\W*\\]" // TODO: check if it can be coalesced into _maybeJSRx
326335 ) ,
327336
328- _maybeJSRx : new RegExp (
337+ _maybeJSRx : new AsyncRegExp (
329338 '(?:(?:\\[[^]+\\]|\\.\\D)[^;&/\'"]*(?:/[^]*|)' +
330339 '(?:\\([^]*\\)|[^]*`[^]+`|=[^=][^]*\\S)' +
331340 // double function call
@@ -358,7 +367,9 @@ XSS.InjectionChecker = (async () => {
358367 _arrayAccessRx : / \s * \[ \d + \] / g,
359368
360369 // inc/dec/self-modifying assignments on DOM props or special properties in object literals via Symbol
361- _riskyOperatorsRx : / (?: \+ \+ | - - ) \s * (?: \/ [ * / ] [ \s \S ] + ) ? (?: (?: \$ | \w { 3 , } ) (?: \/ [ * / ] [ \s \S ] + ) ? (?: \[ | \. \D ) | l o c a t i o n ) | (?: \] | (?: \$ | \w { 3 , } ) (?: \/ [ * / ] [ \s \S ] + ) ? \. [ ^ ] + | l o c a t i o n ) \s * (?: \/ [ * / ] [ \s \S ] + ) ? ( \+ \+ | - - | [ + * \/ < > ~ - ] + \s * (?: \/ [ * / ] [ \s \S ] + ) ? = ) | \{ [ ^ ] * \[ [ ^ ] * S y m b o l [ ^ ] * (?: \. \D | \[ ) [ ^ ] * : / ,
370+ _riskyOperatorsRx : new AsyncRegExp (
371+ / (?: \+ \+ | - - ) \s * (?: \/ [ * / ] [ \s \S ] + ) ? (?: (?: \$ | \w { 3 , } ) (?: \/ [ * / ] [ \s \S ] + ) ? (?: \[ | \. \D ) | l o c a t i o n ) | (?: \] | (?: \$ | \w { 3 , } ) (?: \/ [ * / ] [ \s \S ] + ) ? \. [ ^ ] + | l o c a t i o n ) \s * (?: \/ [ * / ] [ \s \S ] + ) ? ( \+ \+ | - - | [ + * \/ < > ~ - ] + \s * (?: \/ [ * / ] [ \s \S ] + ) ? = ) | \{ [ ^ ] * \[ [ ^ ] * S y m b o l [ ^ ] * (?: \. \D | \[ ) [ ^ ] * : /
372+ ) ,
362373
363374 _assignmentRx : / ^ (?: [ ^ ( ) = " ' \s ] + = (?: [ ^ ( = ' " \[ + ] + | [ ? a - z A - Z _ 0 - 9 ; , & = / ] + | [ \d . | ] + ) ) $ / ,
364375 _badRightHandRx : / = [ \s \S ] * (?: _ Q S _ \b | [ | . ] [ \s \S ] * s o u r c e \b | < [ \s \S ] * \/ [ ^ > ] * > ) / ,
@@ -367,12 +378,12 @@ XSS.InjectionChecker = (async () => {
367378 _openIdRx : / ^ s c o p e = (?: \w + \+ ) \w / , // OpenID authentication scope parameter, see http://forums.informaction.com/viewtopic.php?p=69851#p69851
368379 _gmxRx : / \$ \( c l i e n t N a m e \) - \$ \( d a t a C e n t e r \) \. ( \w + \. ) + \w + / , // GMX webmail, see http://forums.informaction.com/viewtopic.php?p=69700#p69700
369380
370- maybeJS ( expr , mavoChecked = false ) {
371- if ( ! mavoChecked && this . maybeMavo ( expr ) ) return true ;
381+ async maybeJS ( expr , mavoChecked = false ) {
382+ if ( ! mavoChecked && await this . maybeMavo ( expr ) ) return true ;
372383
373384 if ( / ` [ \s \S ] * ` / . test ( expr ) || // ES6 templates, extremely insidious!!!
374385 this . _evalAliasingRx . test ( expr ) ||
375- this . _riskyOperatorsRx . test ( expr ) // this must be checked before removing dots...
386+ await this . _riskyOperatorsRx . asyncTest ( expr ) // this must be checked before removing dots...
376387 ) return true ;
377388
378389 expr = // dotted URL components can lead to false positives, let's remove them
@@ -390,13 +401,13 @@ XSS.InjectionChecker = (async () => {
390401 return this . _singleAssignmentRx . test ( expr ) || this . _riskyAssignmentRx . test ( expr ) && this . _nameRx . test ( expr ) ;
391402
392403 return this . _riskyParensRx . test ( expr ) ||
393- this . _maybeJSRx . test ( expr . replace ( this . _neutralDotsOrParensRx , '' ) ) &&
404+ await this . _maybeJSRx . asyncTest ( expr . replace ( this . _neutralDotsOrParensRx , '' ) ) &&
394405 ! this . _wikiParensRx . test ( expr ) ;
395406
396407 } ,
397408
398- checkNonTrivialJSSyntax : function ( expr ) {
399- return this . maybeJS ( this . reduceQuotes ( expr ) ) && this . checkJSSyntax ( expr ) ;
409+ async checkNonTrivialJSSyntax ( expr ) {
410+ return await this . maybeJS ( this . reduceQuotes ( expr ) ) && this . checkJSSyntax ( expr ) ;
400411 } ,
401412
402413
@@ -490,14 +501,14 @@ XSS.InjectionChecker = (async () => {
490501 return res . join ( '' ) ;
491502 } ,
492503
493- checkLastFunction : function ( ) {
504+ async checkLastFunction ( ) {
494505 var fn = this . syntax . lastFunction ;
495506 if ( ! fn ) return false ;
496507 var m = fn . toString ( ) . match ( / \{ ( [ \s \S ] * ) \} / ) ;
497508 if ( ! m ) return false ;
498509 var expr = this . stripLiteralsAndComments ( m [ 1 ] ) ;
499510 let ret = / = [ \s \S ] * c o o k i e | \b (?: s e t t e r | d o c u m e n t | l o c a t i o n | (?: i n n | o u t ) e r H T M L | \. \W * s r c ) [ \s \S ] * = | [ \w $ \u0080 - \uffff \) \] ] \s * [ \[ \( ] / . test ( expr ) ||
500- this . maybeJS ( expr ) ;
511+ await this . maybeJS ( expr ) ;
501512 if ( ret ) {
502513 this . escalate ( `${ expr } has been flagged as dangerous JS (${ RegExp . lastMatch } )` ) ;
503514 }
@@ -563,7 +574,7 @@ XSS.InjectionChecker = (async () => {
563574 s += ';' + s . match ( / \* \/ [ \s \S ] + / ) ;
564575 }
565576
566- if ( ! this . maybeJS ( s ) ) return false ;
577+ if ( ! await this . maybeJS ( s ) ) return false ;
567578
568579 const MAX_LOOPS = 1200 ;
569580
@@ -601,15 +612,15 @@ XSS.InjectionChecker = (async () => {
601612 let breakSeq = m [ 1 ] ;
602613 let quote = breakSeq in this . breakStops ? breakSeq : '' ;
603614
604- if ( ! this . maybeJS ( quote ? quote + subj : subj ) ) {
615+ if ( ! await this . maybeJS ( quote ? quote + subj : subj ) ) {
605616 this . log ( "Fast escape on " + subj , iterations ) ;
606617 return false ;
607618 }
608619
609620 let script = this . reduceURLs ( subj ) ;
610621
611622 if ( script . length < subj . length ) {
612- if ( ! this . maybeJS ( script ) ) {
623+ if ( ! await this . maybeJS ( script ) ) {
613624 this . log ( "Skipping to first nested URL in " + subj , iterations ) ;
614625 injectionFinderRx . lastIndex += subj . indexOf ( "://" ) + 1 ;
615626 continue ;
@@ -686,16 +697,16 @@ XSS.InjectionChecker = (async () => {
686697 }
687698
688699 if ( quote ) {
689- if ( this . checkNonTrivialJSSyntax ( expr ) ) {
700+ if ( await this . checkNonTrivialJSSyntax ( expr ) ) {
690701 this . log ( "Non-trivial JS inside quoted string detected" , iterations ) ;
691702 return true ;
692703 }
693704 script = this . syntax . unquote ( quote + expr , quote ) ;
694- if ( script && this . maybeJS ( script ) &&
695- ( this . checkNonTrivialJSSyntax ( script ) ||
696- / ' ./ . test ( script ) && this . checkNonTrivialJSSyntax ( "''" + script + "'" ) ||
697- / " ./ . test ( script ) && this . checkNonTrivialJSSyntax ( '""' + script + '"' )
698- ) && this . checkLastFunction ( )
705+ if ( script && await this . maybeJS ( script ) &&
706+ ( await this . checkNonTrivialJSSyntax ( script ) ||
707+ / ' ./ . test ( script ) && await this . checkNonTrivialJSSyntax ( "''" + script + "'" ) ||
708+ / " ./ . test ( script ) && await this . checkNonTrivialJSSyntax ( '""' + script + '"' )
709+ ) && await this . checkLastFunction ( )
699710 ) {
700711 this . log ( "JS quote Break Injection detected" , iterations ) ;
701712 return true ;
@@ -715,14 +726,14 @@ XSS.InjectionChecker = (async () => {
715726 }
716727 }
717728
718- if ( this . maybeJS ( this . reduceQuotes ( script ) ) ) {
729+ if ( await this . maybeJS ( this . reduceQuotes ( script ) ) ) {
719730
720- if ( this . checkJSSyntax ( script ) && this . checkLastFunction ( ) ) {
731+ if ( this . checkJSSyntax ( script ) && await this . checkLastFunction ( ) ) {
721732 this . log ( "JS Break Injection detected" , iterations ) ;
722733 return true ;
723734 }
724735
725- if ( this . checkTemplates ( script ) ) {
736+ if ( await this . checkTemplates ( script ) ) {
726737 this . log ( "JS template expression injection detected" , iterations ) ;
727738 return true ;
728739 }
@@ -822,7 +833,7 @@ XSS.InjectionChecker = (async () => {
822833
823834 this . syntax . lastFunction = null ;
824835 let ret = await this . checkAttributes ( s ) ||
825- ( / [ \\ \( ] | = [ ^ = ] / . test ( s ) || this . _riskyOperatorsRx . test ( s ) ) && await this . checkJSBreak ( s ) || // MAIN
836+ ( / [ \\ \( ] | = [ ^ = ] / . test ( s ) || await this . _riskyOperatorsRx . asyncTest ( s ) ) && await this . checkJSBreak ( s ) || // MAIN
826837 hasUnicodeEscapes && await this . checkJS ( this . unescapeJS ( s ) , true ) ; // optional unescaped recursion
827838 if ( ret ) {
828839 let msg = "JavaScript Injection in " + s ;
0 commit comments