Chrome 拦截页上的 thisisunsafe

HSTS 网站证书出错时 Chrome 不给「继续前往」,但在拦截页上盲打 thisisunsafe 就能进去。按 RFC 6797、Chromium 源码和一次实测讲清楚:为什么没有按钮、这串字母怎么生效、放行记多久、什么时候不该用。

这个技巧最早是在 跑路的duck 的《Chrome HSTS 的小彩蛋》里看到的。本文按官方规范、Chromium 源码重新整理,并在 Chrome 155 上实测了一遍。

先看两张拦截页

证书有问题时,Chrome 会拦下来显示「您的连接不是私密连接」。同样是证书和域名对不上(NET::ERR_CERT_COMMON_NAME_INVALID),点开「高级」之后,两个网站给的选项不一样:

普通网站:展开高级后有「继续前往(不安全)」

普通网站(wrong.host.badssl.com)的最下面有一行「继续前往 wrong.host.badssl.com(不安全)」,点了就能进。

HSTS 网站:没有继续前往,只说因为使用了 HSTS 无法访问

启用了 HSTS 的网站(subdomain.preloaded-hsts.badssl.com)没有这一行,只有一句「您目前无法访问……因为此网站使用了 HSTS」,蓝色主按钮也从「返回安全连接」换成了「重新加载」。页面上没有任何能让你进去的东西。

两个测试域名都来自 badssl.com,专门用来演示各种证书错误。

HSTS 为什么不给你按钮

HSTS(HTTP Strict Transport Security)定义在 RFC 6797。网站通过 HTTPS 返回一个响应头:

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

浏览器收到后,在 max-age 秒内把这个域名记为「已知 HSTS 主机」:之后访问 http:// 会先在本地改写成 https:// 再发出去;带了 includeSubDomains 的话,所有子域名一起算。

关键在证书出错时的处理。规范第 8.4 节要求,连接已知 HSTS 主机时,安全传输层只要出现任何错误(证书校验失败、主机名不匹配、吊销检查失败……),浏览器必须终止连接。第 12.1 节标题就叫「No User Recourse」,意思是不能给用户一个「仍然继续」的对话框,要当成服务器故障处理,用户只能等着重试。理由也写得很直白:允许用户点过警告,就是给中间人攻击留门。网站发出 HSTS 头,等于主动选择了这种「出错就别进」的待遇。

所以 Chrome 拿掉「继续前往」是在照规范办事,不是故障。

一个域名怎么成为 HSTS 主机有两条路,chrome://net-internals/#hsts 里分别叫 static 和 dynamic:

怎么来的能不能在本机删
dynamic(动态)浏览器之前用 HTTPS 访问过、收到了上面那个响应头能
static(静态)域名在 HSTS 预加载列表里,列表直接编译进浏览器不能,只能等网站申请移除、随浏览器更新生效

预加载列表是为了补「第一次访问」的漏洞:响应头只有通过有效的 HTTPS 收到才算数(MDN 也特别说明,经 HTTP 收到的这个头会被忽略),那么第一次访问之前浏览器什么都不知道,第一次明文请求仍可能被劫持。进了预加载列表,浏览器从装好那天起就知道这个域名只能走 HTTPS。上面那个 badssl 的测试域名就在列表里。

thisisunsafe 在源码里是什么

Chrome 的证书拦截页是一张内置网页,脚本在 Chromium 源码的 components/security_interstitials/core/browser/resources/interstitial_large.js。相关的就这一段:

js
/**
 * This allows errors to be skippped by typing a secret phrase into the page.
 * @param {string} e The key that was just pressed.
 */
function handleKeypress(e) {
  // HTTPS errors are serious and should not be ignored. For testing purposes,
  // other approaches are both safer and have fewer side-effects.
  // See https://goo.gl/ZcZixP for more details.
  const BYPASS_SEQUENCE = window.atob('dGhpc2lzdW5zYWZl');
  if (BYPASS_SEQUENCE.charCodeAt(keyPressState) === e.keyCode) {
    keyPressState++;
    if (keyPressState === BYPASS_SEQUENCE.length) {
      sendCommand(SecurityInterstitialCommandId.CMD_PROCEED);
      keyPressState = 0;
    }
  } else {
    keyPressState = 0;
  }
}

dGhpc2lzdW5zYWZl 是 Base64,解出来就是 thisisunsafe。页面监听整个文档的按键,按对一个字母进一格,按错一个就从头再来,凑满 12 个字母就发出「继续」命令(CMD_PROCEED),和点「继续前往」链接发的是同一个命令。所以用法是:拦截页上什么都不用点,直接用键盘打这 12 个字母,页面上不会有任何显示,打完自动跳进网站。打错了不用删,重新从 t 开始打就行。要在英文输入状态下打,否则按键会先进输入法。

