I am LAZY bones?
AN ancient AND boring SITE

博客安全头踩坑记

我这个博客一直是自部署的,很久之前就装了 Really Simple Security(那时候还叫Really Simple SSL) 这个插件,最近它一直提示我的博客不安全,就按它提供的链接扫了一下,结果页面上一排 “Not Found”,HSTS、CSP、Referrer-Policy……六个安全头一个都没有。我心想,不对啊,好歹也是跑了这么多年的站,怎么会一个都没有?(后来看扫描页才发现,帮你加这些头是它 Pro 版的功能,免费版只负责提醒你。。。)


curl 一下看看:

还真是干干净净,除了 nginx 很热情地把自己的版本号和系统都报出来了,啥安全相关的头都没有。。。行吧,补。结果补着补着,把同域名下的另一个服务给补挂了,哈哈。整个过程学到不少东西,写下来,分享给有需要的朋友。

这六个头都是干嘛的?

先说结论:这几个都不是”漏洞”,而是”浏览器端的防护没开”。WordPress 默认不加,nginx 默认也不加,所以只能自己加。

1. Strict-Transport-Security(HSTS):告诉浏览器”以后只许用 HTTPS 访问我”。我的 80 端口虽然早就 301 到 https 了,但用户每次敲 luy.li 的时候,都会先发一次明文请求再被跳走,公共 Wi-Fi 下就有被劫持降级的可能。有了 HSTS,浏览器会直接在本地改成 https,明文请求压根不发出去。不过它保护不了第一次访问(得先收到过这个头才行),想连第一次也保护,就得靠后面会说到的 preload。
2. X-Content-Type-Options: nosniff:不让浏览器瞎猜文件类型,防止有人传个伪装成图片的 HTML 被当脚本执行。零副作用,无脑加。
3. Referrer-Policy:跳去外站的时候少带点信息。推荐 strict-origin-when-cross-origin,跨站只带域名,不带完整路径和参数。
4. Permissions-Policy:把摄像头、麦克风、定位这些用不上的浏览器能力直接关掉。
5. X-XSS-Protection:这个比较有意思,推荐值是 0,也就是关掉。老浏览器内置的 XSS 过滤器本身就有漏洞,已经被废弃了,明确关掉反而更安全。
6. Content-Security-Policy(CSP):白名单,限制页面能从哪加载脚本、样式、能不能被别人 iframe 嵌套。防 XSS 最有效的一层,但也是最容易把站搞坏的一个。

nginx 里怎么加?

在 443 的那个 server {} 里加上:

几个要注意的地方:

1. add_header 的继承是”全有或全无”。某个 location 里只要自己写了一条 add_header(比如给静态文件加 Cache-Control),server 级的所有 add_header 在这个 location 里就全部失效了。这个坑非常隐蔽,加完一定要挨个 location curl 一下。
2. 带上 always,不然 404、500 这些错误页是不带这些头的。
3. CSP 我只给了个宽松版。看了下首页,脚本倒是都来自本站,但 WordPress 和插件会往页面里塞好几段内联 script,要是写 script-src 'self',页面立马就坏了。所以我只做了不会弄坏页面的几件事:升级混合内容、禁止被别的站 iframe 嵌套(顺便防了点击劫持)、禁 object、锁 base。想收紧的话,先用 Content-Security-Policy-Report-Only 跑几天,看控制台报了啥再说。

改完 nginx -t && systemctl reload nginx,再 curl:

server 那行也不再报版本号了,舒服~

顺手堵上的几个口子

扫描器只看响应头,但既然都动配置了,WordPress 常见的几个口子也顺便看了看。

xmlrpc.php。这个默认是开着的,而且支持 system.multicall,意思是一个请求里可以塞上百组用户名密码去试,爆破效率极高。我又不用手机 App 发文章,直接关:

readme.html 和 license.txt。这俩文件能直接访问,里面写着 WordPress 版本号,等于告诉别人该拿哪个版本的漏洞来试。同样 deny 掉(删文件也行,但每次升级 WordPress 会重新生成,不如 nginx 里一劳永逸)。

HSTS 是不认端口的!

到此,我以为都结束了,但看进度条就知道(喂,你是blog哎,哪有进度条?)还没完。配置上线之后,我检查了一下在这个域名下还跑着另一个服务,之前是 https://luy.li:端口号 这种明文HTTP地址访问的,发现已经挂掉了。

