~~嗯?这家伙鸽了多久了?~~

又是一次知道的已经做好了,不知道的对着他耳朵唠叨也不会去做的系列节目。(嗯?) 如果不想听咱啰嗦的话, 直接去看 Mozilla Infosec 的相同主题的内容就好啦。

HTTPS 和 TLS 到底是什么关系?

HTTPS 其实就是包在 SSL/TLS 里的 HTTP 啦,考虑到 SSL 3.0 早在 2015 年就废弃了,现在基本上就只提 TLS 了。(虽然 TLS 1.0 和 1.1 也在 2020 年被废弃了就是。)

因为 HTTPS 能够加密所交换的数据、在传输的过程中保证数据的完整性,以及证明用户是在与目标网站进行通信。所以很多组织都在呼吁让整个互联网的流量都是加密的。 以及各大浏览器也在逐渐的将只支持 HTTP 的网站列入“不安全”的范围。(虽然也有人对这么做有没有作用存疑。)

取得一份证书

关于 TLS 的实现原理不是咱的重点(其实是咱也不是很懂,汝也许可以看看 IETF 的 RFC, 如果真的有必要的话。 汝现在需要知道的最重要的一点就是,要使用 HTTPS 的话,网站管理员要先生成一份私钥,通过这份私钥生成一份包含自己的名字和要申请证书的域名等信息的证书签名请求(CSR),再从大家都信任的某些地方(就是 CA/数字证书认证机构 啦)取得一份证书才能继续接下来的步骤。

(好吧 Certbot 或者 acme.sh 之类用多了都快忘了 CSR 这回事了,感谢依云在下面评论的指正。)

有不止一家企业和组织营运的 CA 负责签发证书,例如历史悠久的 Entrust 和 DigiCert ,和数个公司或组织为了普及 HTTPS 创办的 Let's Encrypt 等等。

如果汝还没有预算购买证书的话,可以先从 Let's Encrypt 开始

不过 Let's Encrypt 的证书有效期只有三个月,所以如果汝对自己的服务没有完整的命令行访问权限(例如比较常见的虚拟共享主机)的话,可能会稍微麻烦一些。

至于怎么申请证书嘛, Let's Encrypt 推荐使用它们制作的 certbot 帮助程序。 可以在这里查阅文档, ~~咱就先跳过了(笑)~~

对于例如不想引入更多依赖而不喜 certbot 的家伙嘛, acme.sh也是一种可行的解决方案。

如果希望只允许特定的 CA 给汝的网站签发证书的话,可以通过 DNS 证书颁发机构授 权记录(简称 CAA)来实现,CA 会提供相应的指南告诉汝如何添加和修改这种记录。

规划网站的兼容目标

越新的标准越能提升安全性,越旧的浏览器就越有可能不支持,大概就是这个样子。所以根据自己网站的受众决定向后兼容的程度就很重要。Mozilla 的标准有三个等级:

  • 现代等级:只支持 TLS 1.3,适用于不需要向后兼容的新网站。
  • 中等等级:支持 TLS 1.2 和 1.3,适用于大多数网站和估计近五年间的设备。
  • 向后兼容等级:只有汝需要考虑支持像是 Windows XP 或者 Java 6 这种相当老的系统时才应该使用这个等级。

它们分别兼容的最旧版本的浏览器和操作系统在下面:

等级 兼容的最旧版本
现代 Firefox 63, Android 10.0, Chrome 70, Edge 75, Java 11, OpenSSL 1.1.1, Opera 57, and Safari 12.1
中级 Firefox 27, Android 4.4.2, Chrome 31, Edge, IE 11 on Windows 7, Java 8u31, OpenSSL 1.0.1, Opera 20, Safari 9
向后兼容(旧的) Firefox 1, Android 2.3, Chrome 1, Edge 12, IE8 on Windows XP, Java 6, OpenSSL 0.9.8, Opera 5, and Safari 1

Mozilla 也提供一个线上配置生成器,可以根据不同的等级和服务器软件生成合适的参考配置文件。 根据汝的需要稍加修改(例如证书的位置)应该就可以使用了。

正确的重定向 HTTP 链接

现在汝的网站有 HTTPS 版本了,很好。但只有用户主动的输入带有 https:// 的地址的时候才能访问 HTTPS 版本, 于是汝还需要让用户通过 HTTP 访问的时候能自动重定向到 HTTPS 版本。

对于目前比较常见的 Apache 和 Nginx 来说,都挺简单的。

# Nginx 示例配置。
server {
  listen 80;

  return 301 https://$host$request_uri;
}
# Apache 的示例配置,记得把 foo.tld 换成汝自己的域名。
<VirtualHost *:80>
  ServerName foo.tld
  Redirect permanent / https://foo.tld/
</VirtualHost>

HTTP 严格传输安全 (HSTS)

汝设置好了正确的重定向,这本来没有问题,直到攻击者劫持了访问汝的网站第一步的 HTTP 响应…… 一场典型的 SSL 剥离攻击开始了。

所以 IETF 为了补救这个缺陷提出了 HTTP 严格传输安全标准,汝也许经常能看到它的缩写 HSTS 。

要实现 HTTP 严格传输安全,核心就是一个 HTTP 首部,例如下面的这个:

Strict-Transport-Security: max-age=31536000; includeSubDomains

如果 https://example.com 的响应中有这段的话:

  • 在接下来的 31536000 秒(即一年)中,浏览器向 example.com 发送 HTTP 请求时,必须采用 HTTPS 来发起连接。 比如,用户点击超链接或在地址栏输入 http://example.com/ ,浏览器应当自动将 http 转写成 https, 然后直接向 https://example.com/ 发送请求。
  • 在接下来的一年中,如果 example.com 服务器发送的TLS证书无效,用户不能忽略浏览器警告继续访问网站。
  • 这也适用于 example.com 下面的子域名,例如 www.example.com 。

以及这段只有在 HTTPS 响应中才会生效,那汝应该也会想那么第一次访问网站时怎么办。 各大浏览器的做法是维护一个预先加载列表,在列表上的网站总是会通过 HTTPS 访问。 汝可以参考这个网站来了解将自己的网站加入预先加载列表的要求。

OCSP 装订

汝也许听说过一个叫 OCSP(在线证书状态协议)的东西。浏览器每次连接支持 OCSP 的网站时,都会向 CA 发送请求查询这个证书的状态。 但这就带来了两个问题:

  • OCSP 是明文发送的,汝和 CA 之间的任何人都能知道汝在访问哪个网站,虽然不能精确到 URL,但进行阻断的话已经足够了。
  • 如果 CA 提供 OCSP 查询的服务器偷偷的留下了访问的记录……

而 OCSP 装订则改为了服务器向 CA 获取 OCSP 响应并缓存一段时间,用户访问时只要验证从服务器发送来的 OCSP 响应的有效性即可。

# Nginx 示例配置。
server {
    ... 
    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # 如果汝的 CA 没有提供 OCSP 使用的证书(例如 Let's Encrypt 就没有),
    # 那么不需要设置 ssl_trusted_certificate 属性。
    ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

    # 设置成汝服务器使用的 DNS 服务器。
    resolver 127.0.0.1;
}
# Apache 的示例配置。
# OCSP 装订缓存的位置,
SSLStaplingCache shmcb:/tmp/stapling_cache(128000)
<VirtualHost *:443>
...
# 如果汝的 CA 没有提供 OCSP 使用的证书(例如 Let's Encrypt 就没有),
# 那么不需要设置 SSLCertificateChainFile 属性。
SSLCertificateChainFile /path/to/DigiCertCA.crt

SSLUseStapling on
</VirtualHost>

Referrer 策略

Referrer 经常用来表示用户是从哪里来的,也有人担心它会泄露一些隐私信息。 Referrer-Policy 首部用来监管哪些访问来源信息——会在 Referer 中发送——应该被包含在生成的请求当中。

汝可以根据汝的网站和用户选择适合的模式。

# no-referrer-when-downgrade 是现代浏览器的默认设置,
# 在同等安全级别的情况下,引用页面的地址会被发送(HTTPS->HTTPS),但是在降级的情况下不会被发送 (HTTPS->HTTP)。
Referrer-Policy: no-referrer-when-downgrade
# 只有原地址相同时才发送 Referrer。
Referrer-Policy: same-origin
# 对于同源的请求,会发送完整的URL作为引用地址;
# 在同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS);在降级的情况下不发送此首部 (HTTPS->HTTP)。
Referrer-Policy: strict-origin-when-cross-origin
# 对不支持 strict-origin-when-cross-origin 的浏览器不发送首部。
Referrer-Policy: no-referrer, strict-origin-when-cross-origin
# 或者直接什么时候都不发送首部。
Referrer-Policy: no-referrer

X-Content-Type-Options 、 X-Frame-Options 和 X-XSS-Protection

X-Content-Type-Options 用来被服务器用来提示客户端一定要遵循在 Content-Type 首部中对 MIME 类型 的设定, 而不能对其进行修改。这就禁用了客户端的 MIME 类型嗅探行为。

# 防止浏览器不正确的识别文件类型,例如把非脚本文件识别成脚本。
X-Content-Type-Options: nosniff

The X-Frame-Options HTTP 响应头是用来给浏览器指示一个页面可否在框架中展现的标记。 (其实还有 <frame>, <embed><object> 的,但这些好像都被弃用了来着。)

站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免被用来进行汝点击的不是实际的地方的那种 clickjacking 攻击。 除了 HTTP 首部以外,内容安全策略(CSP)里的 frame-ancestors 属性也能控制这种行为,但是那个好复杂啊……

# 阻止网页嵌入在框架中。
# 第一行是通过 CSP 设置,第二行是传统的 HTTP 首部形式,它们的效果是相同的。
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
# 只允许相同域名的网页嵌入在框架中。
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
# 只允许特定来源的网页嵌入在框架中。
# 
Content-Security-Policy: frame-ancestors https://framer.mozilla.org
X-Frame-Options: DENY

X-XSS-Protection 响应头是 Internet Explorer,Chrome 和 Safari 的一个特性, 当检测到跨站脚本攻击时,浏览器将停止加载页面。

若网站设置了良好的 Content-Security-Policy 来禁用内联 JavaScript ('unsafe-inline'), 现代浏览器不太需要这些保护, 但其仍然可以为尚不支持 CSP 的旧版浏览器的用户提供保护。

# 启用 X-XSS-Protection如果 mode  block 的话
# 浏览器将在检测到可能的跨站脚本攻击时停止加载而不是清空页面
X-XSS-Protection: 1; mode=block

至于那个最复杂的 CSP 嘛…… ~~下次再说了……~~

更多资源

除了上面提到的 Mozilla 的文档和配置生成器以外:

  • Qualys SSLlab 可以帮汝测试汝的 HTTPS 配置, 也提供了一些可以参考的文档。
  • BadSSL可以测试汝正在使用的浏览器遇到错误的 TLS 设置时的反应。

keyboard_arrow_left 上一篇文章: Google 的 FLoC 是个糟糕透顶的主意

想要表达对咱的支持的话,汝可以:

需要 JavaScript 支持来使用 Isso 😂