作者选择了 Girls Who Code以作为 Write for Donations计划的一部分获得捐款。
介绍
当您访问一个网站时,各种资源被用来加载和渲染它. 例如,当您访问https://www.digitalocean.com
,您的浏览器将直接从digitalocean.com
下载HTML和CSS。 然而,图像和其他资产从assets.digitalocean.com
下载,并从各自的领域下载分析脚本。
一些网站使用多种不同的服务,风格和脚本来加载和渲染其内容,您的浏览器将执行所有这些内容。浏览器不知道代码是否有害,因此开发人员有责任保护用户。
使用 CSP 标题,开发人员可以明确允许某些资源运行,同时阻止所有其他资源。因为大多数网站可以有超过 100 个资源,并且每个资源都必须被批准为其特定的资源类别,实施 CSP 可能是一项艰难的任务。
在本教程中,您将在基本的 Django 应用程序中实现 CSP. 您将定制 CSP 以允许某些域和内线资源运行。
前提条件
要完成本教程,您将需要:
如果你没有一个,你可以创建一个工作Django项目(版本3或更高),无论是在你的本地机器或DigitalOcean Droplet. 如果你没有一个,你可以创建一个教程, 如何在Ubuntu 20.04上安装Django并设置开发环境。
- 一个网页浏览器,如 Firefox或 Chrome和浏览器网络工具的理解。 有关使用浏览器网络工具的更多信息,请查看产品文档的 网络监视器在Firefox或 DevTools网络卡在Chrome. 有关浏览器开发工具的更多一般指导,请参阅指南: [什么是浏
步骤 1 - 创建一个演示视图
在此步骤中,您将更改应用程序如何处理视图,以便您可以添加 CSP 支持。
作为先决条件,你安装了Django并设置了一个样本项目.Django的默认视图太简单,无法展示CSP中间软件的所有功能,所以你将为本教程创建一个简单的HTML页面。
导航到您在前提条件中创建的项目文件夹:
1cd django-apps
在django-apps
目录中,创建你的虚拟环境,我们将它称为通用env
,但你应该使用一个对你和你的项目有意义的名称。
1virtualenv env
现在,用以下命令激活虚拟环境:
1. env/bin/activate
在虚拟环境中,使用nano
或您最喜欢的文本编辑器在项目文件夹中创建一个views.py
文件:
1nano django-apps/testsite/testsite/views.py
现在,您将添加一个基本视图,该视图将显示您下次创建的index.html
模板。
1[label django-apps/testsite/testsite/views.py]
2from django.shortcuts import render
3
4def index(request):
5 return render(request, "index.html")
保存并关闭文件,当你完成。
在新的模板
目录中创建一个index.html
模板:
1mkdir django-apps/testsite/testsite/templates
2nano django-apps/testsite/testsite/templates/index.html
请在 index.html 中添加以下内容:
1[label django-apps/testsite/testsite/templates/index.html]
2<!DOCTYPE html>
3<html>
4 <head>
5 <title>Hello world!</title>
6 <link rel="preconnect" href="https://fonts.googleapis.com" />
7 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
8 <link
9 href="https://fonts.googleapis.com/css2?family=Yellowtail&display=swap"
10 rel="stylesheet"
11 />
12 <style>
13 h1 {
14 font-family: "Yellowtail", cursive;
15 margin: 0.5em 0 0 0;
16 color: #0069ff;
17 font-size: 4em;
18 line-height: 0.6;
19 }
20
21 img {
22 border-radius: 100%;
23 border: 6px solid #0069ff;
24 }
25
26 .center {
27 text-align: center;
28 position: absolute;
29 top: 50vh;
30 left: 50vw;
31 transform: translate(-50%, -50%);
32 }
33 </style>
34 </head>
35 <body>
36 <div class="center">
37 <img src="https://html.sammy-codes.com/images/small-profile.jpeg" />
38 <h1>Hello, Sammy!</h1>
39 </div>
40 </body>
41</html>
我们创建的视图将显示这个简单的HTML页面. 它将显示文本 **Hello, Sammy!**以及Sammy the Shark的图像。
保存并关闭文件,当你完成。
要访问此视图,您需要更新 urls.py:
1nano django-apps/testsite/testsite/urls.py
导入views.py
文件并通过添加突出的行添加新路线:
1[label django-apps/testsite/testsite/urls.py]
2from django.contrib import admin
3from django.urls import path
4from . import views
5
6urlpatterns = [
7 path('admin/', admin.site.urls),
8 path('', views.index),
9]
您刚刚创建的新视图现在可在访问 /
(应用程序运行时) 时查看。
保存并关闭文件。
最后,您需要更新INSTALLED_APPS
,以在settings.py
中包含测试站点
:
1nano django-apps/testsite/testsite/settings.py
1[label django-apps/testsite/testsite/settings.py]
2# ...
3INSTALLED_APPS = [
4 'django.contrib.admin',
5 'django.contrib.auth',
6 'django.contrib.contenttypes',
7 'django.contrib.sessions',
8 'django.contrib.messages',
9 'django.contrib.staticfiles',
10 'testsite',
11]
12# ...
在这里,您将testsite
添加到settings.py
中的应用程序列表中,以便Django可以对您的项目的结构做出一些假设,在这种情况下,它将假设模板
文件夹包含您可以使用的Django模板来渲染视图。
从项目的 root 目录(‘testsite’)启动 Django 开发服务器,使用以下命令,用您自己的服务器的 IP 地址代替 your-server-ip
。
1cd ~/django-apps/testsite
2python manage.py runserver your-server-ip:8000
打开一个浏览器,然后访问your-server-ip:8000
。
此时,页面显示了Sammy the Shark的个人资料图像. 图像底下有蓝色脚本的文本 Hello, Sammy!。
要停止 Django 开发服务器,点击CONTROL-C
。
在此步骤中,您创建了一个基本的视图,作为您的Django项目的首页,然后将CSP支持添加到您的应用程序中。
第2步:安装CSP中间件
在此步骤中,您将安装并实施 CSP 中间件,以便您可以添加 CSP 标题并在您的视图中使用 CSP 功能。中间件会为任何 Django 请求或响应添加额外的功能。
首先,您将使用pip
,Python的包管理器,在Django项目中安装Mozilla的CSP Middleware。 使用以下命令从PyPi,Python Package Index中安装所需的包。 要运行命令,您可以使用CONTROL-C
停止Django开发服务器,或者在终端中打开一个新标签:
1pip install django-csp
接下来,将中间件添加到您的 Django 项目的设置中。
1nano testsite/testsite/settings.py
安装了‘django-csp’,您现在可以在‘settings.py’中添加中间软件,这将为您的回复添加CSP标题。 将下列行添加到‘MIDDLEWARE’配置阵列:
1[label testsite/testsite/settings.py]
2MIDDLEWARE = [
3 'csp.middleware.CSPMiddleware',
4 'django.middleware.security.SecurityMiddleware',
5 'django.contrib.sessions.middleware.SessionMiddleware',
6 'django.middleware.common.CommonMiddleware',
7 'django.middleware.csrf.CsrfViewMiddleware',
8 'django.contrib.auth.middleware.AuthenticationMiddleware',
9 'django.contrib.messages.middleware.MessageMiddleware',
10 'django.middleware.clickjacking.XFrameOptionsMiddleware',
11]
完成后保存并关闭文件. 您的 Django 项目现在支持 CSP. 在下一步,您将开始添加 CSP 标题。
步骤 3 – 实施 CSP 标题
现在,您的项目支持 CSP,它已经准备好加强安全性。为了实现这一目标,您将配置该项目以将 CSP 标题添加到您的响应中。 CSP 标题是告诉浏览器在遇到特定类型的内容时如何行为的内容。
使用 nano 或您最喜欢的文本编辑器,打开 settings.py
:
1nano testsite/testsite/settings.py
定义下列变量在文件中的任何地方:
1[label testsite/testsite/settings.py]
2# Content Security Policy
3
4CSP_IMG_SRC = ("'self'")
5
6CSP_STYLE_SRC = ("'self'")
7
8CSP_SCRIPT_SRC = ("'self'")
这些规则是您的 CSP 的锅炉板. 这些行表示分别允许图像、风格表和脚本的来源。 目前,它们都包含自我
字符串,这意味着只允许来自您自己的域的资源。
保存并关闭文件,当你完成。
使用以下命令运行您的 Django 项目:
1python manage.py runserver your-server-ip:8000
当你访问‘your-server-ip:8000’,你会看到该网站被打破:
正如预期的那样,图像不会出现,文本会以默认的样式(黑色)出现。这意味着CSP标题正在被执行,我们的页面现在更安全。 因为您之前创建的视图是引用不是您的域的样式表和图像,浏览器会阻止它们。
您的项目现在有一个工作 CSP,该 CSP 告诉浏览器阻止非来自您的域的资源. 接下来,您将修改 CSP 以允许特定资源,从而修复主页的缺失图像和风格。
步骤 4 – 修改 CSP 以允许外部资源
现在你有一个基本的 CSP,你会根据你在网站上使用的内容进行修改。 例如,使用 Adobe 字体和嵌入式 YouTube 视频的网站需要允许这些资源。
您可以使用浏览器的 developer tools来做到这一点。在Inspect Element**
中打开Network Monitor**
,刷新页面并查看被阻止的资源:
网络日志显示,两个资源正在被CSP阻止:一个来自fonts.googleapis.com
的风格表和一个来自html.sammy-codes.com
的图像。
若要允许来自外部域的资源,请将域添加到匹配文件类型的 CSP 部分,因此,若要允许从 html.sammy-codes.com’ 获取图像,则将
html.sammy-codes.com’ 添加到 CSP_STYLE_SRC
。
打开「settings.py」并将下列内容添加到「CSP_STYLE_SRC」变量中:
1[label testsite/testsite/settings.py]
2CSP_IMG_SRC = ("'self'", 'https://html.sammy-codes.com')
现在,而不是只允许来自您的域的图像,该网站还允许从html.sammy-codes.com的图像。
索引视图使用 Google 字体. Google 向您的网站提供字体(从 https://fonts.gstatic.com
)和风格表来应用它们(从 https://fonts.googleapis.com
)。 若要允许字体加载,请将下列内容添加到您的 CSP:
1[label testsite/testsite/settings.py]
2CSP_STYLE_SRC = ("'self'", 'https://fonts.googleapis.com')
3
4CSP_FONT_SRC = ("'self'", 'https://fonts.gstatic.com/')
类似于允许来自html.sammy-codes.com
的图像,您还将允许来自fonts.googleapis.com
的风格表和来自fonts.gstatic.com
的字体。
保存并关闭文件。
<$>[警告]
警告: 类似于自己
,还有其他关键字,如不安全的线上
,不安全的错误
或不安全的密码
,可以在CSP中使用。
有关更多信息,请参阅不安全内线脚本
的Mozilla产品文档。
现在,Google 字体将被允许在您的网站上加载风格和字体,并且html.sammy-codes.com
将被允许加载图像. 但是,当您访问您的服务器上的页面时,您可能会注意到现在只有图像正在加载。
步骤 5 – 使用内线脚本和风格
此时,您已更改 CSP 以允许外部资源,但内部资源,如您的视图中的样式和脚本,仍然不允许。
有两种方式允许内线脚本和风格:内线脚本和哈希. 如果您发现您经常修改内线脚本和风格,请使用内线脚本和风格以避免频繁更改您的 CSP。
使用nonce
允许内线脚本
首先,你会使用 nonce 方法. 一个 nonce 是一个随机生成的代币,每个请求都是独一无二的. 如果两个人访问你的网站,他们每个人都会得到一个独特的 nonce
嵌入到你批准的内线脚本和风格中。
要将 nonce 支持添加到您的项目中,您将在settings.py
中更新您的 CSP。
1nano testsite/testsite/settings.py
在CSP_INCLUDE_NONCE_IN
中添加script-src
到settings.py
文件中。
定义CSP_INCLUDE_NONCE_IN
在文件中的任何地方,并添加script-src
:
1[label testsite/testsite/settings.py]
2# Content Security Policy
3
4CSP_INCLUDE_NONCE_IN = ['script-src']
CSP_INCLUDE_NONCE_IN
表示您可以添加nonce
属性到哪些内行脚本。
保存并关闭文件。
现在允许在视图模板中添加nonce
属性时生成内线脚本。
打开index.html
来编辑:
1nano testsite/testsite/templates/index.html
在HTML的<head>
中添加下列片段:
1[label testsite/testsite/templates/index.html]
2<script>
3 console.log("Hello from the console!");
4</script>
此片段会打印到浏览器控制台的Hello from the console!
但是,由于您的项目有一个 CSP,它只允许内线脚本,如果它们有nonce
,这个脚本不会运行,而是会产生错误。
您可以在浏览器的控制台中看到此错误,当您更新页面时:
图像会加载,因为您在上一步允许了外部资源。正如预期的那样,当前的样式是默认的,因为您尚未允许内线样式。
您可以这样做,将 `nonce="{request.csp_nonce}}" 添加到这个脚本中作为属性。
1[label testsite/testsite/templates/index.html]
2<script nonce="{{request.csp_nonce}}">
3 console.log("Hello from the console!");
4</script>
保存并关闭你的文件,当你完成。
如果您更新页面,脚本现在将执行:
当您查看 ** 检查元素** 时,您会注意到该属性没有值:
该值不会出现在安全原因. 浏览器已经处理了该值. 它是隐藏的,所以任何可以访问 DOM 的脚本都无法访问它,并将其应用到其他某些脚本。
请注意,每次更新页面时,nonce
值都会发生变化,这是因为我们项目中的CSP中间件为每个请求生成一个新的nonce
。
这些nonce
值在浏览器收到响应时附加到 CSP 标题中:
浏览器向您的网站提出的每个请求都将为该脚本具有独特的nonce
值,因为nonce
在CSP标题中提供,这意味着Django服务器批准该特定脚本运行。
例如,您也可以将其应用到风格,通过更新CSP_INCLUDE_NONCE_IN
,以允许style-src
。
使用哈希允许内线风格
允许线性脚本和风格的另一个方法是使用哈希,一个哈希是给定线性资源的唯一标识符。
举个例子,这是我们模板中的内线风格:
1[label testsite/testsite/templates/index.html]
2<style>
3 h1 {
4 font-family: "Yellowtail", cursive;
5 margin: 0.5em 0 0 0;
6 color: #0069ff;
7 font-size: 4em;
8 line-height: 0.6;
9 }
10
11 img {
12 border-radius: 100%;
13 border: 6px solid #0069ff;
14 }
15
16 .center {
17 text-align: center;
18 position: absolute;
19 top: 50vh;
20 left: 50vw;
21 transform: translate(-50%, -50%);
22 }
23</style>
当您在浏览器中查看网站时,图像会成功加载,但没有应用字体和风格:
在浏览器的控制台中,你会发现一个错误,即一个内线风格违反CSP(可能有其他错误,但寻找有关内线风格的错误)。
这个错误是因为我们的 CSP 未批准该风格,但请注意,该错误提供了批准该风格片段所需的哈希,此哈希是该特定风格片段独一无二的,没有其他片段将有相同的哈希。当此哈希放在 CSP 中时,每次加载此特定风格时,它将被批准。
现在,您将通过在settings.py
中添加到CSP_STYLE_SRC
来应用该哈希,如下:
1nano testsite/testsite/settings.py
1[label testsite/testsite/settings.py]
2CSP_STYLE_SRC = ("'self' 'sha256-r5bInLZB0y6ZxHFpmz7cjyYrndjwCeDLDu/1KeMikHA='", 'https://fonts.googleapis.com')
将sha256-...
哈希添加到CSP_STYLE_SRC
列表将允许浏览器在没有任何错误的情况下加载风格表。
保存和关闭文件。
现在,在浏览器中重新加载网站,字体和风格应该成功加载:
在此步骤中,您使用了两种不同的方法,即Nonces和Hashes,以允许Inline风格和脚本。
但是,有一个重要的问题要解决。 CSP 很难维护,特别是对于大型网站,您可能需要一种方法来跟踪 CSP 阻止资源时,以便您可以确定它是否是一个恶意资源或只是您网站的破坏部分。
步骤 6 – 使用 Sentry 报告违规行为(可选)
鉴于 CSP 往往是多么严格,所以知道何时阻止内容是好事,特别是因为阻止内容可能意味着您的网站上的某些功能不会起作用。
作为先决条件,您已经在Sentry注册了帐户,现在您将创建一个项目。
在Sentry仪表板的左上角,单击 项目选项卡:
在右上角,点击创建项目
按钮:
您将看到一系列标志,标题表示 **选择平台。**选择 Django:
然后,在底部,命名您的项目(对于这个示例,我们将使用sammys-tutorial
),然后点击创建项目
按钮:
Sentry 会给你一个代码片段来添加到你的 settings.py
文件. 保存此片段来添加在稍后一步。
在您的终端上,安装Sentry SDK:
1pip install --upgrade sentry-sdk
打开settings.py
如下:
1nano testsite/testsite/settings.py
将下列内容添加到文件的末尾,并确保将SENTRY_DSN
替换为仪表板中的值:
1[label testsite/testsite/settings.py]
2import sentry_sdk
3from sentry_sdk.integrations.django import DjangoIntegration
4
5sentry_sdk.init(
6 dsn="SENTRY_DSN",
7 integrations=[DjangoIntegration()],
8
9 # Set traces_sample_rate to 1.0 to capture 100%
10 # of transactions for performance monitoring.
11 # We recommend adjusting this value in production.
12 traces_sample_rate=1.0,
13
14 # If you wish to associate users to errors (assuming you are using
15 # django.contrib.auth) you may enable sending PII data.
16 send_default_pii=True
17)
此代码由Sentry提供,以便它可以记录在您的应用程序中发生的任何错误。 这是Sentry的默认配置,并将Sentry初始化用于在我们的服务器上记录问题。 技术上,您不需要在您的服务器上初始化Sentry,因为CSP违反,但在罕见的情况下有某些问题 rendering nonces或哈希,这些错误将被登录到Sentry。
保存并关闭文件。
接下来,回到您的项目的仪表板,然后点击齿轮图标进入 设置:
点击安全标题
标签:
复制报告-uri
:
将其添加到您的 CSP 如下:
1[label testsite/testsite/settings.py]
2# Content Security Policy
3
4CSP_REPORT_URI = "your-report-uri"
请确保将your-report-uri
替换为您从仪表板中复制的值。
保存并关闭您的文件. 现在,当CSP执法导致违规时,Sentry会将其登录到此URI中. 您可以尝试通过从CSP中删除域或哈希,或从您先前添加的脚本中删除nonce
。 在浏览器中加载页面,您将在Sentry的 Issues页面中看到错误:
If you find you are overwhelmed by the number of logs, you can also define
CSP_REPORT_PERCENTAGE
in settings.py
to only send a percentage of the logs to Sentry.
1[label testsite/testsite/settings.py]
2# Content Security Policy
3# Send 10% of the logs to Sentry
4CSP_REPORT_PERCENTAGE = 0.1
现在,每当发生CSP违规时,您将收到通知,并可以在Sentry中查看错误。
结论
在本文中,您通过内容安全策略保护了 Django 应用程序. 您更新了您的策略以允许外部资源,并使用 nonces 和 hashes 来允许内线 scropts 和风格. 您还将其配置为向 Sentry 发送违规行为。