UNPKG

csp_evaluator

Version:

Evaluate Content Security Policies for a wide range of bypasses and weaknesses

148 lines (131 loc) 4.76 kB
/** * @fileoverview Utils for CSP evaluator. * @author lwe@google.com (Lukas Weichselbaum) * * @license * Copyright 2016 Google Inc. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as csp from './csp'; /** * Removes scheme from url. * @param url Url. * @return url without scheme. */ export function getSchemeFreeUrl(url: string): string { url = url.replace(/^\w[+\w.-]*:\/\//i, ''); // Remove URL scheme. url = url.replace(/^\/\//, ''); // Remove protocol agnostic "//" return url; } /** * Get the hostname from the given url string in a way that supports schemeless * URLs and wildcards (aka `*`) in hostnames */ export function getHostname(url: string): string { const hostname = new URL( 'https://' + getSchemeFreeUrl(url) .replace(':*', '') // Remove wildcard port .replace('*', 'wildcard_placeholder')) .hostname.replace('wildcard_placeholder', '*'); // Some browsers strip the brackets from IPv6 addresses when you access the // hostname. If the scheme free url starts with something that vaguely looks // like an IPv6 address and our parsed hostname doesn't have the brackets, // then we add them back to work around this const ipv6Regex = /^\[[\d:]+\]/; if (getSchemeFreeUrl(url).match(ipv6Regex) && !hostname.match(ipv6Regex)) { return '[' + hostname + ']'; } return hostname; } function setScheme(u: string): string { if (u.startsWith('//')) { return u.replace('//', 'https://'); } return u; } /** * Searches for allowlisted CSP origin (URL with wildcards) in list of urls. * @param cspUrlString The allowlisted CSP origin. Can contain domain and * path wildcards. * @param listOfUrlStrings List of urls to search in. * @return First match found in url list, null otherwise. */ export function matchWildcardUrls( cspUrlString: string, listOfUrlStrings: string[]): URL|null { // non-Chromium browsers don't support wildcards in domain names. We work // around this by replacing the wildcard with `wildcard_placeholder` before // parsing the domain and using that as a magic string. This magic string is // encapsulated in this function such that callers of this function do not // have to worry about this detail. const cspUrl = new URL(setScheme(cspUrlString .replace(':*', '') // Remove wildcard port .replace('*', 'wildcard_placeholder'))); const listOfUrls = listOfUrlStrings.map(u => new URL(setScheme(u))); const host = cspUrl.hostname.toLowerCase(); const hostHasWildcard = host.startsWith('wildcard_placeholder.'); const wildcardFreeHost = host.replace(/^\wildcard_placeholder/i, ''); const path = cspUrl.pathname; const hasPath = path !== '/'; for (const url of listOfUrls) { const domain = url.hostname; if (!domain.endsWith(wildcardFreeHost)) { // Domains don't match. continue; } // If the host has no subdomain wildcard and doesn't match, continue. if (!hostHasWildcard && host !== domain) { continue; } // If the allowlisted url has a path, check if one of the url paths // match. if (hasPath) { // https://www.w3.org/TR/CSP2/#source-list-path-patching if (path.endsWith('/')) { if (!url.pathname.startsWith(path)) { continue; } } else { if (url.pathname !== path) { // Path doesn't match. continue; } } } // We found a match. return url; } // No match was found. return null; } /** * Applies a check to all directive values of a csp. * @param parsedCsp Parsed CSP. * @param check The check function that * should get applied on directive values. */ export function applyCheckFunktionToDirectives( parsedCsp: csp.Csp, check: (directive: string, directiveValues: string[]) => void, ) { const directiveNames = Object.keys(parsedCsp.directives); for (const directive of directiveNames) { const directiveValues = parsedCsp.directives[directive]; if (directiveValues) { check(directive, directiveValues); } } }