HTTP响应头安全问题处理记录

为了提升博客的安全性,用https://securityheaders.com/对博客的HTTP响应头进行了检测,不查不知道,一查吓一跳,按照SecurityHeaders的评分标准只获得了D级别(最低F,最高A+),因此决定对博客的HTTP响应头进行一次规范化处理并记录全过程。

Content-Security-Policy(CSP)


内容安全策略(Content Security Policy,简称CSP)是一种以可信白名单作机制,来限制网站是否可以包含某些来源内容,缓解广泛的内容注入漏洞,比如XSS、点击劫持、SQL注入等等,CSP通过定义运行加载脚本的位置和内容防止恶意代码的加载。简单来说,就是我们能够规定,我们的网站只接受我们指定的请求资源,默认配置下不允许执行内联代码。

响应头

  • Content-Security-Policy
  • X-Content-Security-Policy(旧版)
  • X-Webkit-CSP

使用方法

CSP可以由两种方式指定:HTTP HeaderHTML,如果两种方式同时定义了CSP,则会优先采用HTTP Header的。

通过定义在HTTP Header中使用

1
Content-Security-Policy: default-src 'self'

通过定义在meta标签中使用

1
<meta http-equiv="content-security-policy" content="default-src 'self'">
  • 策略是指定义CSP的语法内容,一个策略由一系列策略指令组成,每个策略指令都描述了一个针对某个特定类型资源以及生效范围的策略。
  • 定义后,凡是不符合CSP策略的外部资源都会被阻止加载。

CSP语法

每一条策略都由指令与指令值组成

1
Content-Security-Policy:指令1 指令值1

多个指令之间使用英文分号

1
Content-Security-Policy:指令1 指令值1;指令2 指令值2;指令3 指令值3

如果一个指令中有多个指令值,则多个指令值之间用英文空格分割

1
Content-Security-Policy:指令a 指令值a1 指令值a2

CSP指令

指令 说明
default-src 定义针对所有类型(js/image/css/font/ajax/iframe/多媒体等)资源的默认加载策略,如果某类型资源没有单独定义策略,就使用默认的。
script-src 定义针对JavaScript的加载策略。
style-src 定义针对样式的加载策略。
img-src 定义针对图片的加载策略。
font-src 定义针对字体的加载策略。
media-src 定义针对多媒体的加载策略,例如音频标签<audio>和视频标签<video>
object-src 定义针对插件的加载策略,例如<object><embed><applet>等标签引入的Flash等插件
child-src 定义针对框架的加载策略,例如: ,。
connect-src 定义针对Ajax/WebSocket等请求的加载策略。不允许的情况下,浏览器会模拟一个状态为400的响应。
sandbox 对请求的资源启用sandbox(类似于iframe的sandbox属性)。
report-uri 告诉浏览器如果请求的资源不被策略允许时,往哪个地址提交日志信息。如果想让浏览器只汇报日志,不阻止任何内容,可以改用Content-Security-Policy-Report-Only头
form-action 定义针对提交的form到特定来源的加载策略。
referrer 定义针对referrer的加载策略。
reflected-xss 定义针对XSS过滤器使用策略。

CSP指令值

指令值 说明
* 允许加载任何内容
'none' 不允许加载任何内容
'self' 允许加载相同源的内容
www.koenli.com 允许加载指定域名的资源
*.koenli.com 允许加载koenli.com任何子域名的资源
https://koenli.com 允许加载koenli.com的HTTPS资源
https: 允许加载HTTPS资源
data: 允许加载data:协议,例如:base64编码的图片
'unsafe-inline' 允许加载inline资源,例如style属性、onclick、inline js、inline css等
'unsafe-eval' 允许加载动态js代码,例如 eval()

CSP例子

  1. 所有内容均来自网站的自己的域
1
Content-Security-Policy: default-src 'self'
  1. 所有内容都来自网站自己的域,还有其他子域(假如网站的地址是:koenli.com)
1
Content-Security-Policy: default-src 'self' *.koenli.com
  1. 网站接受任意域的图像,指定域(koenli.com)的音频、视频和多个指定域(koenlia.com、koenlib.com)的脚本
1
Content-Security-Policy: default-src 'self';img-src *;media-src koenli.com;script-src koenlia.com koenlib.com

Nginx配置Content-Security-Policy

在Nginx中可以通过在适当的位置(例如httpserverlocation块)添加add_header指令来设置Content-Security-Policy响应头。以下是一个配置示例

1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
server_name example.com;

add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trustedscripts.example.com; img-src 'self' https://trustedimages.example.com;";

location / {
root /var/www/html;
index index.html;
}
}

X-Frame-Options


X-Frame-Options响应头用来指示浏览器是否应该被允许在<frame><iframe>或者<object>中显示页面内容。如果这个响应头缺失或者配置不正确,可能会导致点击劫持攻击,即攻击者可以通过嵌入一个看似安全的页面来欺骗用户。

