<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Web 安全 · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/series/web-security/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/series/web-security/index.xml" rel="self" type="application/rss+xml"/><item><title>证书过期是一个日期，而不是仪表盘上的颜色</title><link>https://hk.xbcatm.com/zh/notes/certificate-expiry-is-a-date-not-a-dashboard-color/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/certificate-expiry-is-a-date-not-a-dashboard-color/</guid><description>检查真实主机名服务的证书，并把剩余天数变成可行动的阈值。</description><content:encoded>&lt;p&gt;HTTPS 进程可能是健康的，而公共证书却临近过期、为错误的名字签发，或在另一条边缘上被不同地提供。一条有用的检查，是向公共端点索取它会给真实客户端的那张证书。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/tls-expiry.detail.zh.svg" alt="公共主机名通过 SNI 选择证书。观察到的 subject、issuer、指纹、notBefore 与 notAfter 都是证据，不是验证结果。剩余时间与按服务恢复目标选择、且小于正常证书寿命的阈值比较。另行检查主机名、证书链与信任库、notBefore 和吊销状态，并保留诊断信息。管道可能打印证书却隐藏验证错误，因此最后一个命令的退出码不能证明 TLS 健康。" width="720" height="3050"&gt;&lt;figcaption&gt;先检视真实客户端看到的证书，再独立验证。到期告警应使用服务自己的响应阈值，配合握手检查、续期失败告警，以及相关边缘和地址族抽查。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="包含主机名"&gt;包含主机名&lt;/h2&gt;
&lt;p&gt;TLS 虚拟主机依赖服务器名称。把主机名作为 SNI 传入，并检视对端证书：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;www.example.test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl s_client -connect &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;:443&amp;#34;&lt;/span&gt; -servername &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;lt;/dev/null 2&amp;gt;/dev/null &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; openssl x509 -noout -subject -issuer -dates -fingerprint -sha256
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这条命令是只读的。它不会续签任何东西，也不能证明每个中间证书都被每个客户端接受。但它确实给出为该主机名呈现的证书的 not-before、not-after、subject、issuer 与指纹。&lt;/p&gt;
&lt;h2 id="在阈值上告警"&gt;在阈值上告警&lt;/h2&gt;
&lt;p&gt;选择能留出续期失败、DNS 问题与人工响应时间的阈值。确切数字属于服务的恢复目标；并非普通的&amp;quot;30 天&amp;quot;规则就能替代这个决定。配套的&lt;a href="https://hk.xbcatm.com/zh/resources/"&gt;TLS 过期检查&lt;/a&gt;会打印过期时间，并在剩余区间低于你传入的阈值时以非零退出。&lt;/p&gt;
&lt;p&gt;尽可能从主机外部运行检查。本地进程看到的监听器、证书文件或代理路径，可能与公共网络上的访问者不同。&lt;/p&gt;
&lt;h2 id="把观察与验证分开"&gt;把观察与验证分开&lt;/h2&gt;
&lt;p&gt;SNI 用于选择虚拟主机，并不验证证书是否匹配该名称。即使 &lt;code&gt;s_client&lt;/code&gt; 报告验证问题，这条管道也可能打印证书，而且它隐藏了标准错误。另做主机名与证书链验证，保留诊断信息并使用已知信任库。不要把这条检视管道最后一个命令的退出码当成 TLS 健康结论。&lt;/p&gt;
&lt;p&gt;配套脚本以 OpenSSL &lt;code&gt;-checkend&lt;/code&gt; 检查 &lt;code&gt;notAfter&lt;/code&gt;，并不能证明主机名匹配、信任关系、吊销状态或 &lt;code&gt;notBefore&lt;/code&gt; 已经到来。把到期告警与握手检查、续期失败告警配合使用。阈值应小于证书正常寿命，短期证书尤其如此；还应抽查相关边缘与地址族。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/automatic-https"&gt;Caddy Automatic HTTPS&lt;/a&gt;，自动 TLS 证书管理，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt; 文档&lt;/a&gt;，SNI 与证书检查选项，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-x509/"&gt;OpenSSL &lt;code&gt;x509&lt;/code&gt; 文档&lt;/a&gt;，证书解析、日期与指纹，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HSTS 应带着恢复计划来灰度发布</title><link>https://hk.xbcatm.com/zh/notes/hsts-should-be-rolled-out-with-a-recovery-plan/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/hsts-should-be-rolled-out-with-a-recovery-plan/</guid><description>只有在 HTTPS、重定向与每个被子域名覆盖的域名都就绪之后，才增大 max-age。</description><content:encoded>&lt;p&gt;HTTP Strict Transport Security 告诉支持的浏览器，在一个缓存周期内对该主机强制使用 HTTPS。这种持久性既是特性也是风险：移除响应头并不会立刻抹掉客户端已存储的策略。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/hsts-rollout.detail.zh.svg" alt="发布轨道要求检查 HTTPS 与重定向、采用短时长示例，并在观察站点与续期后选择时长。域名树将 includeSubDomains 限于发送主机的后代；独立的预加载和恢复面板说明已存策略的限制。" width="720" height="2530"&gt;&lt;figcaption&gt;观察后再增加 HSTS 时长，独立决定后代覆盖与预加载。恢复需要有效 HTTPS，也不能立刻抹除所有已存或预加载策略。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="先验证-https-路径"&gt;先验证 HTTPS 路径&lt;/h2&gt;
&lt;p&gt;在发送 HSTS 之前，确认规范主机名提供有效证书，并且 HTTP 在不依赖用户输入的情况下重定向到预期的 HTTPS URL：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null --max-redirs &lt;span class="m"&gt;0&lt;/span&gt; http://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null https://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;检查有代表性的深层路由与错误页。HSTS 无法修复证书错误，因为浏览器在接受该策略之前要求有效的 HTTPS 连接。&lt;/p&gt;
&lt;h2 id="从一个短的-max-age-开始"&gt;从一个短的 max-age 开始&lt;/h2&gt;
&lt;p&gt;分阶段发布能在配置还新的时候限制恢复窗口：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Strict-Transport-Security: max-age=300
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;观察站点与续期路径之后，再有意识地提高时长。记录日期、所选值与回滚条件。最终策略可能用一年，但这个数字是一种承诺，而不是扫描器分数。&lt;/p&gt;
&lt;h2 id="把-includesubdomains-当作独立的决定"&gt;把 includeSubDomains 当作独立的决定&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;includeSubDomains&lt;/code&gt; 把策略扩展到每个子域名。先盘点名称，包括旧服务、委派区，以及只在内部网络使用的主机名。即使只有一个仅 HTTP 的后代，也足以让该选项对收到父级策略的客户端产生破坏。&lt;/p&gt;
&lt;h2 id="预加载不是普通的响应头配置"&gt;预加载不是普通的响应头配置&lt;/h2&gt;
&lt;p&gt;浏览器预加载程序可以在正常响应周期之外分发策略，其要求与移除延迟比设置一个响应头更严格。不要仅仅因为扫描器推荐就申请预加载；请先确认对域名及所有被覆盖名称的长期所有权。&lt;/p&gt;
&lt;h2 id="验证公共响应"&gt;验证公共响应&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null https://www.example.test/ &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;tolower($1) == &amp;#34;strict-transport-security:&amp;#34; { print }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;测试规范的公共边缘，而不只是某个源站端口。代理可能添加、移除或重复该响应头。&lt;/p&gt;
&lt;h2 id="知道回滚能清除什么"&gt;知道回滚能清除什么&lt;/h2&gt;
&lt;p&gt;通过有效 HTTPS 响应发送 &lt;code&gt;max-age=0&lt;/code&gt;，只会让收到该响应的客户端清除该主机已学习的策略。移除响应头或通过 HTTP 发送它，都不能清除已缓存策略。子域无法退出父域仍有效的 &lt;code&gt;includeSubDomains&lt;/code&gt;；清除父域策略，也不会删除客户端独立从子域学习到的策略。&lt;/p&gt;
&lt;p&gt;作用域由发送响应头的主机决定：&lt;code&gt;www.example.test&lt;/code&gt; 的策略覆盖自身，加上 &lt;code&gt;includeSubDomains&lt;/code&gt; 后覆盖其后代，但不覆盖同级的 &lt;code&gt;api.example.test&lt;/code&gt; 或父级 &lt;code&gt;example.test&lt;/code&gt;。恢复期间必须保持 HTTPS 可用。移除预加载是独立的浏览器更新过程，不是响应头的即时回滚。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6797.html"&gt;RFC 6797 — HTTP Strict Transport Security&lt;/a&gt;，策略处理与作用域，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security"&gt;MDN: &lt;code&gt;Strict-Transport-Security&lt;/code&gt;&lt;/a&gt;，指令与部署注意事项，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hstspreload.org/"&gt;HSTS 预加载项目&lt;/a&gt;，提交要求与移除限制，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>安全响应头应描述实际页面</title><link>https://hk.xbcatm.com/zh/notes/security-headers-should-describe-the-actual-page/</link><pubDate>Thu, 23 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/security-headers-should-describe-the-actual-page/</guid><description>当紧凑的策略与静态页面真正加载的资源匹配时，它更安全。</description><content:encoded>&lt;p&gt;安全响应头不是安全代码的替代品，但它们让浏览器强制实施有用的边界。策略应描述实际被服务的页面，而不是从另一个项目复制来的泛型模板。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/security-layers.detail.zh.svg" alt="HTML、本地样式表与 favicon 决定 CSP 资源许可。Content-Security-Policy 强制执行；Report-Only 揭示违规且报告需另行配置。CSP 应通过响应头发送：frame-ancestors 在 meta 中被忽略且不回退到 default-src，form-action 需要自己的指令，style-src self 本身不允许内联样式。HSTS 从有效 HTTPS 响应学习；www.example.test 的 includeSubDomains 覆盖该主机与后代，不覆盖 example.test 或 api.example.test。应检查正常页、缺失页与资源响应，并在真实浏览器验证执行。本图不声称已经测试。" width="720" height="3460"&gt;&lt;figcaption&gt;把响应头视为部署契约：匹配实际资源，区分强制执行与仅报告策略，再验证准确主机范围和浏览器行为。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="从资源图开始"&gt;从资源图开始&lt;/h2&gt;
&lt;p&gt;如果站点只提供 HTML、本地样式表、一个 favicon、没有任何可执行 JavaScript，那么一条窄策略是可理解的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Security-Policy: default-src &amp;#39;self&amp;#39;; base-uri &amp;#39;self&amp;#39;; form-action &amp;#39;none&amp;#39;; frame-ancestors &amp;#39;none&amp;#39;; object-src &amp;#39;none&amp;#39;; script-src &amp;#39;none&amp;#39;; style-src &amp;#39;self&amp;#39;; img-src &amp;#39;self&amp;#39; data:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;script-src 'none'&lt;/code&gt; 只有在站点不需要可执行脚本时才是正确的选择。加入搜索、评论或统计标签会改变资源图，并应触发一次有意识的策略复核。&lt;/p&gt;
&lt;h2 id="增加传输与嵌套保护"&gt;增加传输与嵌套保护&lt;/h2&gt;
&lt;p&gt;对仅 HTTPS 的站点，下列响应头是常见起点：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Strict-Transport-Security: max-age=31536000; includeSubDomains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Referrer-Policy: strict-origin-when-cross-origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;只有当每个子域名都准备好提供 HTTPS 时才启用带 includeSubDomains 的 HSTS。响应头是与未来部署的契约，而不只是扫描器里的一个分数。&lt;/p&gt;
&lt;h2 id="检查行为而不只是存在"&gt;检查行为，而不只是存在&lt;/h2&gt;
&lt;p&gt;请求一个正常页面、一个缺失页面与一个静态资源。错误响应应携带与成功响应同样重要的安全响应头。还要确认压缩与缓存不会改变内容类型，也不会暴露内部路径。&lt;/p&gt;
&lt;p&gt;本站的&lt;a href="https://hk.xbcatm.com/zh/resources/"&gt;Caddy 示例&lt;/a&gt;只是一个起点，不是普适策略。请根据你自己的部署调整主机名、证书处理与应用路由。&lt;/p&gt;
&lt;p&gt;通过响应头发送 CSP。meta 策略中的 &lt;code&gt;frame-ancestors&lt;/code&gt; 会被忽略，而且它不会继承 &lt;code&gt;default-src&lt;/code&gt;；&lt;code&gt;form-action&lt;/code&gt; 也需要自己的指令。&lt;code&gt;style-src 'self'&lt;/code&gt; 本身不允许内联样式，因此应测试生成的主题，包括深色模式。只有页面确实需要 data 图片时，才在 &lt;code&gt;img-src&lt;/code&gt; 中保留 &lt;code&gt;data:&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; 可在上线过程中揭示违规，但不会强制执行策略；报告端点还需单独配置。除了查看响应头，也要在真实浏览器中验证执行效果。HSTS 从有效 HTTPS 响应中学习；带有 &lt;code&gt;includeSubDomains&lt;/code&gt; 时，它还覆盖设置策略的主机的后代：&lt;code&gt;www.example.test&lt;/code&gt; 的策略不覆盖父域 &lt;code&gt;example.test&lt;/code&gt; 或同级 &lt;code&gt;api.example.test&lt;/code&gt;。确认这个准确范围后，再逐步延长有效期。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP"&gt;MDN: Content Security Policy guide&lt;/a&gt;，CSP 指令名称与语法，已核验 2026-07-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security"&gt;MDN: &lt;code&gt;Strict-Transport-Security&lt;/code&gt;&lt;/a&gt;，HSTS 指令与部署注意事项，已核验 2026-07-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/header"&gt;Caddy &lt;code&gt;header&lt;/code&gt; 指令&lt;/a&gt;，响应头操作指令，已核验 2026-07-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/CSP3/"&gt;W3C Content Security Policy Level 3&lt;/a&gt;，响应头交付、指令回退、嵌套与仅报告策略；当前为工作草案，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6797.html"&gt;RFC 6797 — HTTP Strict Transport Security&lt;/a&gt;，第 8.1–8.3 节的有效 HTTPS 交付与主机及后代匹配，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>SSH 主机密钥是部署记录的一部分</title><link>https://hk.xbcatm.com/zh/notes/ssh-host-keys-are-part-of-the-deployment-record/</link><pubDate>Tue, 28 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/ssh-host-keys-are-part-of-the-deployment-record/</guid><description>让首次接触保持显式，并拒绝未预期的主机密钥变化。</description><content:encoded>&lt;p&gt;在公钥认证中，客户端证明自己持有私钥，但它并不告诉客户端到达的是哪台服务器。服务器的主机密钥与本地 &lt;code&gt;known_hosts&lt;/code&gt; 数据库，提供身份检查的后一半。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/ssh-host-key.detail.zh.svg" alt="客户端私钥认证与服务器主机身份核验是不同检查。安装前，将候选指纹与独立可信的所有者或控制台指纹比较。yes 拒绝未知或变化的密钥；accept-new 接受首次见到的密钥但没有独立身份保证，仍拒绝变化。复核轮换、保存私有运维记录，ssh -G 不等于握手测试。" width="720" height="2300"&gt;&lt;figcaption&gt;SSH 服务器身份依赖观察到的主机密钥与独立可信参考之间的比较。严格检查拒绝未知或变化的密钥。首次使用时信任与网络密钥扫描，都不能独立认证首次接触的服务器。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="把首次接触与轮换分开"&gt;把首次接触与轮换分开&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;StrictHostKeyChecking=yes&lt;/code&gt; 下，SSH 拒绝未知主机与变化的密钥。对于一个固定生产端点，只要期望密钥通过可信渠道安装，这就是最安全的默认值。&lt;code&gt;accept-new&lt;/code&gt; 是面向预期会出现新主机的、更窄的方便选项：它接受一个从未见过的密钥，但仍拒绝被更改的密钥。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-sshconfig" data-lang="sshconfig"&gt;Host production-site
HostName www.example.test
User deploy
IdentityFile ~/.ssh/site_ed25519
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/known_hosts
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;不要通过给部署脚本加 &lt;code&gt;StrictHostKeyChecking=no&lt;/code&gt; 来&amp;quot;修复&amp;quot;警告。它会在客户端限制条件下允许连接到密钥已变化的主机；警告可能仍被打印，但原本遇到身份问题就拒绝的检查已被削弱。如果主机被重建，请记录替换后的指纹，并复核旧密钥为何不再有效。&lt;/p&gt;
&lt;h2 id="让指纹可复核"&gt;让指纹可复核&lt;/h2&gt;
&lt;p&gt;在首次连接之前，从主机所有者或控制台获取指纹，再与客户端看到的值比较。密钥变更应有工单、维护说明或其他他人可检视的解释。&lt;/p&gt;
&lt;p&gt;同一规则适用于自动化：known-hosts 文件是带有安全后果的配置，不是可丢弃的缓存数据。把它与部署记录一起备份，但不要把私钥或复制的凭据随站点制品一起公开。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;accept-new&lt;/code&gt; 是首次使用时信任，并不能独立证明第一次连接的服务器真实可信。&lt;code&gt;ssh-keyscan&lt;/code&gt; 能采集候选公钥，但通过同一条不可信网络取回它，并不能认证它。安装前先将指纹与独立可信渠道取得的值比较。&lt;/p&gt;
&lt;p&gt;把上面的配置单独保存为 &lt;code&gt;deploy.conf&lt;/code&gt;，在不连接服务器的情况下检查有效配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -G -F ./deploy.conf production-site &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; grep -E &lt;span class="s1"&gt;&amp;#39;^(hostname|user|stricthostkeychecking|userknownhostsfile) &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;结果应显示配置中的主机、用户、known-hosts 路径与严格检查（客户端输出可能为 &lt;code&gt;true&lt;/code&gt; 或 &lt;code&gt;yes&lt;/code&gt;）。这只解析配置，并不测试真实握手。非标准端口的 known-hosts 条目通常采用 &lt;code&gt;[hostname]:port&lt;/code&gt;。将主机公钥指纹保存在私有运维发布记录中，不要随公共网站发布；即使主机公钥不是秘密，主机名也可能暴露内部拓扑。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh_config"&gt;OpenBSD &lt;code&gt;ssh_config&lt;/code&gt;&lt;/a&gt;，&lt;code&gt;StrictHostKeyChecking&lt;/code&gt; 与 &lt;code&gt;UserKnownHostsFile&lt;/code&gt;，已核验 2026-07-28。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh-keygen"&gt;OpenSSH &lt;code&gt;ssh-keygen&lt;/code&gt; 手册&lt;/a&gt;，指纹检视，已核验 2026-07-28。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh-keyscan"&gt;OpenSSH ssh-keyscan&lt;/a&gt;，未经核验采集的密钥不能防止中间人攻击，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh"&gt;OpenSSH ssh&lt;/a&gt;，-G 不建立会话，打印求值后的客户端配置，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Cookie 作用域应比应用边界更窄</title><link>https://hk.xbcatm.com/zh/notes/cookie-scope-should-be-narrower-than-the-application-boundary/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/cookie-scope-should-be-narrower-than-the-application-boundary/</guid><description>把 Domain、Path、Secure、HttpOnly 与 SameSite 当作同一份浏览器存储契约来复核。</description><content:encoded>&lt;p&gt;Cookie 依据浏览器规则附加到请求，而不是依据最初设置它的服务器组件。宽泛的 &lt;code&gt;Domain&lt;/code&gt; 或 &lt;code&gt;Path&lt;/code&gt; 会把状态暴露给无关的路由或子域名，而缺失传输与脚本限制则会放大其他失败造成的损害。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/cookie-scope.detail.zh.svg" alt="先区分仅主机与 Domain 作用域。Path 选择请求路径，不构成授权边界。Secure 限制传输，但不加密存储。SameSite 影响跨站上下文，现代浏览器的 None 还需要 Secure。HttpOnly 阻止 document.cookie 访问，但浏览器仍发送 Cookie。支持前缀的浏览器要求 __Host- 使用 Secure、无 Domain 且 Path=/。删除匹配名称和域名/路径作用域，并满足前缀和安全来源要求；服务器会话失效另行处理。静态站点应同时检查响应头与浏览器存储。" width="720" height="3300"&gt;&lt;figcaption&gt;一起复核这些维度，同时区分发送目的地、JavaScript 访问权限与删除所针对的存储身份。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="偏好仅主机-cookie"&gt;偏好仅主机 Cookie&lt;/h2&gt;
&lt;p&gt;当省略 &lt;code&gt;Domain&lt;/code&gt; 时，Cookie 被限定在设置它的主机内。加上 &lt;code&gt;Domain=example.test&lt;/code&gt; 则它也允许发给符合条件子域名。只有多个主机确实共享同一应用与信任边界时才使用更宽的形式。&lt;/p&gt;
&lt;p&gt;检视响应而不把真实 Cookie 值打进日志。头部名称不区分大小写，脱敏也必须覆盖 HTTP/2 风格的小写名称：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null https://app.example.test/login &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; tolower($0) ~ /^set-cookie:/ {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; sub(/^[^:]*:[^;]*/, &amp;#34;Set-Cookie: [value redacted]&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; print
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="一起复核这些属性"&gt;一起复核这些属性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Secure&lt;/code&gt; 把 Cookie 传输限制在安全传输上，通常是 HTTPS；它不会加密存储的值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpOnly&lt;/code&gt; 通过 &lt;code&gt;document.cookie&lt;/code&gt; 阻止 JavaScript 访问；它不会阻止浏览器发送该 Cookie。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SameSite&lt;/code&gt; 影响跨站请求，必须匹配应用的导航或嵌入需求。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Path&lt;/code&gt; 控制发送时机，但它不是同一主机上不同应用之间的授权边界。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在现代浏览器中，&lt;code&gt;SameSite=None&lt;/code&gt; 的 Cookie 还需要 &lt;code&gt;Secure&lt;/code&gt;。避免把这对组合复制进不需要跨站用途的应用。&lt;/p&gt;
&lt;p&gt;同站比同源更宽：使用 HTTPS 的兄弟子域名可以同站，同时仍属于不同源。因此，&lt;code&gt;SameSite&lt;/code&gt; 不能替代主机边界或应用自身的 CSRF 防护。Cookie 也不按端口隔离，即便使用 &lt;code&gt;__Host-&lt;/code&gt; 前缀也是如此。&lt;/p&gt;
&lt;h2 id="在其约束契合时使用前缀"&gt;在其约束契合时使用前缀&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;__Secure-&lt;/code&gt; 与 &lt;code&gt;__Host-&lt;/code&gt; 等 Cookie 名称前缀，让支持的浏览器强制额外的要求。&lt;code&gt;__Host-&lt;/code&gt; Cookie 必须使用 &lt;code&gt;Secure&lt;/code&gt;、省略 &lt;code&gt;Domain&lt;/code&gt;、并使用 &lt;code&gt;Path=/&lt;/code&gt;，这使主机专用的意图变得显式。&lt;/p&gt;
&lt;h2 id="用相同作用域测试删除"&gt;用相同作用域测试删除&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;Max-Age=0&lt;/code&gt; 或过去的 &lt;code&gt;Expires&lt;/code&gt; 值，删除同名且域名/路径作用域相同的 Cookie；并非每个属性都参与存储身份。发送删除指令仍需满足前缀与安全来源要求。应保留最初是否省略 &lt;code&gt;Domain&lt;/code&gt; 的选择：让仅主机 Cookie 过期时不要添加 &lt;code&gt;Domain&lt;/code&gt;，并明确指定原来的 &lt;code&gt;Path&lt;/code&gt;。当前 6265bis 草案把仅主机标记也纳入存储身份，因此只匹配界面显示的域名字符串，不足以构成可移植的删除方法。应在目标浏览器中验证删除结果。登出只清除一个作用域时，另一个可能仍然有效；服务器端会话或令牌也可能需要失效处理。&lt;/p&gt;
&lt;h2 id="让静态站点免-cookie"&gt;让静态站点免 Cookie&lt;/h2&gt;
&lt;p&gt;一个真正静态的文档站点通常没有理由设置应用 Cookie。检查有代表性的公共响应是否包含 &lt;code&gt;Set-Cookie&lt;/code&gt;，并在导航后检视浏览器存储，包含第三方脚本的影响。单靠 curl 响应头检查发现不了 JavaScript 设置的 Cookie 或其他浏览器存储；避免增加站点并不需要的存储。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6265.html"&gt;RFC 6265 — HTTP State Management Mechanism&lt;/a&gt;，Cookie 存储与作用域，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie"&gt;MDN: &lt;code&gt;Set-Cookie&lt;/code&gt;&lt;/a&gt;，属性与名称前缀，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html"&gt;HTTPWG Cookie 规范草案&lt;/a&gt;，SameSite 与 Cookie 前缀要求，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CAA 记录应授权你实际使用的签发者</title><link>https://hk.xbcatm.com/zh/notes/caa-records-should-authorize-the-issuer-you-actually-use/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/caa-records-should-authorize-the-issuer-you-actually-use/</guid><description>在限制哪些证书签发机构能为一个域名签发之前，先检视继承的 CAA 策略。</description><content:encoded>&lt;p&gt;认证机构授权（CAA）记录让域名持有者声明，哪些证书颁发机构可以为该域名签发证书。只有当记录与真实续期路径一致、且操作者理解策略如何从父级名称继承时，它才有用。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/caa-policy.detail.zh.svg" alt="跟随别名并逐级向上，遇到第一个相关非空 CAA 记录集就停止，不合并所有父级策略。使用 CA 的授权标识；issuewild 存在时优先控制通配符签发。CAA 约束签发，不执行 HTTPS 信任验证，也不吊销已有证书。应在当前证书健康时编排续期变更。" width="720" height="2164"&gt;&lt;figcaption&gt;CAA 在签发时授权签发者。查询遇到第一个相关非空记录集就停止，通配符标签进一步细化规则。已有证书与 HTTPS 信任属于不同边界，图中签发者仅为示例。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="检视当前策略"&gt;检视当前策略&lt;/h2&gt;
&lt;p&gt;查询确切主机名及其父名称：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; name in www.example.test example.test test&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;== %s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dig +noall +answer CAA &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里用占位域名通过已配置的解析器查询。叶子处空应答不一定代表没有适用策略：查询会跟随别名，并在逐级向上时遇到第一个相关的非空 CAA 记录集就停止，而不是合并所有父级限制。还要查看 DNS 状态与权威信息；只显示答案的空输出可能隐藏 &lt;code&gt;NXDOMAIN&lt;/code&gt; 或 &lt;code&gt;SERVFAIL&lt;/code&gt;。诊断变更时应比较权威应答与递归查询结果。&lt;/p&gt;
&lt;h2 id="匹配-ca-的签发者标识"&gt;匹配 CA 的签发者标识&lt;/h2&gt;
&lt;p&gt;记录使用如 &lt;code&gt;issue&lt;/code&gt; 或 &lt;code&gt;issuewild&lt;/code&gt; 等标签与一个签发者域值：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;example.test. 3600 IN CAA 0 issue &amp;#34;letsencrypt.org&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;该值是授权标识，不是证书里的显示名。在证书颁发机构的当前文档中确认它。没有 &lt;code&gt;issuewild&lt;/code&gt; 时，&lt;code&gt;issue&lt;/code&gt; 也控制通配符签发；存在 &lt;code&gt;issuewild&lt;/code&gt; 时，通配符请求优先适用它。同一标签的多条记录提供备选授权，并不是要求每一家 CA 同时参与。CAA 也不选择某张具体的中间证书。&lt;/p&gt;
&lt;h2 id="在续期前编排-dns-变更"&gt;在续期前编排 DNS 变更&lt;/h2&gt;
&lt;p&gt;在当前证书健康时添加或收紧 CAA。等待先前 TTL 对应的完整时间，查询权威与递归应答，并运行 ACME 客户端支持的暂存或续期测试。准备好回滚记录。&lt;/p&gt;
&lt;p&gt;不要在过期事故期间添加限制性记录，除非签发者路径已验证。一条语法正确但错误的策略，会把自动续期变成硬失败。&lt;/p&gt;
&lt;h2 id="观察签发失败"&gt;观察签发失败&lt;/h2&gt;
&lt;p&gt;CAA 检查发生在签发时，而不是每次 HTTPS 请求。修改 CAA 不会吊销已有证书，也不能证明历史签发符合当时的策略。因此证书监控必须同时覆盖当前过期与续期尝试。把有效的 DNS 应答随 ACME 失败一起记录，而不是只记录一条笼统的授权错误。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8659.html"&gt;RFC 8659 — DNS Certification Authority Authorization&lt;/a&gt;，CAA 查询与属性语义，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/caa/"&gt;Let&amp;rsquo;s Encrypt: CAA&lt;/a&gt;，签发者标识与运维指引，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>证书链是一条应当能打印出来的路径</title><link>https://hk.xbcatm.com/zh/notes/certificate-chain/</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/certificate-chain/</guid><description>把服务器发送的证书列表、客户端信任锚与请求主机名作为 TLS 验证的不同部分分别检查。</description><content:encoded>&lt;p&gt;TLS 客户端必须构建通向既有信任锚的有效证书路径，并验证请求的主机名。打印证书有助于诊断，但单靠打印不能证明这两点。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/cert-chain.detail.zh.svg" alt="服务器发送的叶证书与所需中间证书用于客户端路径构建，信任来自单独配置的信任锚。请求主机名与证书有效期还需分别检查；单靠打印或计数证书不能证明这些结果。" width="720" height="1740"&gt;&lt;figcaption&gt;服务器提供证书，信任来自客户端的信任锚。验证路径、请求主机名与有效期；打印或统计证书数量不能证明这些结果。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="叶证书不是全部"&gt;叶证书不是全部&lt;/h2&gt;
&lt;p&gt;端点首先出示&lt;em&gt;叶证书&lt;/em&gt;。其中包含公钥、有效期，以及用于主机名检查的主体备用名称。公共 CA 的部署通常还应发送构建路径所需的中间证书。缺少中间证书时，没有缓存它、也无法另行取得它的客户端可能失败。&lt;/p&gt;
&lt;p&gt;通常不必发送自签名根证书：信任来自客户端配置的信任锚，而不是服务器把某张证书放进列表。主体名称包含“Root”的交叉签名证书，在所选路径中也可能充当中间证书。因此，证书数量本身不能判断完整性或信任。&lt;/p&gt;
&lt;h2 id="阅读边缘实际发送的列表"&gt;阅读边缘实际发送的列表&lt;/h2&gt;
&lt;p&gt;OpenSSL 文档把 &lt;code&gt;s_client -showcerts&lt;/code&gt; 定义为服务器发送的证书列表，而不是已经验证的链。本笔记先前版本记录过以下站点快照：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;certificate 0: CN=www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; issuer : C=US, O=Let&amp;#39;s Encrypt, CN=YE1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; notAfter : Nov 19 08:37:12 2026 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;certificate 1: CN=YE1 (intermediate)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; issuer : C=US, O=ISRG, CN=Root YE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;certificate 2/3: Root YE / ISRG Root … (CA certificates)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;原记录统计了四个证书块。这里没有保留原始握手日志，本轮审阅也未重现在线握手；这些值应视为历史报告，而不是当前边缘证据。Let&amp;rsquo;s Encrypt 的证书链文档确实列出 YE1 与 Root YE 的交叉签名路径，因此仅凭这些名称不能判定链无效。具体在哪张证书处终止信任，取决于客户端信任库与路径构建。&lt;/p&gt;
&lt;h2 id="把签发授权单独看待"&gt;把签发授权单独看待&lt;/h2&gt;
&lt;p&gt;中间 CA 使根密钥可以保持离线，并将签发操作分离。吊销中间证书可能令依赖它的叶证书路径失效，甚至需要替换证书；并不会自动保全每张叶证书。&lt;/p&gt;
&lt;p&gt;CAA 在签发时授权 CA 文档规定的签发者域标识。它不固定某张中间证书，不改变浏览器信任，也不能证明已有证书符合历史 CAA 策略。&lt;code&gt;dns-caa-records&lt;/code&gt; 笔记讨论的是这项独立的策略检查。&lt;/p&gt;
&lt;h2 id="数量用于盘点验证用于信任"&gt;数量用于盘点，验证用于信任&lt;/h2&gt;
&lt;p&gt;原记录使用的盘点命令如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ echo | openssl s_client -connect www.xbcatm.com:443 -servername www.xbcatm.com -showcerts 2&amp;gt;/dev/null | grep &amp;#39;BEGIN CERT&amp;#39; | wc -l
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它产生只读网络流量，但隐藏诊断信息，且即使前面的命令失败，最后的计数仍可能成功。不要把 &lt;code&gt;4&lt;/code&gt; 或其他固定数量设为健康阈值。只有叶证书时，应检查预期签发路径，而不是认定所有单证书链都无效。&lt;/p&gt;
&lt;p&gt;实际验证时，应使用 OpenSSL 的 &lt;code&gt;-verify_hostname&lt;/code&gt; 与 &lt;code&gt;-verify_return_error&lt;/code&gt; 选项，要求检查主机名并在验证错误时失败，保留诊断信息，同时记录信任库与客户端版本。还要测试实际支持的客户端；一套 OpenSSL 信任库不能证明所有浏览器都兼容。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8446.html"&gt;RFC 8446 — TLS 1.3&lt;/a&gt;，第 4.4.2 节的证书列表顺序与可省略信任锚，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc5280.html"&gt;RFC 5280 — X.509 PKI&lt;/a&gt;，第 6 节的信任锚输入与路径验证，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/faq/"&gt;Let&amp;rsquo;s Encrypt FAQ&lt;/a&gt;，证书兼容性概览，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/certificates/"&gt;Let&amp;rsquo;s Encrypt: Chains of Trust&lt;/a&gt;，YE1 与 Root YE 的交叉签名路径，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt;&lt;/a&gt;，呈现列表与验证选项，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8659.html"&gt;RFC 8659 — CAA&lt;/a&gt;，签发授权与客户端信任的区别，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>审计公共边缘的信任</title><link>https://hk.xbcatm.com/zh/tutorials/audit-a-public-edge/</link><pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/tutorials/audit-a-public-edge/</guid><description>一次只读演练，像细心运维那样检查安全头、TLS 身份、证书链、主机密钥、CAA 与 Cookie 作用域。</description><content:encoded>&lt;p&gt;Web 安全系列把设置当作带作用域与失败模式的契约。本教程检查你有权查询的主机。HTTP、TLS 与 DNS 请求是只读观察，但仍会产生日志和流量。SSH 检查读取你自己的信任记录；部署主机可能不同于网站主机。&lt;/p&gt;
&lt;p&gt;用一次性本地工作目录保存输出。这里没有任何输出被当作刚测得的 &lt;code&gt;www.xbcatm.com&lt;/code&gt; 生产状态；命令通过，只能证明它实际检查的性质。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/tutorial-security.detail.zh.svg" alt="独立安全证据栏区分 SNI 与验证、到期与信任、可变的呈现链、HSTS 主机范围、适用 CAA 签发策略、本地 SSH 记录、Cookie 风险控制和正文 ETag 边界。这些观察不能认证整个服务安全。" width="720" height="4260"&gt;&lt;figcaption&gt;每项观察只支持范围明确的论断。头部存在、证书日期与本地记录不能合成整站安全认证。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="1-从边缘声明的策略开始"&gt;1. 从边缘声明的策略开始&lt;/h2&gt;
&lt;p&gt;抓取 GET 响应，不默默跟随重定向，再检查状态与头部：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS --max-time &lt;span class="m"&gt;20&lt;/span&gt; -D headers.txt -o body.html &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat headers.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;仓库预期策略包含以下示意字段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Security-Policy: default-src &amp;#39;self&amp;#39;; base-uri &amp;#39;self&amp;#39;; form-action &amp;#39;none&amp;#39;; frame-ancestors &amp;#39;none&amp;#39;; object-src &amp;#39;none&amp;#39;; script-src &amp;#39;none&amp;#39;; style-src &amp;#39;self&amp;#39;; img-src &amp;#39;self&amp;#39; data:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Strict-Transport-Security: max-age=31536000; includeSubDomains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;script-src 'none'&lt;/code&gt; 是适合本站无脚本要求的严格选择，不是所有静态站的通用配方。CSP 由浏览器执行；报告模式不会阻止执行。应读完整策略，检查更具体的脚本指令，并比较普通页与错误路由。&lt;code&gt;nosniff&lt;/code&gt; 限制 MIME 处理，尤其是脚本和样式表目标，但不证明正文的媒体类型正确。&lt;/p&gt;
&lt;p&gt;HSTS 从有效 HTTPS 响应学习，作用于发送它的主机。&lt;code&gt;www.example.com&lt;/code&gt; 的 &lt;code&gt;includeSubDomains&lt;/code&gt; 覆盖该名称的后代，不覆盖兄弟 &lt;code&gt;api.example.com&lt;/code&gt; 或父域 &lt;code&gt;example.com&lt;/code&gt;。采用较长时长前先清点实际范围。代理增加或移除头部会改变浏览器契约，因此产物检查与边缘检查需要分开。&lt;/p&gt;
&lt;h2 id="2-读取证书日期前先验证-tls-身份"&gt;2. 读取证书日期前先验证 TLS 身份&lt;/h2&gt;
&lt;p&gt;SNI 选择目标虚拟主机，不验证证书名称。应让验证失败成为致命错误，并显式启用主机名验证。使用与受测客户端相适应、保持更新的信任库。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl s_client -connect &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;:443&amp;#34;&lt;/span&gt; -servername &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -verify_hostname &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -verify_return_error -showcerts &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;/dev/null &amp;gt; tls.txt 2&amp;gt; tls.log
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Continue only if the command succeeded; preserve tls.log on failure.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl x509 -in tls.txt -noout -subject -issuer -dates -ext subjectAltName
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;分别检查每步结果，或在失败即停止的 shell 设置下运行。隐藏 stderr 或把结果管道给成功的 &lt;code&gt;x509&lt;/code&gt;，可能掩盖握手失败。应检查 &lt;code&gt;subjectAltName&lt;/code&gt; 的 DNS 名称；显示的 subject CN 不足以完成现代服务身份验证。某些库版本仍保留旧式 CN 回退，因此也要明确检查预期 SAN 存在。合法匹配的通配符可以有效，不相关的证书则不行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;notAfter&lt;/code&gt; 给出到期时刻，不证明信任链有效、主机名匹配，也不说明检查过吊销。记录日期与测量出的安全窗口，不要依赖记忆。OpenSSL 的结果描述其已配置的信任库与检查，不代表所有浏览器。&lt;/p&gt;
&lt;h2 id="3-读取呈现链不虚构固定数量"&gt;3. 读取呈现链，不虚构固定数量&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;-showcerts&lt;/code&gt; 输出的是服务器发送的证书，不是它们已经组成已验证链的声明。以下命令仅显示数量：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -c &lt;span class="s1"&gt;&amp;#39;BEGIN CERTIFICATE&amp;#39;&lt;/span&gt; tls.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不存在“必须四张”的要求。服务器通常提供叶证书及必要的中间证书；根一般属于客户端信任库，不必发送。数量随 CA 与所选路径变化。只有一张时值得调查是否缺少中间证书，但数量本身既不代表成功，也不代表失败。&lt;/p&gt;
&lt;p&gt;应使用第 2 步真实的验证结果，再调查签发者或 SAN 变化、中间证书缺失、到期及信任库差异。中间证书轮换可能只是正常 CA 维护；数量没变也可能出错。这个计数命令不会自动与任何预期值作比较。&lt;/p&gt;
&lt;h2 id="4-重新验证已抓取的表示"&gt;4. 重新验证已抓取的表示&lt;/h2&gt;
&lt;p&gt;缓存是另一份契约，不是 TLS 身份证据。按照 &lt;a href="https://hk.xbcatm.com/zh/tutorials/http-edge-walkthrough/"&gt;HTTP 演练&lt;/a&gt; 抓取当前 ETag，并对相同表示选择发出条件请求。匹配的条件 GET 可以返回 304；两次请求之间发生发布，则可能合法返回 200。旧的硬编码校验器不是变化站点的可复用测试夹具。&lt;/p&gt;
&lt;p&gt;无脚本策略不等于 CSP 永远不变。安全头可以独立于 HTML 改变，正文 ETag 也不是所有响应头的指纹。发布后应再次记录真实策略。&lt;/p&gt;
&lt;h2 id="5-找到适用的-caa-策略"&gt;5. 找到适用的 CAA 策略&lt;/h2&gt;
&lt;p&gt;CAA 约束合规证书机构的签发，不是证书固定，也不会追溯地使已签发证书失效。先检查查询名称及其祖先，保留响应状态，避免把 DNS 失败误当成空答案。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig www.xbcatm.com CAA +noall +comments +answer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig xbcatm.com CAA +noall +comments +answer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;www&lt;/code&gt; 的空答案不代表签发无限制。RFC 8659 会向上寻找第一个适用的非空 CAA 集合，每次查找按 DNS 规则跟随别名；需要时继续检查祖先。检查 &lt;code&gt;issue&lt;/code&gt;，涉及通配符签发时还要看 &lt;code&gt;issuewild&lt;/code&gt;。只有 &lt;code&gt;iodef&lt;/code&gt; 报告地址并不限制签发。若没有适用限制，CAA 不增加签发者限制，但常规域名验证与 CA 规则仍适用。&lt;/p&gt;
&lt;p&gt;应将 CA 文档规定的 CAA 标识与适用策略比较，不是只看叶证书中供人阅读的 issuer CN。今天的 CAA 结果证明的是今天的 DNS 策略，不证明证书签发时存在什么策略。&lt;/p&gt;
&lt;h2 id="6-检查部署主机已记录的-ssh-身份"&gt;6. 检查部署主机已记录的 SSH 身份&lt;/h2&gt;
&lt;p&gt;使用真实 SSH 目标；非默认端口需要方括号主机加端口形式。&lt;code&gt;ssh-keygen -F&lt;/code&gt; 能找到直接 grep 主机名会遗漏的哈希 known-host 条目。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;deploy_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy.example.test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen -F &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$deploy_host&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.ssh/known_hosts&amp;#34;&lt;/span&gt; &amp;gt; host-record.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen -lf host-record.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;把指纹与独立可信记录比较，例如提供商控制台或已验证的管理员渠道。这只读取本地记录，不证明远端现在呈现什么密钥。网络扫描到的密钥本身也不能完成身份认证。更新信任前先调查变化；重建只是可能原因，不是自动接受的许可。&lt;/p&gt;
&lt;h2 id="7-cookie-作用域不超过必要范围"&gt;7. Cookie 作用域不超过必要范围&lt;/h2&gt;
&lt;p&gt;检查已抓取响应中的所有 &lt;code&gt;Set-Cookie&lt;/code&gt;，以及相关重定向或应用路由。一个页面没设置 Cookie，不证明整站从不设置。主机专用 Cookie 省略 &lt;code&gt;Domain&lt;/code&gt;；添加父域 Domain 会扩大接收它的主机范围。&lt;code&gt;Path&lt;/code&gt; 控制发送范围，但不是防御同源代码的安全边界。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Secure&lt;/code&gt; 限制传输，&lt;code&gt;HttpOnly&lt;/code&gt; 阻止脚本访问 Cookie，&lt;code&gt;SameSite&lt;/code&gt; 控制规定的跨站发送行为。它们针对不同风险，不能互相代替；现代浏览器中的 &lt;code&gt;SameSite=None&lt;/code&gt; 需要 &lt;code&gt;Secure&lt;/code&gt;。对本站无账户的内容模型，扩大作用域前应先问为什么需要 Cookie。共享审计记录时应隐藏 Cookie 值。&lt;/p&gt;
&lt;h2 id="保留范围明确的审计记录"&gt;保留范围明确的审计记录&lt;/h2&gt;
&lt;p&gt;记录 UTC 时间、客户端版本、请求及最终 URL、状态、强制策略头、TLS 验证结果与日期、适用 CAA 查询及 SSH 记录来源。区分观察成功、意外结果与未执行检查。这几项观察不止“两条命令”，也不能证明整个服务安全。&lt;/p&gt;
&lt;p&gt;证书、DNS、代理或部署变化后，重做相关检查。保留旧结果以发现漂移；论断依赖浏览器执行时，应另行测试真实浏览器行为。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://fetch.spec.whatwg.org/#x-content-type-options-header"&gt;WHATWG Fetch Standard&lt;/a&gt;，nosniff 处理与请求目标，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt;&lt;/a&gt;，SNI、主机名验证、致命验证错误与呈现链，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9525.html"&gt;RFC 9525：Service Identity in TLS&lt;/a&gt;，基于 SAN 的服务身份与通配符匹配，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8659.html"&gt;RFC 8659：CAA&lt;/a&gt;，适用记录集搜索与签发策略，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6797.html"&gt;RFC 6797：HSTS&lt;/a&gt;，主机范围与策略处理，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://man.openbsd.org/ssh-keygen"&gt;OpenSSH &lt;code&gt;ssh-keygen&lt;/code&gt;&lt;/a&gt;，已知主机查找与指纹显示，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.w3.org/TR/CSP3/"&gt;W3C：CSP Level 3&lt;/a&gt;，强制与报告模式及脚本指令回退（工作草案），已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie"&gt;MDN：Set-Cookie&lt;/a&gt;，Cookie 属性与边界，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>