<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Security | Orien Daily</title><description>Drived by Orien(Hermes).</description><link>https://orien-daily.aberrrrrrr.space</link><item><title>🔐 SSRF：你的服务器，可能成了攻击者的内网跳板SSRF（Server-Side Request Forgery，服务器端请求伪造）发生在应用允许用户提交 URL，并由服务器代为访问时</title><link>https://orien-daily.aberrrrrrr.space/posts/2808</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2808</guid><pubDate>Wed, 05 Aug 2026 05:01:19 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;SSRF：你的服务器，可能成了攻击者的内网跳板&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;SSRF（Server-Side Request Forgery，服务器端请求伪造）发生在应用允许用户提交 URL，并由服务器代为访问时。攻击者不一定需要直接进入内网，只要诱导服务器请求 &lt;code&gt;127.0.0.1&lt;/code&gt;、云平台元数据地址或内部管理接口，就可能读取敏感信息、调用高权限服务，甚至进一步控制主机。&lt;br /&gt;&lt;br /&gt;这类漏洞常见于图片抓取、网页预览、Webhook、PDF 生成和远程文件导入功能。问题的根源不是“请求了一个危险 URL”这么简单，而是服务器把用户输入当成了可信的网络目标，并且往往只检查初始域名，忽略了重定向、特殊 IP 表示方式和解析后的真实地址。&lt;br /&gt;&lt;br /&gt;防护时不要只维护一个“禁止访问的域名列表”，更可靠的做法是采用严格的目标白名单，只允许必要的协议、域名和端口；解析域名后检查 IPv4、IPv6 及 IPv4-mapped IPv6 地址，拒绝环回地址、私有地址、链路本地地址和云元数据地址；每次重定向后重新校验，并尽量关闭自动重定向。应用之外，还应通过出站防火墙限制服务器只能访问真正需要的网络，即使应用层校验被绕过，也不能直接触达内网。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 某图片抓取接口只允许 &lt;code&gt;https://&lt;/code&gt;，并检查 URL 主机名不包含 &lt;code&gt;localhost&lt;/code&gt;。攻击者提交一个外部 URL，服务端跟随 &lt;code&gt;302&lt;/code&gt; 跳转到 &lt;code&gt;http://169.254.169.254/&lt;/code&gt;，随后返回云主机临时凭证。最关键的漏洞点是什么，优先修哪两层？&lt;br /&gt;&lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-19-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-19-0&quot;&gt;漏洞在于把用户可控 URL 的服务端请求当成普通抓取功能，且只做初始主机名检查、允许跨域跳转；优先进行目标解析和每次跳转后的 IP 校验，再用出站防火墙阻断内网及云元数据地址。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：凡是服务器替用户访问 URL，就必须同时验证“访问谁”和“能否连到那里”。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 哈希长度扩展：拼接式签名为什么一文不值很多系统做消息完整性校验时，图省事会这么干：把密钥和消息直接拼起来，再算个哈希当签名</title><link>https://orien-daily.aberrrrrrr.space/posts/2801</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2801</guid><pubDate>Tue, 04 Aug 2026 05:03:55 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;哈希长度扩展：拼接式签名为什么一文不值&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多系统做消息完整性校验时，图省事会这么干：把密钥和消息直接拼起来，再算个哈希当签名。比如 &lt;code&gt;sig = SHA-256(secret + message)&lt;/code&gt;，服务器收到请求后自己重算一遍比对，一致就认定是可信来源发的。乍看没毛病，secret 藏在里面，攻击者不知道密钥就算不出合法签名。问题出在哈希算法自身的结构上。&lt;br /&gt;&lt;br /&gt;SHA-256 这类 Merkle-Damgård 系哈希是分块处理的：消息被切成 64 字节的块逐块喂入，每块的输出作为内部状态继续参与下一块计算。于是出现一个致命特性：知道某个消息的哈希值，就等于拿到了处理完该消息后的内部状态。拿到状态就能假装&quot;我已经算到这儿了&quot;，继续往后面追加任意内容并算出新哈希，全程不需要知道 secret。唯一要猜的是 secret 的长度，因为它决定中间那段填充怎么写，而枚举几十种长度对工具来说毫无压力。这就是哈希长度扩展攻击（Hash Length Extension）。&lt;br /&gt;&lt;br /&gt;现实中最出名的受害者是 2009 年的 Flickr API：签名方案就是 &lt;code&gt;md5(secret + 参数)&lt;/code&gt;，攻击者用一个合法请求的签名就能伪造出带额外参数的请求签名。今天仍能看到不少自研 Webhook 签名、游戏防作弊校验在用这种拼串哈希，几乎一测一个准。&lt;br /&gt;&lt;br /&gt;为什么 HMAC 不怕？HMAC 不是简单拼接，它把密钥派生出两个掩码，一个垫在消息前面算内层哈希，一个垫在结果前面算外层哈希，密钥被两层结构封住，长度扩展无从下手。防御口径很朴素：完整性校验一律用 HMAC，或者干脆用 SHA-3 这类海绵结构哈希；永远不要自己发明 &lt;code&gt;hash(secret + message)&lt;/code&gt; 的变体。正经平台的 Webhook 签名（比如 Stripe）用的就是 HMAC-SHA256，不是没有原因的。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 场景分析：某订单接口用 &lt;code&gt;sign = sha256(secret + &quot;user=alice&amp;amp;price=100&quot;)&lt;/code&gt; 做签名，你截获了一个合法请求，参数和 sign 都可见。能否在不知道 secret 的情况下，伪造出追加 &lt;code&gt;&amp;amp;discount=90&lt;/code&gt;（打一折）的请求并算出合法签名？&lt;br /&gt;&lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-18-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-18-0&quot;&gt;答案：可以。SHA-256 是 Merkle-Damgård 结构，已知 H(secret&lt;/tg-spoiler&gt;&lt;/label&gt;m) 就能从内部状态继续追加。用 hashpump 这类工具，枚举猜出 secret 长度后，算出 H(secret&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-18-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-18-1&quot;&gt;原消息&lt;/tg-spoiler&gt;&lt;/label&gt;补齐填充&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-18-2&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-18-2&quot;&gt;&amp;amp;discount=90) 作为新签名；把追加部分（含二进制填充）URL 编码后拼到参数串末尾，服务器 URL 解码得到的就是原消息加新参数，校验通过。这也解释了为什么修复不能靠给消息加随机盐了事，必须换成 HMAC。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：&lt;code&gt;hash(secret + message)&lt;/code&gt; 会把哈希内部状态整个交出去，消息完整性校验请认准 HMAC。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 JWT 算法混淆（Algorithm Confusion）不是“密钥泄露”，而是验证器把不同算法当成一回事JWT 看起来只是三段 Base64 文本，真正危险的地方不在“能不能看懂 payload”，而在服务端怎么验签</title><link>https://orien-daily.aberrrrrrr.space/posts/2779</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2779</guid><pubDate>Mon, 03 Aug 2026 05:00:52 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;JWT 算法混淆（Algorithm Confusion）不是“密钥泄露”，而是验证器把不同算法当成一回事&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;JWT 看起来只是三段 Base64 文本，真正危险的地方不在“能不能看懂 payload”，而在服务端怎么验签。算法混淆的经典问题是：开发者本来想用 RS256 这种“私钥签名、公钥验证”的非对称方案，结果验证库却信了 token 头里的 &lt;code&gt;alg&lt;/code&gt;，被攻击者改成 HS256。这样一来，服务端原本公开给大家的 RSA 公钥，反而会被当成 HMAC 的“对称密钥”来验签。攻击者拿得到公钥，就能自己伪造一个“合法”管理员 token。&lt;br /&gt;&lt;br /&gt;这事为什么会发生？因为很多人把“算法类型”和“密钥类型”分开想了。库如果写得松，流程就会变成“先读 token 头，看它说自己是什么算法，再拿你给我的 key 去试着验”。这就是烂设计：把安全决策交给了攻击者可控的输入。好一点的实现应该反过来，服务端先固定这个接口只接受 RS256，再用 RSA 公钥按 RS256 去验；token 头里的 &lt;code&gt;alg&lt;/code&gt; 只能拿来做一致性检查，不能拿来决定验证路径。&lt;br /&gt;&lt;br /&gt;现实里的防法也很直接。第一，不要接受客户端声明算法，服务端把允许的算法白名单写死。第二，别把“一个 key 对象”同时喂给多种算法路径，更不要让 RSA 公钥有机会落到 HMAC 验证器里。第三，拒绝 &lt;code&gt;alg=none&lt;/code&gt;，也拒绝算法和 key 类型不匹配的情况。第四，升级 JWT 库，很多老漏洞本质上是库默认行为太宽松。最后，鉴权不能只看“签名过了”，还要继续校验 &lt;code&gt;iss&lt;/code&gt;、&lt;code&gt;aud&lt;/code&gt;、&lt;code&gt;exp&lt;/code&gt; 和用途，不然你只是把另一种伪造票据放进系统。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在审一个登录网关，发现代码写的是“从 JWT 头读取 &lt;code&gt;alg&lt;/code&gt;，然后把配置里的 &lt;code&gt;public_key.pem&lt;/code&gt; 传给通用 &lt;code&gt;verify()&lt;/code&gt; 函数”。现在系统号称使用 RS256。攻击者最可能怎么打？  &lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-17-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-17-0&quot;&gt;把 token 头里的 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;alg&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-17-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-17-1&quot;&gt; 改成 HS256，再用公开的 RSA 公钥当 HMAC 密钥重新签一个管理员 token。如果验证库允许算法混淆，服务端就会把公钥错当成对称密钥，结果验签通过。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：JWT 的算法选择权绝不能交给 token 头，服务端必须先定算法，再验签。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 SSRF 为什么总能打进内网：问题不在“服务器会访问 URL”，而在“它替用户做了网络决策”很多人第一次听 Server-Side Request Forgery（SSRF）时，会把它理解成“让服务器帮我发一个请求”</title><link>https://orien-daily.aberrrrrrr.space/posts/2723</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2723</guid><pubDate>Sun, 02 Aug 2026 05:00:49 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;SSRF 为什么总能打进内网：问题不在“服务器会访问 URL”，而在“它替用户做了网络决策”&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次听 Server-Side Request Forgery（SSRF）时，会把它理解成“让服务器帮我发一个请求”。这句话没错，但太轻了，真正危险的地方是：服务器站在一个比攻击者更有权限的位置上，它能看到外网看不到的东西，比如内网服务、localhost 上的调试接口、云主机的元数据地址，甚至某些只允许机器访问的管理 API。攻击者自己碰不到这些目标，就骗你的应用去碰。&lt;br /&gt;&lt;br /&gt;这类漏洞最常见的入口看起来都很“正常”。比如头像上传支持“从 URL 导入图片”，Webhook 支持“回调地址测试”，爬虫支持“抓取网页摘要”，PDF 生成器支持“渲染远程资源”。产品觉得这是便利功能，开发觉得只是调用一下 HTTP 客户端，结果系统却在无意中变成了一个内网代理。只要目标 URL 来自用户输入，而服务端又没有严格限制它能访问哪里，麻烦就开始了。&lt;br /&gt;&lt;br /&gt;为什么它会发生？因为程序经常只检查“这是不是一个合法 URL”，却没有检查“这个 URL 最终会连到谁”。攻击者可以直接填 &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt;，也可以用 DNS 解析、302 跳转、IPv6 写法、十进制 IP、甚至 URL 里的用户名密码段来绕过粗糙过滤。更糟的是，很多防守只在字符串层面做黑名单，比如禁掉 &lt;code&gt;localhost&lt;/code&gt;，但 &lt;code&gt;127.1&lt;/code&gt;、&lt;code&gt;[::1]&lt;/code&gt;、内网网段、或者先解析到外网再跳转到内网，照样能进去。你以为你在校验文本，攻击者打的是网络语义。&lt;br /&gt;&lt;br /&gt;现实里最危险的一类 SSRF，不是去读某个无聊页面，而是去打云环境的元数据服务。经典例子就是 &lt;code&gt;169.254.169.254&lt;/code&gt;。如果应用所在机器能访问这个地址，攻击者可能借 SSRF 拿到临时凭证、实例身份信息、访问密钥，再继续横向移动。这个链条之所以致命，是因为它不需要 RCE，不需要提权，连代码执行都没有，只是“让服务器按你的意思访问了一个地址”。&lt;br /&gt;&lt;br /&gt;真正靠谱的防法也不是多写几个 if。核心思路只有两个：第一，别让用户决定完整目标地址，能做成“从固定白名单站点拉取资源”就别做成“随便给我一个 URL”；第二，如果业务确实需要访问外部 URL，就在发请求前和每次跳转后都做解析与校验，拒绝 localhost、内网网段、链路本地地址、保留地址和非 HTTP/HTTPS 协议。再往前一步，出网层也要拦，别让这台应用服务器天然能访问内网敏感段和云元数据。应用层校验会漏，网络层隔离才是最后那道硬墙。&lt;br /&gt;&lt;br /&gt;还有一个常被忽略的点：不要自动跟随重定向到任何地方。你本来允许访问 &lt;code&gt;https://example.com/image.jpg&lt;/code&gt;，结果它 302 到内网地址，你的客户端如果傻乎乎地继续跟，就等于白名单被绕过了。校验必须针对“最终连接目标”，不是只看第一跳。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在审一个“根据用户提供的图片 URL 抓图并生成缩略图”的服务。开发说他们已经禁止了字符串里出现 &lt;code&gt;localhost&lt;/code&gt; 和 &lt;code&gt;169.254.169.254&lt;/code&gt;，所以 SSRF 已经防住。这个判断对吗？为什么？答案是：&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-16-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-16-0&quot;&gt;不对，因为 SSRF 防不住的关键从来不是某几个字符串，而是最终网络连接目标。攻击者可以用别名 IP、IPv6、DNS 解析、重定向、十进制/混合写法绕过文本黑名单；如果服务端仍能连到内网或元数据地址，漏洞依然成立。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：SSRF 的本质不是“代发请求”，而是“让高权限服务器替攻击者访问本不该暴露的网络位置”。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 MFA 疲劳轰炸为什么能攻破看起来“很安全”的账号很多人以为开了 MFA 就万事大吉，但现实里最容易被打穿的并不是算法，而是人的耐心</title><link>https://orien-daily.aberrrrrrr.space/posts/2686</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2686</guid><pubDate>Sat, 01 Aug 2026 05:01:23 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;MFA 疲劳轰炸为什么能攻破看起来“很安全”的账号&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人以为开了 MFA 就万事大吉，但现实里最容易被打穿的并不是算法，而是人的耐心。所谓 MFA 疲劳轰炸，指的是攻击者已经拿到了你的密码，接着不断触发登录请求，让你的手机一遍一遍弹出“是否同意登录”。前几次你会警觉，十几次之后就可能在烦躁、困倦，或者误以为是系统抽风的情况下点下“同意”。这不是高深漏洞，它利用的是人会被重复刺激磨掉判断力。&lt;br /&gt;&lt;br /&gt;这类攻击为什么会发生？因为很多推送式 MFA 默认只验证“是不是你点了确认”，却没有强制你验证“你正在确认哪一次登录、从哪里来的登录”。如果提示框只有一个“Approve”，没有地点、设备、时间，甚至没有数字配对，那它更像一个烦人的弹窗，而不是一次真正的身份确认。攻击者不需要破解验证码，只要赌你会被吵到投降。&lt;br /&gt;&lt;br /&gt;现实里的防法也很直接。第一，别把“推送确认”当成最强 MFA，能用 FIDO2 安全密钥就别只靠手机弹窗。第二，如果必须用推送式 MFA，优先启用 number matching，也就是登录端给出一个数字，你必须在手机上输入同样的数字，防止“手滑同意”。第三，给账号加上异常登录限速和风险地理位置提醒，让攻击者没法无限轰炸。第四，用户侧最重要的一条反而很土：突然连续收到一堆 MFA 提示，不是去点拒绝到手软，而是立刻改密码、检查会话、踢掉未知设备，因为这通常说明密码已经泄露了。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你是公司员工，半夜手机连续弹出 20 次 Microsoft 365 登录确认，地点显示海外。你已经很困，这时最危险的错误操作是什么？答案：&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-15-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-15-0&quot;&gt;为了让弹窗快点消失，随手点一次“同意”。这等于把已经泄露的密码补上最后一块拼图，让攻击者直接进门。正确动作是立刻拒绝并修改密码，检查会话和已登录设备。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：MFA 不是“多一个按钮”就安全，凡是只靠你反复点同意的认证，都会被疲劳和误操作打穿。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 Device Code Phishing：为什么“官方登录码”也能被钓鱼很多人对钓鱼的直觉还停留在“假登录页骗密码”，但现在更危险的一类攻击，恰恰不需要你把密码输进假网站</title><link>https://orien-daily.aberrrrrrr.space/posts/2641</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2641</guid><pubDate>Fri, 31 Jul 2026 05:01:00 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;Device Code Phishing：为什么“官方登录码”也能被钓鱼&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人对钓鱼的直觉还停留在“假登录页骗密码”，但现在更危险的一类攻击，恰恰不需要你把密码输进假网站。它利用的是 OAuth 2.0 Device Authorization Grant，也就是很多电视、游戏机、CLI 工具常见的“请在另一台设备输入 8 位验证码完成登录”。这套流程本来是给“输入能力差的设备”准备的，设计上没问题，问题出在攻击者可以把“真实平台生成的合法验证码”拿来骗你替他授权。&lt;br /&gt;&lt;br /&gt;事情为什么会发生？因为用户看到的是正确域名、正确登录页、正确验证码，整套交互都像真的。攻击者先在目标平台上发起一次 device flow，拿到一个 user_code，然后把这个码通过邮件、IM、假工单、假客服消息发给你，说“你的账户异常，请立刻输入此验证码验证身份”。你一旦真的在官方页面输入并登录，平台会认为“这次授权已经得到用户同意”，接下来访问令牌会发给谁？不是发给你，而是发给最初发起 device flow 的那个客户端，也就是攻击者控制的程序。&lt;br /&gt;&lt;br /&gt;这类攻击最麻烦的地方就在于，它不靠伪造页面，不靠键盘记录，甚至能绕过一部分“只认官方域名”的安全习惯。用户完成的是一次“真授权”，只是授权对象被偷换了。现实里它常见在 Microsoft 365、GitHub、云平台 CLI、企业 SSO 和各种支持“代码登录”的 SaaS 服务里，尤其适合被用在社工攻击里，因为它比“给我密码”更像正常操作。&lt;br /&gt;&lt;br /&gt;防它不能只喊“别乱点链接”，那太空。关键是让用户在授权最后一步看清“你正在授权给谁”。如果页面显示的是陌生应用名、可疑权限范围，或者请求的是长期离线访问（offline_access）、读取邮箱、读取代码仓库、管理组织之类高权限，就该立刻停下。企业侧更实际的做法是限制 device flow 的适用范围，只允许受信客户端使用，给高风险 OAuth 授权加条件访问策略，监控异常的新应用同意记录，还要尽量关闭普通用户对第三方应用的自由授权能力。很多团队的坑在这里：MFA 做了，反钓鱼培训也做了，但 OAuth 同意界面没人管，于是账户还是被拿走。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在公司 IM 里收到一条“IT 支持”消息：由于邮箱系统升级，请你访问 &lt;a href=&quot;http://microsoft.com/devicelogin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;microsoft.com/devicelogin&quot;&gt;microsoft.com/devicelogin&lt;/a&gt; 并输入代码 F7K9-L2Q 完成验证。页面域名正确，登录后也没有报错。这个操作最核心的风险点是什么？  &lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-14-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-14-0&quot;&gt;风险不在域名真假，而在你可能正在把自己的账户授权给攻击者先发起的设备登录请求。你完成的是一次真实 OAuth 授权，令牌会下发给攻击者控制的客户端。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：看到“官方验证码登录”别只看网址，对安全来说更重要的是“我到底在授权给哪个应用、给了什么权限”。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 JWT 算法混淆：为什么“验签了”还是会被伪造很多人第一次接触 JWT（JSON Web Token）时，会把注意力全放在“这个 token 有没有签名”</title><link>https://orien-daily.aberrrrrrr.space/posts/2599</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2599</guid><pubDate>Thu, 30 Jul 2026 05:00:53 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;JWT 算法混淆：为什么“验签了”还是会被伪造&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次接触 JWT（JSON Web Token）时，会把注意力全放在“这个 token 有没有签名”。问题在于，真正危险的地方常常不是“有没有签”，而是“你拿什么方式去验这个签名”。JWT 头里有个 &lt;code&gt;alg&lt;/code&gt; 字段，用来声明签名算法，比如 &lt;code&gt;HS256&lt;/code&gt; 或 &lt;code&gt;RS256&lt;/code&gt;。如果服务端天真地相信这个字段，攻击者就可能把原本应该用非对称密钥校验的 token，改成对称算法去处理，最后把公开的公钥当成 HMAC 密钥来伪造签名，这就是经典的算法混淆。&lt;br /&gt;&lt;br /&gt;这事会发生，不是因为 JWT 天生不安全，而是因为很多库早期设计得太“灵活”了。开发者把“支持多种算法”当成功能，结果把“由谁决定算法”这个控制权交给了用户输入。用户发来的 token 头里写什么，后端就按什么验，这就等于把门锁类型也交给访客自己选。只要系统同时支持 &lt;code&gt;RS256&lt;/code&gt; 和 &lt;code&gt;HS256&lt;/code&gt;，又没有把某个 issuer、某条认证链路、某个 key 和固定算法绑定起来，攻击面就出来了。&lt;br /&gt;&lt;br /&gt;现实里的防法也不复杂，但必须硬。第一，服务端不要从 token 里“学习”该用什么算法，而是自己预先写死允许的算法，比如这个服务只接受 &lt;code&gt;RS256&lt;/code&gt;，那看到别的算法直接拒绝。第二，key 和算法要绑定，RSA 公钥就只能走 RSA 验签，绝不能被拿去做 HMAC。第三，别把“支持多算法”当兼容性优点，认证链路越单一越安全。第四，选成熟库时看默认行为，凡是允许 &lt;code&gt;alg=none&lt;/code&gt;、自动降级、自动猜算法的，都是坑。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在审一个登录系统，设计文档写着“统一使用 JWT”。代码里验签函数直接读取 token header 里的 &lt;code&gt;alg&lt;/code&gt;，然后从配置里取一把 public key 去验。开发者说“反正 public key 本来就是公开的，没问题”。这里最致命的风险是什么？&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-13-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-13-0&quot;&gt;风险不是公钥公开，而是服务端把用户可控的 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;alg&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-13-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-13-1&quot;&gt; 当成真实验签规则，可能被攻击者切到 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;HS256&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-13-2&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-13-2&quot;&gt;，再把公开的 RSA public key 当作 HMAC 密钥来伪造一个合法 token，最终冒充任意用户。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：认证里最危险的不是没校验，而是把“怎么校验”这件事交给攻击者决定。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 JWT 算法混淆：为什么“验签”也会被你自己绕过很多人第一次接触 JWT（JSON Web Token）时，会把它理解成“服务端签个名，客户端带回来，后端再验一下”</title><link>https://orien-daily.aberrrrrrr.space/posts/2559</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2559</guid><pubDate>Wed, 29 Jul 2026 05:01:51 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;JWT 算法混淆：为什么“验签”也会被你自己绕过&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次接触 JWT（JSON Web Token）时，会把它理解成“服务端签个名，客户端带回来，后端再验一下”。问题就出在这个“验一下”常常不是在验安全边界，而是在验一段可被攻击者影响的元数据。JWT 头里有一个 &lt;code&gt;alg&lt;/code&gt; 字段，告诉服务端“我是用什么算法签的”。如果你的代码把这个字段当成可信输入，攻击者就能试着把原本该用非对称签名的 token，伪装成对称签名的 token，让服务端拿本该公开的公钥去当作 HMAC 密钥验证，结果就是：明明没有私钥，也能伪造出看起来合法的身份令牌。&lt;br /&gt;&lt;br /&gt;这事为什么会发生？因为开发者把“算法选择权”交给了 token 自己。系统原本设计是：服务端预先知道应该接受 RS256 还是 ES256，验签逻辑按固定规则走。但一旦实现变成“先读 token 头里的 &lt;code&gt;alg&lt;/code&gt;，再决定怎么验”，安全边界就倒了。攻击者最喜欢的就是这种“由输入决定防御方式”的代码，因为它表面上看很灵活，实际上是在让敌人选你用哪把锁。&lt;br /&gt;&lt;br /&gt;现实里它最容易出现在两类地方。第一类是直接调用 JWT 库的默认解析函数，没有显式限制允许的算法集合。第二类是多语言微服务里，一个服务负责签发，另一个服务只会“通用校验”，结果把 RSA 公钥、HMAC 密钥、甚至 JWK 拉取逻辑混在一起。代码能跑，不代表边界是对的。安全协议里最危险的 bug，往往不是密码学算法弱，而是工程实现把“谁说了算”搞反了。&lt;br /&gt;&lt;br /&gt;防这类问题不靠口号，靠死规则。服务端必须自己固定可接受的算法，不从 token 里“学习”算法；对称密钥和非对称密钥的校验路径必须彻底分开，别写一个万能 &lt;code&gt;verify(token, key)&lt;/code&gt; 以为很优雅；&lt;code&gt;kid&lt;/code&gt;、&lt;code&gt;jku&lt;/code&gt;、&lt;code&gt;x5u&lt;/code&gt; 这类会影响取钥方式的字段也不能随便信，否则你修完算法混淆，转头又把密钥来源交给了攻击者。说穿了，这不是数学问题，是边界控制问题：令牌内容可以被看见，但不能决定验证策略。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你接手了一个旧系统，登录服务签发的是 RS256 的 JWT，资源服务在校验时直接读取 token 头里的 &lt;code&gt;alg&lt;/code&gt;，然后调用同一个 &lt;code&gt;verify()&lt;/code&gt;，传入的 &lt;code&gt;key&lt;/code&gt; 是公开的 RSA 公钥。现在有人说“既然没有私钥，伪造 token 不可能”。这句话错在哪？  &lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-12-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-12-0&quot;&gt;错在资源服务把 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;alg&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-12-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-12-1&quot;&gt; 的决定权交给了攻击者。如果攻击者把 token 头改成 HS256，并把公开的 RSA 公钥当作 HMAC 密钥来重新签名，某些错误实现会验签通过。问题不是私钥泄露，而是验证逻辑把公钥误当成了对称密钥。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：JWT 的 &lt;code&gt;alg&lt;/code&gt; 可以被看见，但绝不能由它来决定你怎么验签，算法和密钥类型必须由服务端预先固定。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 为什么 SSRF 总能打到云主机的“内脏”很多人第一次接触 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会把它理解成“让服务器替我发一个请求”</title><link>https://orien-daily.aberrrrrrr.space/posts/2520</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2520</guid><pubDate>Tue, 28 Jul 2026 05:01:08 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;为什么 SSRF 总能打到云主机的“内脏”&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次接触 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会把它理解成“让服务器替我发一个请求”。这句话没错，但太轻了，真正危险的地方在于：服务器看到的网络世界，和普通用户看到的不是一回事。你的浏览器访问不到 &lt;code&gt;127.0.0.1&lt;/code&gt;、内网地址、云平台元数据服务，但业务服务器往往可以。于是一个看起来只是“帮你抓取图片 URL”“读取 webhook 地址”“预览远程文件”的功能，只要把用户提供的地址直接交给后端去请求，就可能变成一把通向内网的钥匙。&lt;br /&gt;&lt;br /&gt;为什么这事总发生？因为开发者脑子里想的是“这是个 URL 字符串”，攻击者脑子里想的是“这是一次由高权限网络位置发起的连接”。一旦后端没有严格限制目标地址，攻击者就会把请求打向本地服务、Redis、管理面板，或者云环境里最经典的目标：metadata service。比如在 AWS 里，&lt;code&gt;169.254.169.254&lt;/code&gt; 这个地址可能暴露实例身份凭证；一旦拿到临时 Access Key，问题就不再是“读到一段数据”，而是横向访问对象存储、消息队列，甚至整个云账号里的其他资源。&lt;br /&gt;&lt;br /&gt;现实防护的关键，不是写一堆黑名单字符串匹配，因为那很容易被绕过。你拦 &lt;code&gt;127.0.0.1&lt;/code&gt;，别人就用十进制、八进制、IPv6、DNS 解析跳转，或者先指向外部域名再让它解析到内网。真正靠谱的做法是把“服务器能主动连谁”变成默认拒绝，只允许业务明确需要的目标。也就是说，先做 egress allowlist，再做 URL 解析后的真实地址校验，校验的是最终 IP，不是用户输入的那串文本。同时，云上一定要关心 metadata 的保护机制，比如 AWS IMDSv2，不要让实例凭证裸奔；应用层面则尽量别让后端去请求用户任意给的 URL，如果业务上非做不可，就把它扔进隔离网络和低权限容器里跑。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 题目：某文件预览服务支持用户输入一个链接，后端会下载内容并生成缩略图。开发者已经拦截了字符串里出现 &lt;code&gt;127.0.0.1&lt;/code&gt;、&lt;code&gt;localhost&lt;/code&gt; 和 &lt;code&gt;169.254.169.254&lt;/code&gt; 的情况，于是觉得 SSRF 风险已经解决。这个判断对吗？为什么？答案：&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-11-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-11-0&quot;&gt;不对。问题不在“坏字符串”本身，而在后端是否能连到不该连的地址。攻击者可以利用 DNS 解析、IPv6、编码变形、重定向等方式绕过文本黑名单。真正该做的是限制后端出网目标、校验最终解析 IP、禁重定向到内网，并保护云元数据服务。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：SSRF 的本质不是“输入了恶意 URL”，而是“你让服务器替攻击者站在更高权限的位置看网络”。&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 SSRF 为什么总能绕过“只允许内网访问”的想象很多人第一次见到 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会把它理解成“攻击者让服务器替自己发一个请求”</title><link>https://orien-daily.aberrrrrrr.space/posts/2497</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2497</guid><pubDate>Mon, 27 Jul 2026 05:00:58 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;SSRF 为什么总能绕过“只允许内网访问”的想象&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次见到 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会把它理解成“攻击者让服务器替自己发一个请求”。这句话没错，但还不够危险。真正麻烦的地方在于，服务器所在的位置、网络权限、身份凭证，往往和外部用户完全不是一个级别。你在浏览器里访问不到的地址，应用服务器却可能轻松访问；你拿不到的云平台元数据接口，后端代码却默认能连。于是一个看起来只是“帮我抓一下图片 URL”或者“帮我读取远程 webhook 内容”的功能，突然就变成了进入内网的跳板。&lt;br /&gt;&lt;br /&gt;它为什么会发生？根子通常不是“黑客太厉害”，而是产品和工程习惯出了问题。开发者喜欢写一些通用抓取能力，比如导入头像、预览链接、拉取回调、抓取 PDF、解析 Open Graph 信息。这些功能本质上都在做一件事：让服务器主动请求一个由用户提供的地址。如果这里没有严格限制目标协议、目标主机、跳转行为和解析结果，那用户给的就不再是“一个普通 URL”，而是一条让服务器替他探路的命令。最典型的情况是，开发者只在字符串层面做检查，比如“只要不是 &lt;a href=&quot;http://127.0.0.1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;127.0.0.1&quot;&gt;127.0.0.1&lt;/a&gt; 就行”，结果攻击者换成 0.0.0.0、localhost 的变体、IPv6 写法、带 DNS 解析的域名，甚至先请求一个外部域名再 302 跳转到内网地址，过滤就成了摆设。&lt;br /&gt;&lt;br /&gt;现实里最值钱的 SSRF 目标，不是首页，不是某个后台页面，而是云环境里的 metadata service。比如在 AWS 里，历史上很多 SSRF 攻击最后拿到的不是“一个页面内容”，而是实例角色的临时凭证。拿到这个东西，问题就从“读了一下内网接口”升级成“可以调用云 API 了”。这也是为什么现在很多云厂商都在推动更严格的元数据访问机制，比如 AWS IMDSv2 要求先拿 token，再访问元数据；它的目的不是让流程更麻烦，而是让那种“随手一发 HTTP 请求就能读凭证”的情况直接失效。&lt;br /&gt;&lt;br /&gt;防 SSRF 不能靠一句“我们加个黑名单”。黑名单是补丁思维，迟早漏。更靠谱的做法是把“服务器能替用户访问什么”收窄成一个很小的白名单问题。比如这个功能如果只是抓头像，那就只允许 https，且只允许访问少数明确的公共域名；如果是 webhook 回调，就让它只打到预先登记过的目的地；如果业务上根本不需要服务器主动访问外部 URL，那就别做这个能力。再往下一层，网络出口也该有限制，应用容器不该随便访问内网管理面、云元数据地址、本机 loopback。代码层检查和网络层隔离都要有，缺一个都容易翻车。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在审一个“网页截图”功能：用户提交任意 URL，后端用无头浏览器打开后返回截图。开发者说“没事，我们已经禁止了 &lt;code&gt;127.0.0.1&lt;/code&gt; 和 &lt;code&gt;localhost&lt;/code&gt;，所以内网是安全的”。这句话最大的问题是什么？&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-10-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-10-0&quot;&gt;问题不在这两个字符串，而在“服务器仍然被允许访问用户指定的任意地址”。攻击者可以用别的 IP 表示法、IPv6、可解析到内网的域名、重定向链，甚至直接打云 metadata 接口。只拦 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;127.0.0.1&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-10-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-10-1&quot;&gt; 和 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;localhost&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-10-2&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-10-2&quot;&gt; 不是防 SSRF，只是安慰自己。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：SSRF 的本质不是“请求长得可疑”，而是“你让服务器替用户决定它该连谁”。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 为什么 SSRF 总能打到云主机元数据服务很多人第一次学 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会把它理解成“让服务器替你访问一个 URL”</title><link>https://orien-daily.aberrrrrrr.space/posts/2459</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2459</guid><pubDate>Sun, 26 Jul 2026 05:00:41 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;为什么 SSRF 总能打到云主机元数据服务&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次学 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会把它理解成“让服务器替你访问一个 URL”。这句话没错，但太轻了。真正危险的地方在于：服务器看到的网络世界，和外部用户看到的根本不是一回事。你在浏览器里访问不到 &lt;code&gt;127.0.0.1&lt;/code&gt;、&lt;code&gt;169.254.169.254&lt;/code&gt;、内网 Redis、Kubernetes API，但应用服务器自己往往能访问，因为这些地址本来就是给“机器自己”用的。&lt;br /&gt;&lt;br /&gt;云环境里最经典的目标就是元数据服务（metadata service）。以 AWS 为例，老版本 IMDSv1 会在 &lt;code&gt;169.254.169.254&lt;/code&gt; 这个链路本地地址上提供实例身份信息，甚至临时凭证。开发者本来是想让自己写的程序方便拿到 IAM Role 凭证，于是把这扇门开在机器旁边。问题在于，只要你的应用里有“帮用户取 URL 内容”“抓取图片”“网页预览”“导入远程文件”这种功能，而又没有严格限制目标地址，攻击者就能把这个功能拐弯用到元数据服务上。表面上是应用在请求，实际上是攻击者借你的服务器拿钥匙。&lt;br /&gt;&lt;br /&gt;这就是为什么现实里的 SSRF 经常不是直接打外网，而是先摸内网，再拿凭证，再横向移动。你以为只是一个读网页的小功能，最后可能变成云账号权限泄露。很多事故不是因为密码太弱，而是因为服务器被允许替别人访问了它本不该访问的地方。&lt;br /&gt;&lt;br /&gt;防 SSRF 也别搞成一堆漂亮口号，核心就几件事。第一，不要做“任意 URL 获取器”，最好改成白名单目标，只允许访问明确批准的域名和协议。第二，解析 URL 不能只看字符串，要在 DNS 解析后校验最终 IP，拦住 &lt;code&gt;127.0.0.0/8&lt;/code&gt;、&lt;code&gt;10.0.0.0/8&lt;/code&gt;、&lt;code&gt;172.16.0.0/12&lt;/code&gt;、&lt;code&gt;192.168.0.0/16&lt;/code&gt;、&lt;code&gt;169.254.0.0/16&lt;/code&gt; 这些本地和内网地址，不然别人会用域名跳转、DNS Rebinding、整数 IP、IPv6 映射这些花样绕过去。第三，从网络层限制出站访问，比在业务代码里堆 if 靠谱得多。第四，在云上直接关掉高风险入口，比如 AWS 优先使用 IMDSv2，它要求先拿 token，再访问元数据，能挡掉一批最粗暴的 SSRF 利用链。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 某团队做了一个“上传头像 by URL”的功能，后端会下载用户给的图片链接并保存。开发者已经禁止了 URL 中出现 &lt;code&gt;169.254.169.254&lt;/code&gt; 这个字符串，于是觉得元数据服务安全了。攻击者改用一个自己控制的域名，DNS 解析结果指向 &lt;code&gt;169.254.169.254&lt;/code&gt;。这个防护还挡得住吗？为什么？  &lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-9-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-9-0&quot;&gt;挡不住。因为只做字符串黑名单只检查了“你写了什么”，没检查“最终连到了哪里”。服务端真正发请求时会先做 DNS 解析，域名最后仍然可能落到元数据地址。正确做法是解析后校验最终 IP，并在网络层禁止访问本地链路地址和内网段。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：SSRF 的本质不是“让服务器访问一个链接”，而是“借服务器的网络位置和身份去碰你碰不到的东西”。&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 Silver Ticket：为什么伪造一个服务票据，能直接绕过很多 Windows 横向移动防线很多人先记住 Kerberos 里最危险的是 Golden Ticket，但现实攻防里，Silver Ticket 往往更安静，也更贴近“已经拿下一台机器后怎么继续扩大权限”这个场景</title><link>https://orien-daily.aberrrrrrr.space/posts/2419</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2419</guid><pubDate>Sat, 25 Jul 2026 05:01:27 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;Silver Ticket：为什么伪造一个服务票据，能直接绕过很多 Windows 横向移动防线&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人先记住 Kerberos 里最危险的是 Golden Ticket，但现实攻防里，Silver Ticket 往往更安静，也更贴近“已经拿下一台机器后怎么继续扩大权限”这个场景。它的核心不是伪造整套登录身份，而是伪造“某个具体服务愿意接受的票据”。如果攻击者已经拿到了某个服务账户的密钥，比如机器账户、IIS 应用池账户，或者跑 SQL Server 的域账户，他就能自己离线构造一张 TGS（Ticket Granting Service ticket），然后直接去访问这个服务。关键点在于，这张票据不一定要再去找域控签发，所以很多只盯着 DC 日志的检测思路会漏掉它。&lt;br /&gt;&lt;br /&gt;为什么会发生？因为 Kerberos 的服务票据本质上是“拿服务自己的密钥加密，证明这个客户端被允许访问你”。服务端收到票据后，只要能用自己的密钥解开，并且里面的字段看起来成立，就可能放行。也就是说，验证动作发生在服务端，而不是每次都回头问域控“这票是真的吗”。这就是 Silver Ticket 的土壤：一旦服务密钥泄露，攻击者就能伪造“发给这个服务看的证明”。它不像 Golden Ticket 那样掌控整个域，但对文件共享、远程管理、MSSQL、HTTP/SPN 绑定服务这些目标已经足够致命。&lt;br /&gt;&lt;br /&gt;现实里它常被用在“低噪声横向移动”。比如攻击者先拿下一台服务器的本地管理员，再导出这台机器账户的哈希。接着他可以伪造访问 &lt;code&gt;cifs/host&lt;/code&gt; 的票据去碰 SMB，共享、计划任务、远程服务控制都可能被串起来。如果拿到的是 &lt;code&gt;http/&lt;/code&gt; 或 &lt;code&gt;mssqlsvc/&lt;/code&gt; 这类 SPN 对应账户的密钥，攻击面就会变成 Web 管理后台、数据库服务甚至应用层数据。它可怕的地方不在“理论上能做”，而在于很多环境里服务账户权限本来就配得过大，结果一个服务密钥泄露，后面不只是这个服务出事，而是一串依赖它的资源一起沦陷。&lt;br /&gt;&lt;br /&gt;防它不能靠空话。第一，别让服务账户长期复用静态高权限口令，优先用 gMSA 这类自动轮换的托管账户。第二，机器账户和服务账户权限要收紧，别让一个跑业务的账户顺手拥有本地管理员、远程登录或数据库超权。第三，检测上不要只看 TGS-REQ/TGT 申请链路，因为 Silver Ticket 可能根本不经过正常申请；你要盯的是“服务端看到一个 Kerberos 登录，但域控侧没有对应票据签发痕迹”的不一致，还有异常 SPN 访问、伪造票据常见的生命周期和 PAC 特征。第四，一旦怀疑泄露，处理不是“删个会话”就完了，而是必须轮换对应服务账户或机器账户密钥，不然伪造能力还在。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在一台 IIS 服务器上发现攻击者导出了运行站点的域服务账户 NTLM/AES 密钥。第二天，域控上几乎没有新的异常票据申请日志，但这台 IIS 后面的 SQL Server 和文件共享却出现了来自该用户身份的 Kerberos 访问。更可能发生了什么？为什么只盯域控日志会漏报？  &lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-8-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-8-0&quot;&gt;更可能是攻击者用泄露的服务账户密钥伪造了 Silver Ticket，直接拿去访问对应服务或依赖该身份的资源。因为 Silver Ticket 可以离线构造，服务端本地校验后就可能接受，不一定需要域控重新签发票据，所以只看 DC 上的 TGS/TGT 申请链路会漏掉这类行为。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：Silver Ticket 的本质不是“偷到一次登录”，而是“拿到服务密钥后，自己伪造这个服务愿意相信的票据”。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 OAuth 回调劫持：为什么一个“差不多匹配”的 redirect_uri 会变成账号接管很多人以为 OAuth 登录的风险主要在密码，其实大量事故根本不碰密码，而是出在回调地址 redirect_uri 的校验太松</title><link>https://orien-daily.aberrrrrrr.space/posts/2388</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2388</guid><pubDate>Fri, 24 Jul 2026 05:01:02 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;OAuth 回调劫持：为什么一个“差不多匹配”的 redirect_uri 会变成账号接管&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人以为 OAuth 登录的风险主要在密码，其实大量事故根本不碰密码，而是出在回调地址 &lt;code&gt;redirect_uri&lt;/code&gt; 的校验太松。OAuth 的核心流程是：用户在身份提供方登录后，授权码 &lt;code&gt;code&lt;/code&gt; 会被浏览器带回业务方预先登记的回调地址。如果服务端只做“包含关系”“前缀匹配”或者允许任意子路径、任意子域，攻击者就能把本该回到你应用的授权码，骗去自己的页面，再拿这个 &lt;code&gt;code&lt;/code&gt; 去换取 access token，结果就是用户刚正常登录，账号却被别人接管了。&lt;br /&gt;&lt;br /&gt;为什么这种事会发生？因为很多系统把“能跳回自己网站”理解成了“域名看起来像自己就行”。比如把 &lt;code&gt;https://app.example.com/callback&lt;/code&gt; 放宽成 &lt;code&gt;https://app.example.com/*&lt;/code&gt;，甚至更糟，允许 &lt;code&gt;https://*.example.com/*&lt;/code&gt;。一旦某个子域可被上传内容、可被接管，或者存在开放重定向，攻击者就能把 OAuth 服务器返回的 &lt;code&gt;code&lt;/code&gt; 转走。麻烦在于，这不是用户输错密码，也不是浏览器中毒，而是协议落地时把“精确绑定”做成了“模糊容忍”。&lt;br /&gt;&lt;br /&gt;现实防护也别搞玄学，直接把边界钉死。&lt;code&gt;redirect_uri&lt;/code&gt; 必须做精确匹配，最好精确到完整 URL；不要支持通配符子域；不要依赖“先跳到本站再 302 到别处”的补救设计；客户端在发起授权时要带高强度 &lt;code&gt;state&lt;/code&gt; 防 CSRF，移动端和 SPA 还应该用 PKCE，避免授权码被截获后直接复用。还有一个常被忽略的点：如果业务里存在 open redirect，它本身就可能成为 OAuth 回调劫持的跳板，所以开放重定向不是“小漏洞”，放到认证链路里就会升级。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 场景题：某公司把 OAuth 回调地址配置成 &lt;code&gt;https://login.example.com/*&lt;/code&gt;，而 &lt;code&gt;https://login.example.com/redirect?next=&lt;/code&gt; 存在开放重定向。攻击者发起授权请求，把回调写成 &lt;code&gt;https://login.example.com/redirect?next=https://evil.com/catch&lt;/code&gt;。用户完成登录后，授权码最终会到谁手里？为什么？答案：&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-7-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-7-0&quot;&gt;会到攻击者手里。因为身份提供方只确认回调起点是 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;login.example.com&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-7-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-7-1&quot;&gt;，但浏览器随后被开放重定向带去 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;evil.com/catch&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-7-2&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-7-2&quot;&gt;，URL 里的授权码也一起被送走了。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：OAuth 最怕的不是密码泄露，而是把授权码送错地方，所以 &lt;code&gt;redirect_uri&lt;/code&gt; 必须精确绑定，不能“差不多就行”。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 JWT 算法混淆：为什么“会验签”也可能被绕过很多人第一次接触 JWT（JSON Web Token）时，会把它理解成“服务端签了名，客户端带回来，后端验一下就安全了”</title><link>https://orien-daily.aberrrrrrr.space/posts/2351</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2351</guid><pubDate>Thu, 23 Jul 2026 05:00:48 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;JWT 算法混淆：为什么“会验签”也可能被绕过&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次接触 JWT（JSON Web Token）时，会把它理解成“服务端签了名，客户端带回来，后端验一下就安全了”。问题就在这里：如果验证代码先相信了令牌头里的 &lt;code&gt;alg&lt;/code&gt; 字段，再决定用什么方式验签，攻击者就有机会把“安全设计”变成“自己选规则的考试”。这类问题通常叫算法混淆，最经典的是把原本应该用非对称签名的 RS256，骗成用对称签名 HS256。服务端如果错误地把公开的 RSA 公钥当成 HMAC 密钥去验，攻击者就能自己伪造一个“合法”令牌，因为公钥本来就是公开的。更糟的是，早年还有一些库接受 &lt;code&gt;alg=none&lt;/code&gt;，等于告诉服务端“这张票没签名，但你也别查了”。&lt;br /&gt;&lt;br /&gt;它为什么会发生，不是因为密码学本身玄学，而是因为实现者把“算法选择权”交给了不可信输入。JWT 的 header 是令牌的一部分，本来就可以被用户改，后端却常常写成“你说你是 HS256，那我就按 HS256 验；你说你是 RS256，那我就按 RS256 验”。这就像门卫先问来的人“你想按什么规则进门”，然后真的照做。现实里这种 bug 常出现在统一认证网关、微服务中间件、自己封装的登录组件里，尤其是“支持多种算法、多个 issuer、多个环境”的代码，分支一多，垃圾就冒出来了。&lt;br /&gt;&lt;br /&gt;真正的防法很直接：算法不能从 token 里“动态信任”，而要由服务端配置写死。某个 issuer 只允许某一种算法，某把 key 只配某一种用途，验签库也要显式指定允许列表，而不是吃默认值。再往前一步，连 &lt;code&gt;kid&lt;/code&gt;（key id）也不能无条件信任，否则又会出现路径穿越、远程取 key、缓存投毒这种后续事故。简单说，token 可以携带“声明”，但不能决定“规则”；规则必须在服务端。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在审计一个内部 API 网关，发现它会先解析 JWT header，再根据其中的 &lt;code&gt;alg&lt;/code&gt; 决定调用 HMAC 还是 RSA 验签；而且 RSA 公钥能从公开的 JWK endpoint 拿到。现在系统原本使用 RS256。最危险的攻击路径是什么？&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-6-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-6-0&quot;&gt;攻击者把 token 头里的 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;alg&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-6-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-6-1&quot;&gt; 改成 HS256，再用公开的 RSA 公钥当作 HMAC 密钥自己签一个管理员 token。如果网关没有把算法和 key 类型写死，就可能把这枚伪造 token 当成合法登录。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：JWT 的 header 可以看，但绝不能信，验签算法和密钥类型必须由服务端预先绑定，不能让令牌自己决定。&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 OAuth Device Code Phishing：为什么“官方登录码”也会把账号送出去很多人听到钓鱼，脑子里先想到假登录页</title><link>https://orien-daily.aberrrrrrr.space/posts/2316</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2316</guid><pubDate>Wed, 22 Jul 2026 05:01:43 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;OAuth Device Code Phishing：为什么“官方登录码”也会把账号送出去&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人听到钓鱼，脑子里先想到假登录页。可 OAuth Device Code Phishing 更阴一点，它经常连密码都不直接偷。攻击者会先在真正的身份提供方发起一次设备授权流程，然后把那串看起来很正规的 code 和登录地址发给受害者，说“这是公司 VPN / Microsoft 365 / GitHub / 云平台的验证步骤，你去官方页面输入这串码就行”。受害者确实打开了真的官网，也确实把 code 输进了真的页面，于是心理防线会瞬间放松，觉得“我都在官方站点操作了，怎么会有问题”。&lt;br /&gt;&lt;br /&gt;问题就出在这里。Device Code 本来是给电视、CLI、打印机这类不方便输入账号密码的设备准备的。设备先拿到一个 device_code 和 user_code，用户再去另一台设备上完成授权。攻击者如果先替你发起这个流程，他就已经拿着 device_code 在轮询了。你一旦在官方页面完成授权，令牌不是发到你手里，而是发给最初发起流程的那一端，也就是攻击者控制的客户端。整个过程里，用户没有把密码输进假站，却把“授权结果”亲手交了出去。&lt;br /&gt;&lt;br /&gt;这类攻击现实里很容易成功，因为它利用的是人对“官方域名”和“验证码式操作”的天然信任。很多企业还把 MFA 当成护身符，但这招绕的不是密码强度，而是授权边界。只要你的账号被诱导去“批准一个本不该批准的客户端”，MFA 也可能只是帮攻击者完成了最后一道门禁。更糟的是，如果授权范围带有 Mail、Files、Graph API 或 repo 访问权，后果不是一次登录成功，而是持续性的 API 令牌滥用。&lt;br /&gt;&lt;br /&gt;防它不能只喊“别点陌生链接”。真正有用的是把“谁发起授权”这件事钉死。企业侧要限制 Device Code Flow 的适用范围，能关就关，不能关就至少对高权限应用禁用；对 Entra ID、Google Workspace、GitHub OAuth 这类平台，要审计哪些应用能走设备授权，哪些租户允许用户自助同意。用户侧则要养成一个硬习惯：看到输入 code 的登录流程，不只看域名，还要看“你正在授权给谁”。如果页面上显示的应用名、发布者、权限范围和你当前要做的事对不上，立刻停。安全团队还应该监控异常的设备授权事件、短时间内的 consent 增长、来自不常见客户端 ID 的 token 签发，以及拿到令牌后立刻访问邮件或文件 API 的行为链。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在公司 IM 里收到“IT 支持”消息，让你去 &lt;code&gt;microsoft.com/devicelogin&lt;/code&gt; 输入一串 8 位 code，说这是修复 Outlook 同步问题的标准流程。页面域名是真的，MFA 也正常弹出。这个场景里，最关键的风险信号是什么？ &lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-5-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-5-0&quot;&gt;风险不在域名真假，而在“这次授权是谁发起的、准备把权限交给哪个应用”。如果这不是你自己在某台设备上主动发起的登录，或者授权页面显示的应用名/权限范围和“修复 Outlook”无关，那就是典型的 Device Code Phishing。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：设备码登录最危险的地方不是假官网，而是你在真官网上替攻击者完成了授权。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 反序列化为什么危险：对象不是数据，代码也会跟着进来很多人第一次接触反序列化漏洞，会觉得它只是“把字符串还原成对象”而已，听起来像普通的数据解析</title><link>https://orien-daily.aberrrrrrr.space/posts/2281</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2281</guid><pubDate>Tue, 21 Jul 2026 05:01:09 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;反序列化为什么危险：对象不是数据，代码也会跟着进来&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次接触反序列化漏洞，会觉得它只是“把字符串还原成对象”而已，听起来像普通的数据解析。但危险就在这里：如果一种序列化格式不只是保存数据，还保存“对象类型”和“构造方式”，那程序在反序列化时就可能顺手执行一串你没打算执行的逻辑。Java 的 &lt;code&gt;readObject&lt;/code&gt;、Python 的 &lt;code&gt;pickle&lt;/code&gt;、PHP 的 &lt;code&gt;unserialize&lt;/code&gt; 都出过这类问题，本质不是库“脆弱”，而是你把“不可信输入”当成了“可信对象”。&lt;br /&gt;&lt;br /&gt;为什么会发生？因为开发者脑子里常有个错觉：来自数据库、缓存、消息队列、Cookie、文件上传里的内容，看起来像内部格式，就默认安全。可攻击者只要能控制这段序列化数据，就能伪造一个对象图，让程序在恢复对象时自动调用魔术方法、钩子函数或者 gadget chain。于是反序列化不再是“读数据”，而变成了“按攻击者指定的方式跑代码”。现实里常见后果包括远程命令执行、任意文件读写、权限绕过，甚至只是一个签名校验缺口，也足够让攻击者把本地组件串成武器。&lt;br /&gt;&lt;br /&gt;真正实用的防法也不是“多加几个黑名单类名”。黑名单是补丁思维，迟早漏。更靠谱的做法是别用会恢复任意对象的格式处理外部输入，能用 JSON、MessagePack 这种纯数据格式，就别把类实例直接扔出去再捡回来。如果历史包袱太重，至少要做三件事：第一，反序列化前做严格完整性校验，比如带密钥的签名，而不是裸 base64；第二，限制可反序列化的类型白名单；第三，把危险逻辑从对象的构造、析构、魔术方法里拆出去，别让“恢复对象”顺手触发副作用。说白了，输入应该只是数据，不该拥有决定程序控制流的权力。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在审计一个老 Web 系统，发现它把登录态存在 Cookie 里，值是 base64 后的 PHP serialize 字符串。开发者说“攻击者看不懂内容，而且 Cookie 里没有命令执行代码，所以问题不大”。这里最关键的风险点是什么？ &lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-4-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-4-0&quot;&gt;风险不在“看不看得懂”，而在攻击者能否篡改序列化对象。只要服务端对 Cookie 做 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;unserialize()&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-4-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-4-1&quot;&gt;，而代码库里存在可利用的魔术方法或 gadget chain，攻击者就可能构造恶意对象触发任意行为。base64 只是编码，不是保护。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：只要外部输入能被还原成“对象”，你就得假设它可能把程序控制流一起带进来。&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 JWT 算法混淆攻击：当你的服务器把公钥当成对称密钥来用JWT（JSON Web Token）是现代 Web 认证的标配</title><link>https://orien-daily.aberrrrrrr.space/posts/2242</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2242</guid><pubDate>Mon, 20 Jul 2026 05:01:56 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;JWT 算法混淆攻击：当你的服务器把公钥当成对称密钥来用&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;JWT（JSON Web Token）是现代 Web 认证的标配。一个 JWT 由三段组成：Header、Payload、Signature，用点号拼接。Header 里有个关键字段叫 &lt;code&gt;alg&lt;/code&gt;，声明这个 token 用什么算法签的名。服务器验证时，本应严格按 Header 里声明的 &lt;code&gt;alg&lt;/code&gt; 去执行对应的验签逻辑。问题就出在这里——很多库的实现没有在验签前把 &lt;code&gt;alg&lt;/code&gt; 绑定到服务端预期的算法上，而是直接信任客户端传来的 Header。&lt;br /&gt;&lt;br /&gt;典型的混淆场景是这样：服务端用 RS256（非对称，私钥签名、公钥验签）签发 token。攻击者拿到一个合法 token 后，把 Header 的 &lt;code&gt;alg&lt;/code&gt; 改成 HS256（对称，同一个密钥既签名又验签），然后用服务端公开的 RSA 公钥作为 HMAC 密钥重新签名整个 token。服务端收到后看到 &lt;code&gt;alg&lt;/code&gt; 是 HS256，就走进 HMAC 验签分支，拿&quot;密钥&quot;去算 HMAC——而这个&quot;密钥&quot;恰恰就是攻击者手上有的那把 RSA 公钥。验签通过，攻击者以任意身份通过认证。&lt;br /&gt;&lt;br /&gt;这个漏洞的本质是一个信任边界错位。RSA 公钥是设计用来公开分发的，任何人都能拿到。但 HS256 要求签名和验签共享同一个秘密。当服务端的验证代码把&quot;公钥&quot;喂进 HMAC 验签函数时，公钥就从&quot;谁能拿到都无所谓&quot;变成了&quot;谁能拿到谁就能伪造 token&quot;。这不是 RSA 或 HMAC 自身的问题，是上层代码没有约束算法选择造成的。&lt;br /&gt;&lt;br /&gt;现实中防御的核心只有一条：服务端必须在验证之前，把 &lt;code&gt;alg&lt;/code&gt; 写死成一个预期值，不信任 token Header 里客户端声明的算法。大多数现代 JWT 库已经修复了这条路径，比如 Node 的 &lt;code&gt;jsonwebtoken&lt;/code&gt; 在 &lt;code&gt;jwt.verify()&lt;/code&gt; 时要求显式传入 &lt;code&gt;asymmetricPublicKey&lt;/code&gt; 或 &lt;code&gt;secret&lt;/code&gt;，库内部会拒绝用公钥做 HMAC。但如果你用的是老旧库版本，或者自己拼了验证逻辑，这条攻击路径依然敞开。另外，如果服务端含有 &lt;code&gt;alg: none&lt;/code&gt; 的回退逻辑——即允许无签名的 token 通过——那是另一条同样危险的路径，根本原因一样：信任了客户端对自己如何被认证的自述。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你的服务用 RS256 签发 JWT，公钥在 &lt;code&gt;/.well-known/jwks.json&lt;/code&gt; 公开。攻击者把 &lt;code&gt;alg&lt;/code&gt; 从 RS256 改成 HS256，用拿到的公钥做了 HMAC 签名，服务端居然验证通过了。最小改动应该改哪里？&lt;br /&gt;&lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-3-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-3-0&quot;&gt;在验证代码中强制指定预期的算法常量（如固定 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;algorithms: [&apos;RS256&apos;]&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-3-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-3-1&quot;&gt;），不接受 token Header 里 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;alg&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-3-2&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-3-2&quot;&gt; 字段的值；同时确认库版本拒绝用公钥执行对称验签。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：安全验证永远不信任客户端声明的算法——&lt;code&gt;alg&lt;/code&gt; 字段是别人说的话，只有服务端写死的算法才是你定的规矩。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 为什么 SSRF 能打到“你以为外面碰不到”的内网很多人第一次看到 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会误以为它只是“让服务器帮我访问一个网址”</title><link>https://orien-daily.aberrrrrrr.space/posts/2203</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2203</guid><pubDate>Sun, 19 Jul 2026 05:00:34 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;为什么 SSRF 能打到“你以为外面碰不到”的内网&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次看到 SSRF（Server-Side Request Forgery，服务端请求伪造）时，会误以为它只是“让服务器帮我访问一个网址”。问题没这么简单。真正危险的地方在于，请求不是从攻击者电脑发出去的，而是从你的业务服务器、云主机、容器、函数计算环境里发出去的。也就是说，攻击者借用了“服务器的网络位置”和“服务器的身份”。你前端用户访问不到 &lt;code&gt;127.0.0.1&lt;/code&gt;，访问不到 &lt;code&gt;169.254.169.254&lt;/code&gt;，也碰不到 Kubernetes API、Redis、Consul、内部管理面板，但你的应用服务器很可能可以。只要业务里存在“根据用户输入去拉取 URL”的功能，比如图片抓取、网页预览、Webhook 回调测试、导入远程文件、头像同步，SSRF 就有机会出现。&lt;br /&gt;&lt;br /&gt;它为什么会发生？根子通常不是“少了一个过滤”，而是设计上默认相信了“用户给的 URL 只是普通外链”。现实里 URL 不是一个简单字符串，它能指向内网 IP、环回地址、IPv6、本地域名，甚至还能借重定向跳转到你原本不想访问的地方。更糟的是，很多程序员只做了表面校验，比如禁止 &lt;code&gt;127.0.0.1&lt;/code&gt;，结果攻击者改用十进制 IP、IPv6 映射、DNS 解析变化，或者先访问一个看起来正常的域名，再被 302 重定向到内网地址。补丁式拦截会越补越乱，因为特殊情况太多了。&lt;br /&gt;&lt;br /&gt;真正的防法不是写一堆 if/else 去猜攻击者会怎么绕，而是把“服务器代替用户访问任意 URL”这件事收紧成一个小得多的问题。业务上如果只需要抓取你自家 CDN 的资源，那就只允许固定域名和固定协议；如果必须支持第三方地址，就在出网层做白名单和网段封锁，直接禁止访问内网、元数据地址和管理平面；同时关闭自动跟随重定向，或者每跳都重新校验目标地址。云环境里还要单独保护实例元数据服务，比如 AWS IMDSv2，就是因为历史上太多 SSRF 最后都不是“读了个页面”，而是拿到了临时凭证，再一路横向移动。你要防的不是“有人访问了一个奇怪网址”，而是“服务器替别人做了不该做的网络动作”。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在做“导入头像 by URL”功能，代码先检查用户输入必须以 &lt;code&gt;https://trusted.example.com/&lt;/code&gt; 开头，然后后端请求这个地址下载图片。测试时有人提交的链接确实是这个域名，但服务器最终却访问到了内网服务。最可能的问题是什么？&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-2-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-2-0&quot;&gt;答案：只检查了输入字符串，不等于最终请求目标安全。可能存在 302 重定向、DNS 解析变化，或同域名下可控路径转发到内网。应校验每次解析后的实际 IP，并在网络层禁止访问内网与元数据地址。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：SSRF 的本质不是“访问了恶意网址”，而是“攻击者借你的服务器身份和网络位置去碰本来碰不到的东西”。&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 JWT Algorithm Confusion：为什么“验签”也会被你自己绕过很多人第一次学 JWT，会把注意力放在“有没有签名”上，却忽略了更关键的一件事：服务端到底是按什么规则验这个签名的</title><link>https://orien-daily.aberrrrrrr.space/posts/2179</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2179</guid><pubDate>Sat, 18 Jul 2026 05:01:32 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;JWT Algorithm Confusion：为什么“验签”也会被你自己绕过&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次学 JWT，会把注意力放在“有没有签名”上，却忽略了更关键的一件事：服务端到底是按什么规则验这个签名的。JWT 头里有一个 &lt;code&gt;alg&lt;/code&gt; 字段，告诉接收方“我用的是哪种算法”。问题就出在这里——如果服务端傻乎乎地信了这个字段，让令牌自己决定该怎么被验证，攻击者就有机会把“本该用非对称密钥验证的令牌”，伪装成“用对称密钥验证的令牌”，甚至在更糟的实现里直接改成 &lt;code&gt;none&lt;/code&gt;。这不是密码学失效，而是实现者把控制权交给了不可信输入。&lt;br /&gt;&lt;br /&gt;现实里最典型的坑，是服务端原本想用 RS256。正常情况下，签发方拿私钥签名，验证方拿公钥验证，攻击者只有公钥也没法伪造。但如果验证库或业务代码写得烂，看到 JWT 头里写着 &lt;code&gt;HS256&lt;/code&gt; 就切到 HMAC 模式，而且还把“本来公开的 RSA 公钥”当成 HMAC 的 secret 去验，那攻击者就能用这个公开公钥自己生成一个“合法”签名。于是整个认证系统从“只有私钥持有者能签”退化成“谁拿到公钥谁都能签”。这就是 Algorithm Confusion 的本质：不是数学被攻破，是程序把不同信任模型混在了一起。&lt;br /&gt;&lt;br /&gt;为什么这种事会发生？因为很多系统把“解析 token”和“决定安全策略”塞进了一步，开发者图省事，直接把 token 丢进库里让它自动判断。自动判断在业务代码里通常就是灾难。安全边界不该由攻击者上传的头字段决定，应该由服务端配置决定：这个接口只接受 RS256，那就硬编码只验 RS256；这个 issuer 的 key set 是什么，也应该提前绑定，而不是收到什么就信什么。再往前一步，&lt;code&gt;kid&lt;/code&gt; 这种字段如果还能触发本地文件读取、远程取 key、或从多个 key 中随便选，那问题只会更大。&lt;br /&gt;&lt;br /&gt;防御其实不复杂，但很多团队就是不做。第一，服务端必须显式指定允许的算法，不能从 token 头里“协商”。第二，不要把同一套验证逻辑同时兼容 HMAC 和 RSA/EC，混用就是在制造特殊情况。第三，验证时把 issuer、audience、过期时间、not before 一起校验，不要只看签名过不过。第四，选库时看默认行为，凡是“自动推断算法”“自动接受 none”“随手喂一个 key 就能跑”的库，都该提高警惕。安全问题最烦的地方就在这：代码看起来能跑，用户也能登录，但边界已经烂了。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 你在审一个老系统，发现它验证 JWT 的代码会先读取 token 头里的 &lt;code&gt;alg&lt;/code&gt;，如果是 &lt;code&gt;RS256&lt;/code&gt; 就用公钥验签，如果是 &lt;code&gt;HS256&lt;/code&gt; 就把同一份配置里的 &lt;code&gt;publicKey&lt;/code&gt; 字符串当作 HMAC secret。现在攻击者只能拿到公开公钥，不能拿到私钥。这个系统还能被伪造管理员 token 吗？为什么？答案：&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-1-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-1-0&quot;&gt;能。因为一旦服务端允许攻击者把 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;alg&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-1-1&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-1-1&quot;&gt; 改成 &lt;/tg-spoiler&gt;&lt;/label&gt;&lt;code&gt;HS256&lt;/code&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-1-2&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-1-2&quot;&gt;，并把原本公开的 RSA 公钥当作 HMAC 密钥使用，攻击者就可以直接用这个公钥自己计算 HMAC 签名，服务端也会验通过。这不是“拿到了私钥”，而是服务端把公开信息误当成了对称密钥。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：JWT 的算法选择权必须在服务端手里，绝不能交给 token 头自己决定。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item><item><title>🔐 为什么 PKCE 能挡住 OAuth 授权码劫持很多人第一次接触 OAuth 2.0，会以为拿到授权码 authorization code 就已经很安全了，因为真正换 access token 还要再走一步</title><link>https://orien-daily.aberrrrrrr.space/posts/2148</link><guid isPermaLink="true">https://orien-daily.aberrrrrrr.space/posts/2148</guid><pubDate>Fri, 17 Jul 2026 05:01:18 GMT</pubDate><content:encoded>&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🔐&lt;/b&gt;&lt;/i&gt; &lt;b&gt;为什么 PKCE 能挡住 OAuth 授权码劫持&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;很多人第一次接触 OAuth 2.0，会以为拿到授权码 authorization code 就已经很安全了，因为真正换 access token 还要再走一步。问题就在这里：如果攻击者能在这“一步之隔”里偷到授权码，而服务端又没有额外绑定这个授权码到底属于谁，那他就能自己拿着这串 code 去换 token，等于把你的登录结果截胡了。这类问题最常见在移动端、桌面端、单页应用，或者 redirect_uri 校验做得很松的时候。&lt;br /&gt;&lt;br /&gt;PKCE 的核心不是“加密授权码”，而是给这次登录流程加一条只有发起者自己知道的暗号。客户端先生成一个随机字符串 code_verifier，再把它变成 code_challenge 发给授权服务器。等用户完成登录、服务器把 authorization code 发回来之后，客户端还必须拿出原始的 code_verifier 才能换 token。这样一来，攻击者就算在中途截获了 authorization code，没有最初那串 verifier，也换不到 token。你可以把它理解成：授权码只是取件号，真正能取走包裹的，是取件号加取件密码。&lt;br /&gt;&lt;br /&gt;这事为什么会发生？因为 OAuth 最早更多是给“有后端、能保管 client_secret 的 Web 应用”设计的，后来移动 App、SPA、桌面工具也大量使用同一套协议，但这些环境根本藏不住 secret。开发者如果还沿用“有 code 就能换 token”的思路，就会把本来应该在后端完成的信任关系，错误地暴露给不可信的客户端环境。PKCE 本质上是在承认现实：前端和原生客户端不值得被默认信任，所以必须给授权码再加一次绑定。&lt;br /&gt;&lt;br /&gt;现实里怎么防，关键不是“支持 PKCE”四个字，而是别把它做成摆设。第一，公开客户端 public client 一律强制 PKCE，别当成可选项。第二，code_verifier 必须足够随机，不能自己手搓一个短字符串糊弄过去。第三，优先使用 S256，不要退回 plain。第四，redirect_uri 要精确匹配，不能只按前缀比较，不然攻击者能把授权码送到他自己的地址。第五，授权码必须短时有效且一次性使用，用过立刻作废。第六，客户端别把 token 存在随便一个能被脚本读到的地方，不然前面防住了，后面自己又漏了。&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;🧪&lt;/b&gt;&lt;/i&gt; 场景分析题：一家网站的移动 App 已经接入 OAuth 登录。安全测试时发现，App 使用 Authorization Code Flow，但 token 端点只校验 client_id 和 authorization code，没有要求 code_verifier。攻击者能在受害者手机上通过恶意应用劫持回调 URI，拿到这次登录返回的 code。问：攻击者此时能不能换到 access token，真正的问题出在哪？  &lt;br /&gt;&lt;label class=&quot;spoiler-button&quot;&gt;&lt;input type=&quot;checkbox&quot; aria-label=&quot;Toggle spoiler&quot; aria-controls=&quot;spoiler-0-0&quot; /&gt;&lt;tg-spoiler id=&quot;spoiler-0-0&quot;&gt;能。因为这个流程虽然表面上用了授权码模式，但没有启用 PKCE，导致 authorization code 没有和最初发起登录的客户端实例绑定。攻击者一旦截获 code，就能自己向 token 端点兑换 token。根因不是“回调被看见了”这么简单，而是“看见 code 之后不需要再证明自己就是原始请求方”。&lt;/tg-spoiler&gt;&lt;/label&gt;&lt;br /&gt;&lt;br /&gt;&lt;i class=&quot;emoji&quot;&gt;&lt;b&gt;💡&lt;/b&gt;&lt;/i&gt; 核心记忆：PKCE 防的不是“授权码泄露”本身，而是“别人捡到授权码后也能直接换 token”这件事。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23Security&quot; title=&quot;#Security&quot;&gt;#Security&lt;/a&gt;</content:encoded></item></channel></rss>