响应头

  • X-Frame-Options

语法

1
2
3
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: "ALLOW-FROM https://example.com/"

说明
在Nginx中""引号是必须要写的哦!

响应头值 说明
DENY 不允许被嵌入到任何<iframe><frame>中。
SAMEORIGIN 只能被本站页面嵌入到<iframe><frame>中。
ALLOW-FROM uri 只能被嵌入到指定域名的<iframe><frame>中。

Nginx配置X-Frame-Options

在Nginx中可以通过在适当的位置(例如httpserverlocation块)添加add_header指令来设置X-Frame-Options响应头。以下是一个配置示例

1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
server_name example.com;

add_header X-Frame-Options "SAMEORIGIN";

location / {
root /var/www/html;
index index.html;
}
}

X-Content-Type-Options


X-Content-Type-Options用于控制浏览器是否应该尝试MIME类型嗅探。如果启用了X-Content-Type-Options,浏览器将遵循服务器提供的MIME类型,用于防止浏览器执行MIME类型错误的响应体(response body)。

如果在HTTP响应头中指定的Content-Type与实际响应体返回的MIME类型不一致,这种情况下浏览器可能会忽略响应头中指定的Content-Type,执行实际响应体的MIME类型,造成安全风险,而设置X-Content-Type-Options就是为了避免这种类型的安全风险。

响应头

  • X-Content-Type-Options

语法

1
X-Content-Type-Options: nosniff
响应头值 说明
nosniff 当设置为nosniff时,浏览器将不会尝试根据内容进行MIME类型检测。即使服务器返回的内容与请求的MIME类型不匹配,浏览器也会按照服务器返回的MIME类型来处理响应

Nginx配置X-Frame-Options

在Nginx中可以通过在适当的位置(例如httpserverlocation块)添加add_header指令来设置X-Content-Type-Options响应头。以下是一个配置示例

1
2
3
4
5
http {
...
add_header X-Content-Type-Options "nosniff";
...
}

Referrer-Policy


Referrer-Policy主要用于控制浏览器在发送跨域请求时如何处理HTTP Referrer信息

当用户在浏览器上点击一个链接时,会产生一个HTTP请求,用于获取新的页面内容,而在该请求的报头中,会包含一个Referer字段,用以指示该请求是从哪个页面跳转来的,常被用于分析用户来源等信息。但是也有成为用户的一个不安全因素,比如有些网站直接将SessionID或Token放在地址栏里传递,会原样不动地当作Referrer报头的内容传递给第三方网站。

所以就有了Referrer-Policy,用于过滤Referer报头内容,目前是一个候选标准,不过已经有部分浏览器支持该标准。具体的可查看这里

使用场景

浏览器向服务器请求资源的时候,Referer字段的逻辑是这样的,用户在地址栏输入网址,或者选中浏览器书签,就不发送Referer字段。主要是以下三种场景,会发送Referer字段

  • 用户点击网页上的链接
  • 用户发送表单
  • 网页加载静态资源,比图加载图片、脚本、样式

响应头

  • Referrer-Policy

指令值

目前包含了以下几种指令值

1
2
3
4
5
6
7
8
9
10
11
enum ReferrerPolicy {
"",
"no-referrer",
"no-referrer-when-downgrade",
"same-origin",
"origin",
"strict-origin",
"origin-when-cross-origin",
"strict-origin-when-cross-origin",
"unsafe-url"
};
  • 空字符串

若设为空串则默认按照浏览器的机制设置Referer字段的内容,默认情况下和no-referrer-when-downgrade设置的一样。

  • no-referrer

不发送Referer字段

  • no-referrer-when-downgrade (默认值)

如果从HTTPS网址链接到HTTP网址,不发送Referer字段,其他情况发送(包括HTTP网址链接到HTTP网址),这是浏览器的默认行为。

原地址 跳转地址 Referer
https://example.com?token=123 https://example.com/path https://example.com?token=123
http://example.com?token=123 http://example.com/path http://example.com?token=123
https://example.com http://example.com/path 无(协议降级)
http://example.com?token=123 https://example.com/path http://example.com?token=123
  • same-origin

链接到同源网址(协议、域名、端口都相同)时才发送,否则不发送。

说明
https://koenli.com链接到http://koenli.com也属于跨域。

原地址 跳转地址 Referer
https://example.com?token=123 https://example.com/path https://example.com?token=123
http://example.com?token=123 http://example.com/path http://example.com?token=123
https://example.com http://example.com/path 无(协议不同)
http://example.com?token=123 https://example.com/path 无(协议不同)
http://example.com?token=123 http://example.com:88/path 无(端口不同)
https://example.com?token=123 https://caixw.io 无(域名不同)
  • origin

Referer字段一律只发送源信息(协议+域名+端口),不管是否跨域。

原地址 跳转地址 Referer
https://example.com?token=123 https://example.com/path https://example.com
http://example.com?token=123 https://example.com/path http://example.com
https://example.com?token=123 https://caixw.io https://example.com
  • strict-origin

