@@ -30,8 +30,10 @@ import * as utils from '../utils';
30
30
* A list of CSP directives that can allow XSS vulnerabilities if they fail
31
31
* validation.
32
32
*/
33
- export const DIRECTIVES_CAUSING_XSS : Directive [ ] =
34
- [ Directive . SCRIPT_SRC , Directive . OBJECT_SRC , Directive . BASE_URI ] ;
33
+ export const DIRECTIVES_CAUSING_XSS : Directive [ ] = [
34
+ Directive . SCRIPT_SRC , Directive . SCRIPT_SRC_ATTR , Directive . SCRIPT_SRC_ELEM ,
35
+ Directive . OBJECT_SRC , Directive . BASE_URI
36
+ ] ;
35
37
36
38
/**
37
39
* A list of URL schemes that can allow XSS vulnerabilities when requests to
@@ -54,20 +56,23 @@ export const URL_SCHEMES_CAUSING_XSS: string[] = ['data:', 'http:', 'https:'];
54
56
* is present).
55
57
*/
56
58
export function checkScriptUnsafeInline ( effectiveCsp : Csp ) : Finding [ ] {
57
- const directiveName =
58
- effectiveCsp . getEffectiveDirective ( Directive . SCRIPT_SRC ) ;
59
- const values : string [ ] = effectiveCsp . directives [ directiveName ] || [ ] ;
59
+ const violations : Finding [ ] = [ ] ;
60
+ const directivesToCheck = effectiveCsp . getEffectiveDirectives ( [
61
+ Directive . SCRIPT_SRC , Directive . SCRIPT_SRC_ATTR , Directive . SCRIPT_SRC_ELEM
62
+ ] ) ;
60
63
61
- // Check if unsafe-inline is present.
62
- if ( values . includes ( Keyword . UNSAFE_INLINE ) ) {
63
- return [ new Finding (
64
- Type . SCRIPT_UNSAFE_INLINE ,
65
- `'unsafe-inline' allows the execution of unsafe in-page scripts ` +
66
- 'and event handlers.' ,
67
- Severity . HIGH , directiveName , Keyword . UNSAFE_INLINE ) ] ;
64
+ for ( const directive of directivesToCheck ) {
65
+ const values = effectiveCsp . directives [ directive ] || [ ] ;
66
+ if ( values . includes ( Keyword . UNSAFE_INLINE ) ) {
67
+ violations . push ( new Finding (
68
+ Type . SCRIPT_UNSAFE_INLINE ,
69
+ `'unsafe-inline' allows the execution of unsafe in-page scripts ` +
70
+ 'and event handlers.' ,
71
+ Severity . HIGH , directive , Keyword . UNSAFE_INLINE ) ) ;
72
+ }
68
73
}
69
74
70
- return [ ] ;
75
+ return violations ;
71
76
}
72
77
73
78
@@ -81,19 +86,23 @@ export function checkScriptUnsafeInline(effectiveCsp: Csp): Finding[] {
81
86
* @param parsedCsp Parsed CSP.
82
87
*/
83
88
export function checkScriptUnsafeEval ( parsedCsp : Csp ) : Finding [ ] {
84
- const directiveName = parsedCsp . getEffectiveDirective ( Directive . SCRIPT_SRC ) ;
85
- const values : string [ ] = parsedCsp . directives [ directiveName ] || [ ] ;
89
+ const violations : Finding [ ] = [ ] ;
90
+ const directivesToCheck = parsedCsp . getEffectiveDirectives ( [
91
+ Directive . SCRIPT_SRC , Directive . SCRIPT_SRC_ATTR , Directive . SCRIPT_SRC_ELEM
92
+ ] ) ;
86
93
87
- // Check if unsafe-eval is present.
88
- if ( values . includes ( Keyword . UNSAFE_EVAL ) ) {
89
- return [ new Finding (
90
- Type . SCRIPT_UNSAFE_EVAL ,
91
- `'unsafe-eval' allows the execution of code injected into DOM APIs ` +
92
- 'such as eval().' ,
93
- Severity . MEDIUM_MAYBE , directiveName , Keyword . UNSAFE_EVAL ) ] ;
94
+ for ( const directive of directivesToCheck ) {
95
+ const values = parsedCsp . directives [ directive ] || [ ] ;
96
+ if ( values . includes ( Keyword . UNSAFE_EVAL ) ) {
97
+ violations . push ( new Finding (
98
+ Type . SCRIPT_UNSAFE_EVAL ,
99
+ `'unsafe-eval' allows the execution of code injected into DOM APIs ` +
100
+ 'such as eval().' ,
101
+ Severity . MEDIUM_MAYBE , directive , Keyword . UNSAFE_EVAL ) ) ;
102
+ }
94
103
}
95
104
96
- return [ ] ;
105
+ return violations ;
97
106
}
98
107
99
108
@@ -254,75 +263,80 @@ export function checkMissingDirectives(parsedCsp: Csp): Finding[] {
254
263
*/
255
264
export function checkScriptAllowlistBypass ( parsedCsp : Csp ) : Finding [ ] {
256
265
const violations : Finding [ ] = [ ] ;
257
- const effectiveScriptSrcDirective =
258
- parsedCsp . getEffectiveDirective ( Directive . SCRIPT_SRC ) ;
259
- const scriptSrcValues =
260
- parsedCsp . directives [ effectiveScriptSrcDirective ] || [ ] ;
261
- if ( scriptSrcValues . includes ( Keyword . NONE ) ) {
262
- return violations ;
263
- }
266
+ parsedCsp
267
+ . getEffectiveDirectives ( [ Directive . SCRIPT_SRC , Directive . SCRIPT_SRC_ELEM ] )
268
+ . forEach ( effectiveScriptSrcDirective => {
269
+ const scriptSrcValues =
270
+ parsedCsp . directives [ effectiveScriptSrcDirective ] || [ ] ;
271
+ if ( scriptSrcValues . includes ( Keyword . NONE ) ) {
272
+ return ;
273
+ }
264
274
265
- for ( const value of scriptSrcValues ) {
266
- if ( value === Keyword . SELF ) {
267
- violations . push ( new Finding (
268
- Type . SCRIPT_ALLOWLIST_BYPASS ,
269
- `'self' can be problematic if you host JSONP, AngularJS or user ` +
270
- 'uploaded files.' ,
271
- Severity . MEDIUM_MAYBE , effectiveScriptSrcDirective , value ) ) ;
272
- continue ;
273
- }
275
+ for ( const value of scriptSrcValues ) {
276
+ if ( value === Keyword . SELF ) {
277
+ violations . push ( new Finding (
278
+ Type . SCRIPT_ALLOWLIST_BYPASS ,
279
+ `'self' can be problematic if you host JSONP, AngularJS or user ` +
280
+ 'uploaded files.' ,
281
+ Severity . MEDIUM_MAYBE , effectiveScriptSrcDirective , value ) ) ;
282
+ continue ;
283
+ }
274
284
275
- // Ignore keywords, nonces and hashes (they start with a single quote).
276
- if ( value . startsWith ( '\'' ) ) {
277
- continue ;
278
- }
285
+ // Ignore keywords, nonces and hashes (they start with a single
286
+ // quote).
287
+ if ( value . startsWith ( '\'' ) ) {
288
+ continue ;
289
+ }
279
290
280
- // Ignore standalone schemes and things that don't look like URLs (no dot).
281
- if ( csp . isUrlScheme ( value ) || value . indexOf ( '.' ) === - 1 ) {
282
- continue ;
283
- }
291
+ // Ignore standalone schemes and things that don't look like URLs (no
292
+ // dot).
293
+ if ( csp . isUrlScheme ( value ) || value . indexOf ( '.' ) === - 1 ) {
294
+ continue ;
295
+ }
284
296
285
- const url = '//' + utils . getSchemeFreeUrl ( value ) ;
297
+ const url = '//' + utils . getSchemeFreeUrl ( value ) ;
286
298
287
- const angularBypass = utils . matchWildcardUrls ( url , angular . URLS ) ;
299
+ const angularBypass = utils . matchWildcardUrls ( url , angular . URLS ) ;
288
300
289
- let jsonpBypass = utils . matchWildcardUrls ( url , jsonp . URLS ) ;
301
+ let jsonpBypass = utils . matchWildcardUrls ( url , jsonp . URLS ) ;
290
302
291
- // Some JSONP bypasses only work in presence of unsafe-eval.
292
- if ( jsonpBypass ) {
293
- const evalRequired = jsonp . NEEDS_EVAL . includes ( jsonpBypass . hostname ) ;
294
- const evalPresent = scriptSrcValues . includes ( Keyword . UNSAFE_EVAL ) ;
295
- if ( evalRequired && ! evalPresent ) {
296
- jsonpBypass = null ;
297
- }
298
- }
303
+ // Some JSONP bypasses only work in presence of unsafe-eval.
304
+ if ( jsonpBypass ) {
305
+ const evalRequired =
306
+ jsonp . NEEDS_EVAL . includes ( jsonpBypass . hostname ) ;
307
+ const evalPresent = scriptSrcValues . includes ( Keyword . UNSAFE_EVAL ) ;
308
+ if ( evalRequired && ! evalPresent ) {
309
+ jsonpBypass = null ;
310
+ }
311
+ }
299
312
300
- if ( jsonpBypass || angularBypass ) {
301
- let bypassDomain = '' ;
302
- let bypassTxt = '' ;
303
- if ( jsonpBypass ) {
304
- bypassDomain = jsonpBypass . hostname ;
305
- bypassTxt = ' JSONP endpoints' ;
306
- }
307
- if ( angularBypass ) {
308
- bypassDomain = angularBypass . hostname ;
309
- bypassTxt += ( bypassTxt . trim ( ) === '' ) ? '' : ' and' ;
310
- bypassTxt += ' Angular libraries' ;
311
- }
313
+ if ( jsonpBypass || angularBypass ) {
314
+ let bypassDomain = '' ;
315
+ let bypassTxt = '' ;
316
+ if ( jsonpBypass ) {
317
+ bypassDomain = jsonpBypass . hostname ;
318
+ bypassTxt = ' JSONP endpoints' ;
319
+ }
320
+ if ( angularBypass ) {
321
+ bypassDomain = angularBypass . hostname ;
322
+ bypassTxt += ( bypassTxt . trim ( ) === '' ) ? '' : ' and' ;
323
+ bypassTxt += ' Angular libraries' ;
324
+ }
312
325
313
- violations . push ( new Finding (
314
- Type . SCRIPT_ALLOWLIST_BYPASS ,
315
- bypassDomain + ' is known to host' + bypassTxt +
316
- ' which allow to bypass this CSP.' ,
317
- Severity . HIGH , effectiveScriptSrcDirective , value ) ) ;
318
- } else {
319
- violations . push ( new Finding (
320
- Type . SCRIPT_ALLOWLIST_BYPASS ,
321
- `No bypass found; make sure that this URL doesn't serve JSONP ` +
322
- 'replies or Angular libraries.' ,
323
- Severity . MEDIUM_MAYBE , effectiveScriptSrcDirective , value ) ) ;
324
- }
325
- }
326
+ violations . push ( new Finding (
327
+ Type . SCRIPT_ALLOWLIST_BYPASS ,
328
+ bypassDomain + ' is known to host' + bypassTxt +
329
+ ' which allow to bypass this CSP.' ,
330
+ Severity . HIGH , effectiveScriptSrcDirective , value ) ) ;
331
+ } else {
332
+ violations . push ( new Finding (
333
+ Type . SCRIPT_ALLOWLIST_BYPASS ,
334
+ `No bypass found; make sure that this URL doesn't serve JSONP ` +
335
+ 'replies or Angular libraries.' ,
336
+ Severity . MEDIUM_MAYBE , effectiveScriptSrcDirective , value ) ) ;
337
+ }
338
+ }
339
+ } ) ;
326
340
327
341
return violations ;
328
342
}
0 commit comments