HSTS 是按主机名生效的,跟端口没有关系(RFC 6797 里写得明明白白)。浏览器只要从 https://luy.li 收到过一次 HSTS 头,之后访问 https://luy.li:任意端口 都会被自动改写成 https://luy.li:同一个端口。而那个端口上跑的是 HTTP,并且我不想改成 HTTPS。

也就是说,部署完之后,所有看过我博客的浏览器,再打开那个服务都会连不上,而且这个状态会被浏览器记两年。好在非浏览器的客户端一般不遵守 HSTS,只有浏览器会中招,不幸中的万幸 😂

咱们换个思路:HSTS 只管下发它的那个主机名,如果我不加 includeSubDomains,它就管不到子域名。刚好我的 DNS 配了泛解析,随便起个子域名就能解析到同一台机器上,换成 http://xxx.luy.li:端口号 访问,一行配置都不用改。可问题是,扫描器推荐的值里偏偏就带着 includeSubDomains。。。那能不能加上它,再给那个子域名开个白名单呢?

includeSubDomains 能加白名单吗?

不能。HSTS 标准里压根没有排除机制:

1. 端口:HSTS 完全不看端口,没法单独放过某个端口。
2. 子域名:父域名一旦带了 includeSubDomains,所有子域名、不管几层都被覆盖。子域名自己返回 max-age=0 也没用,它只能清掉自己的那条策略,管不了父域名的。
3. preload:header 里写上 preload 只是个表态,要去 hstspreload.org 提交、审核通过,才会真正被写进浏览器的内置列表。它的硬性要求就是必须带 includeSubDomains,所有子域名都得能正常走 HTTPS(我的证书一开始就没包含 www,差点栽在这)。而且一旦进了列表,想撤出来得等浏览器发新版,几个月起步。

所以,如果某个服务就是要跑明文 HTTP,又想上 includeSubDomains,只剩两条路:给它另起一个独立的域名,或者直接用 IP 访问(HSTS 对 IP 不生效)。我似乎有洁癖,所以选择了换个域名跑,扫描就全绿了。

浏览器把 HSTS 存哪了?

踩完这个坑,自然会想:已经中招的浏览器,怎么把 luy.li 的 HSTS 记录删掉?

Chrome 最方便。打开 chrome://net-internals/#hsts(Edge 是 edge://net-internals/#hsts),先在 “Query HSTS/PKP domain” 里查一下 luy.li,能看到记录就说明中招了;再在 “Delete domain security policies” 里输入 luy.li,点 Delete 就行。底层文件在 Profile 目录下的 TransportSecurity,是个 JSON,但里面的主机名是哈希过的,直接看是看不出来的。注意这里只能删浏览器动态记下的,preload 内置列表里的删不掉。

Firefox 在历史记录里找到这个站,右键”忘记此网站”,不过会把 Cookie 什么的一起清掉。底层文件是 Profile 目录下的 SiteSecurityServiceState.bin。

Safari 的话,我在 ~/Library 下搜了一圈,现在的 macOS 是各个 App 自己存一份的,比如 Caches/com.apple.helpd/HSTS.plist、Caches/us.zoom.xos/WebKit/HSTS。Safari 是沙盒应用,它那份应该在 ~/Library/Containers/com.apple.Safari/ 下面,终端去访问直接报 Operation not permitted,除非给终端开”完全磁盘访问权限”。所以老老实实走界面吧:Safari 设置 → 隐私 → 管理网站数据,搜 luy.li,移除,然后彻底退出 Safari 再开。

不过要清楚一点:删了也白删。只要这个浏览器再打开一次 https://luy.li,HSTS 头又会被写回来。删除只适合临时救急,想长期共存,还是得像上面那样把服务挪到别的域名上。

几个值得记住的坑

1. nginx 的 add_header:location 里写了任何一条,就不再继承 server 级的全部 add_header。
2. HSTS 按主机名生效,不认端口。同一个域名下只要有一个端口跑明文 HTTP,就不能在这个域名上开 HSTS。
3. includeSubDomains 没有白名单,preload 基本是单行道,加之前想清楚。
4. 撤销 HSTS 不能只删掉这个头,要返回 max-age=0,已经缓存了策略的浏览器才会清掉。已经进了 preload 列表的,这招也不管用。

好了,我去把各个设备上的书签都换成其他域名了。本来只是想让扫描器变绿,结果差点把自己的另一个服务搞瘫。。。😅

最后修改时间: 2026年09月26日 08:20

本文章发表于: 2026年09月26日 08:18 | 所属分类:经验技巧. | 您可以在此订阅本文章的所有评论. | 您也可以发表评论, 或从您的网站trackback.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注