注释里那句话说得很清楚:HTTPS 错误很严重、不该被忽略,测试的话有更安全、副作用更小的做法。这串字母是给开发者留的,不是给普通用户的功能,所以拿 Base64 包了一层,也从不在界面上提示。

为什么 HSTS 也拦不住它

命令发到浏览器进程之后,由 ssl_error_ui.cc 处理。Chrome 内部把「放行」分成两层:

是什么谁会把它关掉
soft override拦截页上那行「继续前往」链接HSTS 主机、几种致命错误(证书已吊销、证书无效等)、管理策略
hard override收到 CMD_PROCEED 时是否真的放行只有明确设置了「禁止越过拦截页」时

ssl_error_options_mask.cc 里算这两个开关时,HSTS 这类「致命」情况只会去掉 soft override,hard override 不受影响;而处理 CMD_PROCEED 时只检查 hard override。所以链接没了,这串字母照样有效。

实测

用 Chrome 155.0.8059.39(无头模式,全新的用户配置)打开 https://subdomain.preloaded-hsts.badssl.com/:

HSTS 网站的拦截页,只有「高级」和「重新加载」

不点任何东西,模拟键盘依次输入 thisisunsafe。打完之后页面地址从拦截页的 chrome-error://chromewebdata/ 变成了 https://subdomain.preloaded-hsts.badssl.com/,显示的是 badssl 服务器返回的 nginx 404 页面(这个测试子域名本来就没有内容),说明请求确实发到了对方服务器。

还有一个现象:用同一份配置再打开一次,拦截页不出现了,直接进去。这是因为放行会被记住,见下一节。

放行之后会记多久

Chromium 的 stateful_ssl_host_state_delegate.cc 开头写着:

cpp
// Certificate error bypasses are remembered for one week.
const uint64_t kCertErrorBypassExpirationInSeconds = UINT64_C(604800);

一次放行在这台电脑、这个浏览器配置里记 7 天,按「域名 + 证书指纹 + 错误类型」记。7 天内再访问同一个网站、对方还是同一张证书,就不会再拦。地址栏左边会一直显示「不安全」,点开会看到「您已选择为此网站关闭安全警告。」,旁边的「开启警告」按钮可以提前撤销。

什么时候不该用

HSTS 网站的意思是「我承诺永远用有效证书」。它出现证书错误,常见原因无非几种:

1、网站自己的证书过期或配错了;

2、你连的是公共 Wi-Fi,登录页把连接截走了;

3、有人在中间冒充它。

前两种进去问题不大,第三种正是 HSTS 要防的情况。拿不准是哪种的时候,不要在放行后的页面上输入密码、验证码或任何个人信息。

公司或学校管理的电脑上,管理员可能用 Chrome 的企业策略 SSLErrorOverrideAllowed 关掉放行;按官方说明,设为禁用时用户不能越过任何证书警告页。

如果是自己的网站被 HSTS 卡住

开发时最常见的情况是:域名以前用 HTTPS 发过 HSTS 头,后来换成自签名证书或者暂时只开了 HTTP,浏览器就一直拦。与其每次打 thisisunsafe,不如把本机记录的那条策略删掉。

打开 chrome://net-internals/#hsts,在 Query HSTS domain 里输入域名查询:

chrome://net-internals/#hsts 的查询结果:badssl 测试域名是 static 记录

结果里 static_* 有值,说明在预加载列表里,本机删不掉;dynamic_* 有值,说明是之前访问时记下的,在下面的 Delete domain security policies 里输入域名删掉即可(页面上也注明了不能删预加载条目)。

服务器那边,要让访客的浏览器忘掉这条策略,就在 HTTPS 上返回 max-age=0(RFC 6797 第 6.1.1 节):

http
Strict-Transport-Security: max-age=0

已经进了预加载列表的域名,按 hstspreload.org 的说明,移除很难,生效要等几个月随 Chrome 更新推送,其他浏览器什么时候跟进也没有保证。申请加入之前要想清楚。

参考

来源用到的内容
RFC 6797第 6.1 节响应头语法、第 8.4 节出错必须终止、第 12.1 节 No User Recourse
MDN:Strict-Transport-Security首次访问的问题、HTTP 下收到的头会被忽略
hstspreload.org预加载列表的加入条件与移除
Chromium 源码拦截页脚本、soft / hard override、放行记 7 天
Chrome 企业策略SSLErrorOverrideAllowed

评论

评论加载中……

登录后再评论

注册要用邮箱收个验证码,只为确认邮箱能收信,不会拿去做别的。账号设置