如果从HTTPS网址链接到HTTP网址,不发送Referer字段,其他情况只发送源信息(协议+域名+端口)。

说明
与origin类似,但是不能降级。

原地址 跳转地址 Referer
https://example.com?token=123 https://example.com/path https://example.com
http://example.com?token=123 https://example.com/path http://example.com
http://example.com?token=123 http://caixw.io http://example.com
https://example.com?token=123 http://caixw.io
  • origin-when-cross-origin

当发请求给同源网站时,浏览器会在Referer字段中显示完整的URL信息,发给非同源网站时,则只显示源信息(协议+域名+端口)

原地址 跳转地址 Referer
https://example.com?token=123 https://example.com/path https://example.com?token=123
http://example.com?token=123 https://example.com/path http://example.com
http://example.com?token=123 http://caixw.io http://example.com
  • strict-origin-when-cross-origin

同源时,发送完整的Referer字段;跨域时,如果HTTPS网址链接到HTTP网址,不发送Referer字段,否则发送源信息(协议+域名+端口)。

说明
与origin-when-cross-origin类似,但不能降级。

原地址 跳转地址 Referer
https://example.com?token=123 https://example.com/path https://example.com?token=123
https://example.com?token=123 https://caixw.io https://example.com
https://example.com?token=123 http://example.com/path
https://example.com?token=123 http://example.com/
  • unsafe-url

浏览器总是会将完整的URL信息显示在Referer字段中,无论请求发给任何网站。

Nginx配置Referrer-Policy

在Nginx中可以通过在适当的位置(例如httpserverlocation块)添加add_header指令来设置Referrer-Policy响应头。以下是一个配置示例

1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
server_name example.com;

location / {
root /var/www/html;
index index.html index.htm;

add_header Referrer-Policy "strict-origin-when-cross-origin";
}
}

Permissions-Policy


Permissions-Policy响应标头提供了一种可以在本页面或包含的iframe上启用或禁止浏览器特性的机制。它可以帮助网站防止恶意活动、提高隐私保护、减少跨站点攻击等。该头部允许指定要允许或禁止的功能类型和来源,包括网络检索、摄像头访问、麦克风访问、弹出窗口、全屏模式等。

语法

1
Permissions-Policy: <directive> <allowlist>

<allowlist>
一个来源列表,在括号中包含的以下一个或多个值,并用空格分隔:

  • *:允许在当前文档和所有包含的内容(比如iframes)中使用本特性。
  • 'self':允许在当前文档中使用本特性,但在包含的内容(比如iframes)仍使用原值。
  • 'src':(只在iframe中允许) 只要在src中的URL和加载iframe用的URL相同,则本特性在iframe中允许,
  • 'none':从最上层到包含的内容都禁止本特性。
  • <origin(s)>: 在特定的源中允许,源URL以空格分割。
    *none值只允许单独使用,而selfsrc值可以与多个源一起使用。

所有的特性都有一个如下的默认的allowlist

  • *: 本特性默认在最上层和包含的内容中(iframes)允许。
  • 'self': 本特性默认在最上层允许,而包含的内容中(iframes)使用源地址相同设定。也就是说本特性在iframe中不允许跨域访问。
  • 'none': 本特性默认在最上层和包含的内容中(iframes)都禁止。

指令

指令 说明
autoplay 控制是否允许当前文档自动播放媒体。
camera 控制是否允许当前文档使用视频输入设备。
document-domain 控制是否允许当前文档设置 document.domain。
encrypted-media 控制是否允许当前文档使用 Encrypted Media Extension (en-US) API(EME)。
fullscreen 控制是否允许当前文档使用 Element.requestFullScreen()。
geolocation 控制是否允许当前文档使用 Geolocation 接口。
microphone 控制是否允许当前文档使用音频输入设备。
midi 控制是否允许当前文档使用 Web MIDI API (en-US)。
payment 控制是否允许当前文档使用 Payment Request API。
vr / xr 控制是否允许当前文档使用 WebVR API。

示例

例如公司想要在应用中禁用震动和定位API,则可以在返回的response中传递以下定义权限策略的HTTP的标头信息:

1
Permissions-Policy: vibrate 'none'; geolocation 'none'

Nginx配置Permissions-Policy

在Nginx中可以通过在适当的位置(例如httpserverlocation块)添加add_header指令来设置Permissions-Policy响应头。以下是一个配置示例

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name example.com;

location / {
root /var/www/html;
index index.html index.htm;

# 添加 Permissions-Policy 头,限制了地理位置、麦克风和摄像头只能由同源网页使用。
add_header Permissions-Policy "geolocation=self, microphone=self, camera=self";
}
}

结语


经过一系列的优化配置,再次使用https://securityheaders.com/进行检测后已经达到了A级别(最低F,最高A+)。当然HTTP响应头还有很多,并不只有这里提到的,更多的HTTP响应头可以参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP

参考文档