Skip to content

Commit e4cbbd2

Browse files
CSP Evaluator Teamddworken
CSP Evaluator Team
authored andcommitted
No public description
PiperOrigin-RevId: 644359009
1 parent 5833e74 commit e4cbbd2

File tree

5 files changed

+349
-179
lines changed

5 files changed

+349
-179
lines changed

checks/security_checks.ts

Lines changed: 97 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import * as utils from '../utils';
3030
* A list of CSP directives that can allow XSS vulnerabilities if they fail
3131
* validation.
3232
*/
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+
];
3537

3638
/**
3739
* 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:'];
5456
* is present).
5557
*/
5658
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+
]);
6063

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+
}
6873
}
6974

70-
return [];
75+
return violations;
7176
}
7277

7378

@@ -81,19 +86,23 @@ export function checkScriptUnsafeInline(effectiveCsp: Csp): Finding[] {
8186
* @param parsedCsp Parsed CSP.
8287
*/
8388
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+
]);
8693

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+
}
94103
}
95104

96-
return [];
105+
return violations;
97106
}
98107

99108

@@ -254,75 +263,80 @@ export function checkMissingDirectives(parsedCsp: Csp): Finding[] {
254263
*/
255264
export function checkScriptAllowlistBypass(parsedCsp: Csp): Finding[] {
256265
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+
}
264274

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+
}
274284

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+
}
279290

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+
}
284296

285-
const url = '//' + utils.getSchemeFreeUrl(value);
297+
const url = '//' + utils.getSchemeFreeUrl(value);
286298

287-
const angularBypass = utils.matchWildcardUrls(url, angular.URLS);
299+
const angularBypass = utils.matchWildcardUrls(url, angular.URLS);
288300

289-
let jsonpBypass = utils.matchWildcardUrls(url, jsonp.URLS);
301+
let jsonpBypass = utils.matchWildcardUrls(url, jsonp.URLS);
290302

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+
}
299312

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+
}
312325

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+
});
326340

327341
return violations;
328342
}

0 commit comments

Comments
 (0)