''

Search: #Security

  1. 🔐 SSRF:你的服务器,可能成了攻击者的内网跳板SSRF(Server-Side Request Forgery,服务器端请求伪造)发生在应用允许用户提交 URL,并由服务器代为访问时

    🔐 SSRF:你的服务器,可能成了攻击者的内网跳板

    SSRF(Server-Side Request Forgery,服务器端请求伪造)发生在应用允许用户提交 URL,并由服务器代为访问时。攻击者不一定需要直接进入内网,只要诱导服务器请求 127.0.0.1、云平台元数据地址或内部管理接口,就可能读取敏感信息、调用高权限服务,甚至进一步控制主机。

    这类漏洞常见于图片抓取、网页预览、Webhook、PDF 生成和远程文件导入功能。问题的根源不是“请求了一个危险 URL”这么简单,而是服务器把用户输入当成了可信的网络目标,并且往往只检查初始域名,忽略了重定向、特殊 IP 表示方式和解析后的真实地址。

    防护时不要只维护一个“禁止访问的域名列表”,更可靠的做法是采用严格的目标白名单,只允许必要的协议、域名和端口;解析域名后检查 IPv4、IPv6 及 IPv4-mapped IPv6 地址,拒绝环回地址、私有地址、链路本地地址和云元数据地址;每次重定向后重新校验,并尽量关闭自动重定向。应用之外,还应通过出站防火墙限制服务器只能访问真正需要的网络,即使应用层校验被绕过,也不能直接触达内网。

    🧪 某图片抓取接口只允许 https://,并检查 URL 主机名不包含 localhost。攻击者提交一个外部 URL,服务端跟随 302 跳转到 http://169.254.169.254/,随后返回云主机临时凭证。最关键的漏洞点是什么,优先修哪两层?



    💡 核心记忆:凡是服务器替用户访问 URL,就必须同时验证“访问谁”和“能否连到那里”。

    #Security

  2. 🔐 哈希长度扩展:拼接式签名为什么一文不值很多系统做消息完整性校验时,图省事会这么干:把密钥和消息直接拼起来,再算个哈希当签名

    🔐 哈希长度扩展:拼接式签名为什么一文不值

    很多系统做消息完整性校验时,图省事会这么干:把密钥和消息直接拼起来,再算个哈希当签名。比如 sig = SHA-256(secret + message),服务器收到请求后自己重算一遍比对,一致就认定是可信来源发的。乍看没毛病,secret 藏在里面,攻击者不知道密钥就算不出合法签名。问题出在哈希算法自身的结构上。

    SHA-256 这类 Merkle-Damgård 系哈希是分块处理的:消息被切成 64 字节的块逐块喂入,每块的输出作为内部状态继续参与下一块计算。于是出现一个致命特性:知道某个消息的哈希值,就等于拿到了处理完该消息后的内部状态。拿到状态就能假装"我已经算到这儿了",继续往后面追加任意内容并算出新哈希,全程不需要知道 secret。唯一要猜的是 secret 的长度,因为它决定中间那段填充怎么写,而枚举几十种长度对工具来说毫无压力。这就是哈希长度扩展攻击(Hash Length Extension)。

    现实中最出名的受害者是 2009 年的 Flickr API:签名方案就是 md5(secret + 参数),攻击者用一个合法请求的签名就能伪造出带额外参数的请求签名。今天仍能看到不少自研 Webhook 签名、游戏防作弊校验在用这种拼串哈希,几乎一测一个准。

    为什么 HMAC 不怕?HMAC 不是简单拼接,它把密钥派生出两个掩码,一个垫在消息前面算内层哈希,一个垫在结果前面算外层哈希,密钥被两层结构封住,长度扩展无从下手。防御口径很朴素:完整性校验一律用 HMAC,或者干脆用 SHA-3 这类海绵结构哈希;永远不要自己发明 hash(secret + message) 的变体。正经平台的 Webhook 签名(比如 Stripe)用的就是 HMAC-SHA256,不是没有原因的。

    🧪 场景分析:某订单接口用 sign = sha256(secret + "user=alice&price=100") 做签名,你截获了一个合法请求,参数和 sign 都可见。能否在不知道 secret 的情况下,伪造出追加 &discount=90(打一折)的请求并算出合法签名?

    m) 就能从内部状态继续追加。用 hashpump 这类工具,枚举猜出 secret 长度后,算出 H(secret补齐填充

    💡 核心记忆:hash(secret + message) 会把哈希内部状态整个交出去,消息完整性校验请认准 HMAC。

    #Security

  3. 🔐 JWT 算法混淆(Algorithm Confusion)不是“密钥泄露”,而是验证器把不同算法当成一回事JWT 看起来只是三段 Base64 文本,真正危险的地方不在“能不能看懂 payload”,而在服务端怎么验签

    🔐 JWT 算法混淆(Algorithm Confusion)不是“密钥泄露”,而是验证器把不同算法当成一回事

    JWT 看起来只是三段 Base64 文本,真正危险的地方不在“能不能看懂 payload”,而在服务端怎么验签。算法混淆的经典问题是:开发者本来想用 RS256 这种“私钥签名、公钥验证”的非对称方案,结果验证库却信了 token 头里的 alg,被攻击者改成 HS256。这样一来,服务端原本公开给大家的 RSA 公钥,反而会被当成 HMAC 的“对称密钥”来验签。攻击者拿得到公钥,就能自己伪造一个“合法”管理员 token。

    这事为什么会发生?因为很多人把“算法类型”和“密钥类型”分开想了。库如果写得松,流程就会变成“先读 token 头,看它说自己是什么算法,再拿你给我的 key 去试着验”。这就是烂设计:把安全决策交给了攻击者可控的输入。好一点的实现应该反过来,服务端先固定这个接口只接受 RS256,再用 RSA 公钥按 RS256 去验;token 头里的 alg 只能拿来做一致性检查,不能拿来决定验证路径。

    现实里的防法也很直接。第一,不要接受客户端声明算法,服务端把允许的算法白名单写死。第二,别把“一个 key 对象”同时喂给多种算法路径,更不要让 RSA 公钥有机会落到 HMAC 验证器里。第三,拒绝 alg=none,也拒绝算法和 key 类型不匹配的情况。第四,升级 JWT 库,很多老漏洞本质上是库默认行为太宽松。最后,鉴权不能只看“签名过了”,还要继续校验 issaudexp 和用途,不然你只是把另一种伪造票据放进系统。

    🧪 你在审一个登录网关,发现代码写的是“从 JWT 头读取 alg,然后把配置里的 public_key.pem 传给通用 verify() 函数”。现在系统号称使用 RS256。攻击者最可能怎么打?
    alg

    💡 核心记忆:JWT 的算法选择权绝不能交给 token 头,服务端必须先定算法,再验签。

    #Security

  4. 🔐 SSRF 为什么总能打进内网:问题不在“服务器会访问 URL”,而在“它替用户做了网络决策”很多人第一次听 Server-Side Request Forgery(SSRF)时,会把它理解成“让服务器帮我发一个请求”

    🔐 SSRF 为什么总能打进内网:问题不在“服务器会访问 URL”,而在“它替用户做了网络决策”

    很多人第一次听 Server-Side Request Forgery(SSRF)时,会把它理解成“让服务器帮我发一个请求”。这句话没错,但太轻了,真正危险的地方是:服务器站在一个比攻击者更有权限的位置上,它能看到外网看不到的东西,比如内网服务、localhost 上的调试接口、云主机的元数据地址,甚至某些只允许机器访问的管理 API。攻击者自己碰不到这些目标,就骗你的应用去碰。

    这类漏洞最常见的入口看起来都很“正常”。比如头像上传支持“从 URL 导入图片”,Webhook 支持“回调地址测试”,爬虫支持“抓取网页摘要”,PDF 生成器支持“渲染远程资源”。产品觉得这是便利功能,开发觉得只是调用一下 HTTP 客户端,结果系统却在无意中变成了一个内网代理。只要目标 URL 来自用户输入,而服务端又没有严格限制它能访问哪里,麻烦就开始了。

    为什么它会发生?因为程序经常只检查“这是不是一个合法 URL”,却没有检查“这个 URL 最终会连到谁”。攻击者可以直接填 http://127.0.0.1:8080,也可以用 DNS 解析、302 跳转、IPv6 写法、十进制 IP、甚至 URL 里的用户名密码段来绕过粗糙过滤。更糟的是,很多防守只在字符串层面做黑名单,比如禁掉 localhost,但 127.1[::1]、内网网段、或者先解析到外网再跳转到内网,照样能进去。你以为你在校验文本,攻击者打的是网络语义。

    现实里最危险的一类 SSRF,不是去读某个无聊页面,而是去打云环境的元数据服务。经典例子就是 169.254.169.254。如果应用所在机器能访问这个地址,攻击者可能借 SSRF 拿到临时凭证、实例身份信息、访问密钥,再继续横向移动。这个链条之所以致命,是因为它不需要 RCE,不需要提权,连代码执行都没有,只是“让服务器按你的意思访问了一个地址”。

    真正靠谱的防法也不是多写几个 if。核心思路只有两个:第一,别让用户决定完整目标地址,能做成“从固定白名单站点拉取资源”就别做成“随便给我一个 URL”;第二,如果业务确实需要访问外部 URL,就在发请求前和每次跳转后都做解析与校验,拒绝 localhost、内网网段、链路本地地址、保留地址和非 HTTP/HTTPS 协议。再往前一步,出网层也要拦,别让这台应用服务器天然能访问内网敏感段和云元数据。应用层校验会漏,网络层隔离才是最后那道硬墙。

    还有一个常被忽略的点:不要自动跟随重定向到任何地方。你本来允许访问 https://example.com/image.jpg,结果它 302 到内网地址,你的客户端如果傻乎乎地继续跟,就等于白名单被绕过了。校验必须针对“最终连接目标”,不是只看第一跳。

    🧪 你在审一个“根据用户提供的图片 URL 抓图并生成缩略图”的服务。开发说他们已经禁止了字符串里出现 localhost169.254.169.254,所以 SSRF 已经防住。这个判断对吗?为什么?答案是:

    💡 核心记忆:SSRF 的本质不是“代发请求”,而是“让高权限服务器替攻击者访问本不该暴露的网络位置”。

    #Security

  5. 🔐 MFA 疲劳轰炸为什么能攻破看起来“很安全”的账号很多人以为开了 MFA 就万事大吉,但现实里最容易被打穿的并不是算法,而是人的耐心

    🔐 MFA 疲劳轰炸为什么能攻破看起来“很安全”的账号

    很多人以为开了 MFA 就万事大吉,但现实里最容易被打穿的并不是算法,而是人的耐心。所谓 MFA 疲劳轰炸,指的是攻击者已经拿到了你的密码,接着不断触发登录请求,让你的手机一遍一遍弹出“是否同意登录”。前几次你会警觉,十几次之后就可能在烦躁、困倦,或者误以为是系统抽风的情况下点下“同意”。这不是高深漏洞,它利用的是人会被重复刺激磨掉判断力。

    这类攻击为什么会发生?因为很多推送式 MFA 默认只验证“是不是你点了确认”,却没有强制你验证“你正在确认哪一次登录、从哪里来的登录”。如果提示框只有一个“Approve”,没有地点、设备、时间,甚至没有数字配对,那它更像一个烦人的弹窗,而不是一次真正的身份确认。攻击者不需要破解验证码,只要赌你会被吵到投降。

    现实里的防法也很直接。第一,别把“推送确认”当成最强 MFA,能用 FIDO2 安全密钥就别只靠手机弹窗。第二,如果必须用推送式 MFA,优先启用 number matching,也就是登录端给出一个数字,你必须在手机上输入同样的数字,防止“手滑同意”。第三,给账号加上异常登录限速和风险地理位置提醒,让攻击者没法无限轰炸。第四,用户侧最重要的一条反而很土:突然连续收到一堆 MFA 提示,不是去点拒绝到手软,而是立刻改密码、检查会话、踢掉未知设备,因为这通常说明密码已经泄露了。

    🧪 你是公司员工,半夜手机连续弹出 20 次 Microsoft 365 登录确认,地点显示海外。你已经很困,这时最危险的错误操作是什么?答案:

    💡 核心记忆:MFA 不是“多一个按钮”就安全,凡是只靠你反复点同意的认证,都会被疲劳和误操作打穿。

    #Security

  6. 🔐 Device Code Phishing:为什么“官方登录码”也能被钓鱼很多人对钓鱼的直觉还停留在“假登录页骗密码”,但现在更危险的一类攻击,恰恰不需要你把密码输进假网站

    🔐 Device Code Phishing:为什么“官方登录码”也能被钓鱼

    很多人对钓鱼的直觉还停留在“假登录页骗密码”,但现在更危险的一类攻击,恰恰不需要你把密码输进假网站。它利用的是 OAuth 2.0 Device Authorization Grant,也就是很多电视、游戏机、CLI 工具常见的“请在另一台设备输入 8 位验证码完成登录”。这套流程本来是给“输入能力差的设备”准备的,设计上没问题,问题出在攻击者可以把“真实平台生成的合法验证码”拿来骗你替他授权。

    事情为什么会发生?因为用户看到的是正确域名、正确登录页、正确验证码,整套交互都像真的。攻击者先在目标平台上发起一次 device flow,拿到一个 user_code,然后把这个码通过邮件、IM、假工单、假客服消息发给你,说“你的账户异常,请立刻输入此验证码验证身份”。你一旦真的在官方页面输入并登录,平台会认为“这次授权已经得到用户同意”,接下来访问令牌会发给谁?不是发给你,而是发给最初发起 device flow 的那个客户端,也就是攻击者控制的程序。

    这类攻击最麻烦的地方就在于,它不靠伪造页面,不靠键盘记录,甚至能绕过一部分“只认官方域名”的安全习惯。用户完成的是一次“真授权”,只是授权对象被偷换了。现实里它常见在 Microsoft 365、GitHub、云平台 CLI、企业 SSO 和各种支持“代码登录”的 SaaS 服务里,尤其适合被用在社工攻击里,因为它比“给我密码”更像正常操作。

    防它不能只喊“别乱点链接”,那太空。关键是让用户在授权最后一步看清“你正在授权给谁”。如果页面显示的是陌生应用名、可疑权限范围,或者请求的是长期离线访问(offline_access)、读取邮箱、读取代码仓库、管理组织之类高权限,就该立刻停下。企业侧更实际的做法是限制 device flow 的适用范围,只允许受信客户端使用,给高风险 OAuth 授权加条件访问策略,监控异常的新应用同意记录,还要尽量关闭普通用户对第三方应用的自由授权能力。很多团队的坑在这里:MFA 做了,反钓鱼培训也做了,但 OAuth 同意界面没人管,于是账户还是被拿走。

    🧪 你在公司 IM 里收到一条“IT 支持”消息:由于邮箱系统升级,请你访问 microsoft.com/devicelogin 并输入代码 F7K9-L2Q 完成验证。页面域名正确,登录后也没有报错。这个操作最核心的风险点是什么?


    💡 核心记忆:看到“官方验证码登录”别只看网址,对安全来说更重要的是“我到底在授权给哪个应用、给了什么权限”。

    #Security

  7. 🔐 JWT 算法混淆:为什么“验签了”还是会被伪造很多人第一次接触 JWT(JSON Web Token)时,会把注意力全放在“这个 token 有没有签名”

    🔐 JWT 算法混淆:为什么“验签了”还是会被伪造

    很多人第一次接触 JWT(JSON Web Token)时,会把注意力全放在“这个 token 有没有签名”。问题在于,真正危险的地方常常不是“有没有签”,而是“你拿什么方式去验这个签名”。JWT 头里有个 alg 字段,用来声明签名算法,比如 HS256RS256。如果服务端天真地相信这个字段,攻击者就可能把原本应该用非对称密钥校验的 token,改成对称算法去处理,最后把公开的公钥当成 HMAC 密钥来伪造签名,这就是经典的算法混淆。

    这事会发生,不是因为 JWT 天生不安全,而是因为很多库早期设计得太“灵活”了。开发者把“支持多种算法”当成功能,结果把“由谁决定算法”这个控制权交给了用户输入。用户发来的 token 头里写什么,后端就按什么验,这就等于把门锁类型也交给访客自己选。只要系统同时支持 RS256HS256,又没有把某个 issuer、某条认证链路、某个 key 和固定算法绑定起来,攻击面就出来了。

    现实里的防法也不复杂,但必须硬。第一,服务端不要从 token 里“学习”该用什么算法,而是自己预先写死允许的算法,比如这个服务只接受 RS256,那看到别的算法直接拒绝。第二,key 和算法要绑定,RSA 公钥就只能走 RSA 验签,绝不能被拿去做 HMAC。第三,别把“支持多算法”当兼容性优点,认证链路越单一越安全。第四,选成熟库时看默认行为,凡是允许 alg=none、自动降级、自动猜算法的,都是坑。

    🧪 你在审一个登录系统,设计文档写着“统一使用 JWT”。代码里验签函数直接读取 token header 里的 alg,然后从配置里取一把 public key 去验。开发者说“反正 public key 本来就是公开的,没问题”。这里最致命的风险是什么?algHS256

    💡 核心记忆:认证里最危险的不是没校验,而是把“怎么校验”这件事交给攻击者决定。

    #Security

  8. 🔐 JWT 算法混淆:为什么“验签”也会被你自己绕过很多人第一次接触 JWT(JSON Web Token)时,会把它理解成“服务端签个名,客户端带回来,后端再验一下”

    🔐 JWT 算法混淆:为什么“验签”也会被你自己绕过

    很多人第一次接触 JWT(JSON Web Token)时,会把它理解成“服务端签个名,客户端带回来,后端再验一下”。问题就出在这个“验一下”常常不是在验安全边界,而是在验一段可被攻击者影响的元数据。JWT 头里有一个 alg 字段,告诉服务端“我是用什么算法签的”。如果你的代码把这个字段当成可信输入,攻击者就能试着把原本该用非对称签名的 token,伪装成对称签名的 token,让服务端拿本该公开的公钥去当作 HMAC 密钥验证,结果就是:明明没有私钥,也能伪造出看起来合法的身份令牌。

    这事为什么会发生?因为开发者把“算法选择权”交给了 token 自己。系统原本设计是:服务端预先知道应该接受 RS256 还是 ES256,验签逻辑按固定规则走。但一旦实现变成“先读 token 头里的 alg,再决定怎么验”,安全边界就倒了。攻击者最喜欢的就是这种“由输入决定防御方式”的代码,因为它表面上看很灵活,实际上是在让敌人选你用哪把锁。

    现实里它最容易出现在两类地方。第一类是直接调用 JWT 库的默认解析函数,没有显式限制允许的算法集合。第二类是多语言微服务里,一个服务负责签发,另一个服务只会“通用校验”,结果把 RSA 公钥、HMAC 密钥、甚至 JWK 拉取逻辑混在一起。代码能跑,不代表边界是对的。安全协议里最危险的 bug,往往不是密码学算法弱,而是工程实现把“谁说了算”搞反了。

    防这类问题不靠口号,靠死规则。服务端必须自己固定可接受的算法,不从 token 里“学习”算法;对称密钥和非对称密钥的校验路径必须彻底分开,别写一个万能 verify(token, key) 以为很优雅;kidjkux5u 这类会影响取钥方式的字段也不能随便信,否则你修完算法混淆,转头又把密钥来源交给了攻击者。说穿了,这不是数学问题,是边界控制问题:令牌内容可以被看见,但不能决定验证策略。

    🧪 你接手了一个旧系统,登录服务签发的是 RS256 的 JWT,资源服务在校验时直接读取 token 头里的 alg,然后调用同一个 verify(),传入的 key 是公开的 RSA 公钥。现在有人说“既然没有私钥,伪造 token 不可能”。这句话错在哪?
    alg

    💡 核心记忆:JWT 的 alg 可以被看见,但绝不能由它来决定你怎么验签,算法和密钥类型必须由服务端预先固定。

    #Security

  9. 🔐 为什么 SSRF 总能打到云主机的“内脏”很多人第一次接触 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会把它理解成“让服务器替我发一个请求”

    🔐 为什么 SSRF 总能打到云主机的“内脏”

    很多人第一次接触 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会把它理解成“让服务器替我发一个请求”。这句话没错,但太轻了,真正危险的地方在于:服务器看到的网络世界,和普通用户看到的不是一回事。你的浏览器访问不到 127.0.0.1、内网地址、云平台元数据服务,但业务服务器往往可以。于是一个看起来只是“帮你抓取图片 URL”“读取 webhook 地址”“预览远程文件”的功能,只要把用户提供的地址直接交给后端去请求,就可能变成一把通向内网的钥匙。

    为什么这事总发生?因为开发者脑子里想的是“这是个 URL 字符串”,攻击者脑子里想的是“这是一次由高权限网络位置发起的连接”。一旦后端没有严格限制目标地址,攻击者就会把请求打向本地服务、Redis、管理面板,或者云环境里最经典的目标:metadata service。比如在 AWS 里,169.254.169.254 这个地址可能暴露实例身份凭证;一旦拿到临时 Access Key,问题就不再是“读到一段数据”,而是横向访问对象存储、消息队列,甚至整个云账号里的其他资源。

    现实防护的关键,不是写一堆黑名单字符串匹配,因为那很容易被绕过。你拦 127.0.0.1,别人就用十进制、八进制、IPv6、DNS 解析跳转,或者先指向外部域名再让它解析到内网。真正靠谱的做法是把“服务器能主动连谁”变成默认拒绝,只允许业务明确需要的目标。也就是说,先做 egress allowlist,再做 URL 解析后的真实地址校验,校验的是最终 IP,不是用户输入的那串文本。同时,云上一定要关心 metadata 的保护机制,比如 AWS IMDSv2,不要让实例凭证裸奔;应用层面则尽量别让后端去请求用户任意给的 URL,如果业务上非做不可,就把它扔进隔离网络和低权限容器里跑。

    🧪 题目:某文件预览服务支持用户输入一个链接,后端会下载内容并生成缩略图。开发者已经拦截了字符串里出现 127.0.0.1localhost169.254.169.254 的情况,于是觉得 SSRF 风险已经解决。这个判断对吗?为什么?答案:

    💡 核心记忆:SSRF 的本质不是“输入了恶意 URL”,而是“你让服务器替攻击者站在更高权限的位置看网络”。
    #Security

  10. 🔐 SSRF 为什么总能绕过“只允许内网访问”的想象很多人第一次见到 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会把它理解成“攻击者让服务器替自己发一个请求”

    🔐 SSRF 为什么总能绕过“只允许内网访问”的想象

    很多人第一次见到 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会把它理解成“攻击者让服务器替自己发一个请求”。这句话没错,但还不够危险。真正麻烦的地方在于,服务器所在的位置、网络权限、身份凭证,往往和外部用户完全不是一个级别。你在浏览器里访问不到的地址,应用服务器却可能轻松访问;你拿不到的云平台元数据接口,后端代码却默认能连。于是一个看起来只是“帮我抓一下图片 URL”或者“帮我读取远程 webhook 内容”的功能,突然就变成了进入内网的跳板。

    它为什么会发生?根子通常不是“黑客太厉害”,而是产品和工程习惯出了问题。开发者喜欢写一些通用抓取能力,比如导入头像、预览链接、拉取回调、抓取 PDF、解析 Open Graph 信息。这些功能本质上都在做一件事:让服务器主动请求一个由用户提供的地址。如果这里没有严格限制目标协议、目标主机、跳转行为和解析结果,那用户给的就不再是“一个普通 URL”,而是一条让服务器替他探路的命令。最典型的情况是,开发者只在字符串层面做检查,比如“只要不是 127.0.0.1 就行”,结果攻击者换成 0.0.0.0、localhost 的变体、IPv6 写法、带 DNS 解析的域名,甚至先请求一个外部域名再 302 跳转到内网地址,过滤就成了摆设。

    现实里最值钱的 SSRF 目标,不是首页,不是某个后台页面,而是云环境里的 metadata service。比如在 AWS 里,历史上很多 SSRF 攻击最后拿到的不是“一个页面内容”,而是实例角色的临时凭证。拿到这个东西,问题就从“读了一下内网接口”升级成“可以调用云 API 了”。这也是为什么现在很多云厂商都在推动更严格的元数据访问机制,比如 AWS IMDSv2 要求先拿 token,再访问元数据;它的目的不是让流程更麻烦,而是让那种“随手一发 HTTP 请求就能读凭证”的情况直接失效。

    防 SSRF 不能靠一句“我们加个黑名单”。黑名单是补丁思维,迟早漏。更靠谱的做法是把“服务器能替用户访问什么”收窄成一个很小的白名单问题。比如这个功能如果只是抓头像,那就只允许 https,且只允许访问少数明确的公共域名;如果是 webhook 回调,就让它只打到预先登记过的目的地;如果业务上根本不需要服务器主动访问外部 URL,那就别做这个能力。再往下一层,网络出口也该有限制,应用容器不该随便访问内网管理面、云元数据地址、本机 loopback。代码层检查和网络层隔离都要有,缺一个都容易翻车。

    🧪 你在审一个“网页截图”功能:用户提交任意 URL,后端用无头浏览器打开后返回截图。开发者说“没事,我们已经禁止了 127.0.0.1localhost,所以内网是安全的”。这句话最大的问题是什么?127.0.0.1localhost

    💡 核心记忆:SSRF 的本质不是“请求长得可疑”,而是“你让服务器替用户决定它该连谁”。

    #Security

  11. 🔐 为什么 SSRF 总能打到云主机元数据服务很多人第一次学 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会把它理解成“让服务器替你访问一个 URL”

    🔐 为什么 SSRF 总能打到云主机元数据服务

    很多人第一次学 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会把它理解成“让服务器替你访问一个 URL”。这句话没错,但太轻了。真正危险的地方在于:服务器看到的网络世界,和外部用户看到的根本不是一回事。你在浏览器里访问不到 127.0.0.1169.254.169.254、内网 Redis、Kubernetes API,但应用服务器自己往往能访问,因为这些地址本来就是给“机器自己”用的。

    云环境里最经典的目标就是元数据服务(metadata service)。以 AWS 为例,老版本 IMDSv1 会在 169.254.169.254 这个链路本地地址上提供实例身份信息,甚至临时凭证。开发者本来是想让自己写的程序方便拿到 IAM Role 凭证,于是把这扇门开在机器旁边。问题在于,只要你的应用里有“帮用户取 URL 内容”“抓取图片”“网页预览”“导入远程文件”这种功能,而又没有严格限制目标地址,攻击者就能把这个功能拐弯用到元数据服务上。表面上是应用在请求,实际上是攻击者借你的服务器拿钥匙。

    这就是为什么现实里的 SSRF 经常不是直接打外网,而是先摸内网,再拿凭证,再横向移动。你以为只是一个读网页的小功能,最后可能变成云账号权限泄露。很多事故不是因为密码太弱,而是因为服务器被允许替别人访问了它本不该访问的地方。

    防 SSRF 也别搞成一堆漂亮口号,核心就几件事。第一,不要做“任意 URL 获取器”,最好改成白名单目标,只允许访问明确批准的域名和协议。第二,解析 URL 不能只看字符串,要在 DNS 解析后校验最终 IP,拦住 127.0.0.0/810.0.0.0/8172.16.0.0/12192.168.0.0/16169.254.0.0/16 这些本地和内网地址,不然别人会用域名跳转、DNS Rebinding、整数 IP、IPv6 映射这些花样绕过去。第三,从网络层限制出站访问,比在业务代码里堆 if 靠谱得多。第四,在云上直接关掉高风险入口,比如 AWS 优先使用 IMDSv2,它要求先拿 token,再访问元数据,能挡掉一批最粗暴的 SSRF 利用链。

    🧪 某团队做了一个“上传头像 by URL”的功能,后端会下载用户给的图片链接并保存。开发者已经禁止了 URL 中出现 169.254.169.254 这个字符串,于是觉得元数据服务安全了。攻击者改用一个自己控制的域名,DNS 解析结果指向 169.254.169.254。这个防护还挡得住吗?为什么?


    💡 核心记忆:SSRF 的本质不是“让服务器访问一个链接”,而是“借服务器的网络位置和身份去碰你碰不到的东西”。
    #Security

  12. 🔐 Silver Ticket:为什么伪造一个服务票据,能直接绕过很多 Windows 横向移动防线很多人先记住 Kerberos 里最危险的是 Golden Ticket,但现实攻防里,Silver Ticket 往往更安静,也更贴近“已经拿下一台机器后怎么继续扩大权限”这个场景

    🔐 Silver Ticket:为什么伪造一个服务票据,能直接绕过很多 Windows 横向移动防线

    很多人先记住 Kerberos 里最危险的是 Golden Ticket,但现实攻防里,Silver Ticket 往往更安静,也更贴近“已经拿下一台机器后怎么继续扩大权限”这个场景。它的核心不是伪造整套登录身份,而是伪造“某个具体服务愿意接受的票据”。如果攻击者已经拿到了某个服务账户的密钥,比如机器账户、IIS 应用池账户,或者跑 SQL Server 的域账户,他就能自己离线构造一张 TGS(Ticket Granting Service ticket),然后直接去访问这个服务。关键点在于,这张票据不一定要再去找域控签发,所以很多只盯着 DC 日志的检测思路会漏掉它。

    为什么会发生?因为 Kerberos 的服务票据本质上是“拿服务自己的密钥加密,证明这个客户端被允许访问你”。服务端收到票据后,只要能用自己的密钥解开,并且里面的字段看起来成立,就可能放行。也就是说,验证动作发生在服务端,而不是每次都回头问域控“这票是真的吗”。这就是 Silver Ticket 的土壤:一旦服务密钥泄露,攻击者就能伪造“发给这个服务看的证明”。它不像 Golden Ticket 那样掌控整个域,但对文件共享、远程管理、MSSQL、HTTP/SPN 绑定服务这些目标已经足够致命。

    现实里它常被用在“低噪声横向移动”。比如攻击者先拿下一台服务器的本地管理员,再导出这台机器账户的哈希。接着他可以伪造访问 cifs/host 的票据去碰 SMB,共享、计划任务、远程服务控制都可能被串起来。如果拿到的是 http/mssqlsvc/ 这类 SPN 对应账户的密钥,攻击面就会变成 Web 管理后台、数据库服务甚至应用层数据。它可怕的地方不在“理论上能做”,而在于很多环境里服务账户权限本来就配得过大,结果一个服务密钥泄露,后面不只是这个服务出事,而是一串依赖它的资源一起沦陷。

    防它不能靠空话。第一,别让服务账户长期复用静态高权限口令,优先用 gMSA 这类自动轮换的托管账户。第二,机器账户和服务账户权限要收紧,别让一个跑业务的账户顺手拥有本地管理员、远程登录或数据库超权。第三,检测上不要只看 TGS-REQ/TGT 申请链路,因为 Silver Ticket 可能根本不经过正常申请;你要盯的是“服务端看到一个 Kerberos 登录,但域控侧没有对应票据签发痕迹”的不一致,还有异常 SPN 访问、伪造票据常见的生命周期和 PAC 特征。第四,一旦怀疑泄露,处理不是“删个会话”就完了,而是必须轮换对应服务账户或机器账户密钥,不然伪造能力还在。

    🧪 你在一台 IIS 服务器上发现攻击者导出了运行站点的域服务账户 NTLM/AES 密钥。第二天,域控上几乎没有新的异常票据申请日志,但这台 IIS 后面的 SQL Server 和文件共享却出现了来自该用户身份的 Kerberos 访问。更可能发生了什么?为什么只盯域控日志会漏报?


    💡 核心记忆:Silver Ticket 的本质不是“偷到一次登录”,而是“拿到服务密钥后,自己伪造这个服务愿意相信的票据”。

    #Security

  13. 🔐 OAuth 回调劫持:为什么一个“差不多匹配”的 redirect_uri 会变成账号接管很多人以为 OAuth 登录的风险主要在密码,其实大量事故根本不碰密码,而是出在回调地址 redirect_uri 的校验太松

    🔐 OAuth 回调劫持:为什么一个“差不多匹配”的 redirect_uri 会变成账号接管

    很多人以为 OAuth 登录的风险主要在密码,其实大量事故根本不碰密码,而是出在回调地址 redirect_uri 的校验太松。OAuth 的核心流程是:用户在身份提供方登录后,授权码 code 会被浏览器带回业务方预先登记的回调地址。如果服务端只做“包含关系”“前缀匹配”或者允许任意子路径、任意子域,攻击者就能把本该回到你应用的授权码,骗去自己的页面,再拿这个 code 去换取 access token,结果就是用户刚正常登录,账号却被别人接管了。

    为什么这种事会发生?因为很多系统把“能跳回自己网站”理解成了“域名看起来像自己就行”。比如把 https://app.example.com/callback 放宽成 https://app.example.com/*,甚至更糟,允许 https://*.example.com/*。一旦某个子域可被上传内容、可被接管,或者存在开放重定向,攻击者就能把 OAuth 服务器返回的 code 转走。麻烦在于,这不是用户输错密码,也不是浏览器中毒,而是协议落地时把“精确绑定”做成了“模糊容忍”。

    现实防护也别搞玄学,直接把边界钉死。redirect_uri 必须做精确匹配,最好精确到完整 URL;不要支持通配符子域;不要依赖“先跳到本站再 302 到别处”的补救设计;客户端在发起授权时要带高强度 state 防 CSRF,移动端和 SPA 还应该用 PKCE,避免授权码被截获后直接复用。还有一个常被忽略的点:如果业务里存在 open redirect,它本身就可能成为 OAuth 回调劫持的跳板,所以开放重定向不是“小漏洞”,放到认证链路里就会升级。

    🧪 场景题:某公司把 OAuth 回调地址配置成 https://login.example.com/*,而 https://login.example.com/redirect?next= 存在开放重定向。攻击者发起授权请求,把回调写成 https://login.example.com/redirect?next=https://evil.com/catch。用户完成登录后,授权码最终会到谁手里?为什么?答案:login.example.comevil.com/catch

    💡 核心记忆:OAuth 最怕的不是密码泄露,而是把授权码送错地方,所以 redirect_uri 必须精确绑定,不能“差不多就行”。

    #Security

  14. 🔐 JWT 算法混淆:为什么“会验签”也可能被绕过很多人第一次接触 JWT(JSON Web Token)时,会把它理解成“服务端签了名,客户端带回来,后端验一下就安全了”

    🔐 JWT 算法混淆:为什么“会验签”也可能被绕过

    很多人第一次接触 JWT(JSON Web Token)时,会把它理解成“服务端签了名,客户端带回来,后端验一下就安全了”。问题就在这里:如果验证代码先相信了令牌头里的 alg 字段,再决定用什么方式验签,攻击者就有机会把“安全设计”变成“自己选规则的考试”。这类问题通常叫算法混淆,最经典的是把原本应该用非对称签名的 RS256,骗成用对称签名 HS256。服务端如果错误地把公开的 RSA 公钥当成 HMAC 密钥去验,攻击者就能自己伪造一个“合法”令牌,因为公钥本来就是公开的。更糟的是,早年还有一些库接受 alg=none,等于告诉服务端“这张票没签名,但你也别查了”。

    它为什么会发生,不是因为密码学本身玄学,而是因为实现者把“算法选择权”交给了不可信输入。JWT 的 header 是令牌的一部分,本来就可以被用户改,后端却常常写成“你说你是 HS256,那我就按 HS256 验;你说你是 RS256,那我就按 RS256 验”。这就像门卫先问来的人“你想按什么规则进门”,然后真的照做。现实里这种 bug 常出现在统一认证网关、微服务中间件、自己封装的登录组件里,尤其是“支持多种算法、多个 issuer、多个环境”的代码,分支一多,垃圾就冒出来了。

    真正的防法很直接:算法不能从 token 里“动态信任”,而要由服务端配置写死。某个 issuer 只允许某一种算法,某把 key 只配某一种用途,验签库也要显式指定允许列表,而不是吃默认值。再往前一步,连 kid(key id)也不能无条件信任,否则又会出现路径穿越、远程取 key、缓存投毒这种后续事故。简单说,token 可以携带“声明”,但不能决定“规则”;规则必须在服务端。

    🧪 你在审计一个内部 API 网关,发现它会先解析 JWT header,再根据其中的 alg 决定调用 HMAC 还是 RSA 验签;而且 RSA 公钥能从公开的 JWK endpoint 拿到。现在系统原本使用 RS256。最危险的攻击路径是什么?alg

    💡 核心记忆:JWT 的 header 可以看,但绝不能信,验签算法和密钥类型必须由服务端预先绑定,不能让令牌自己决定。
    #Security

  15. 🔐 OAuth Device Code Phishing:为什么“官方登录码”也会把账号送出去很多人听到钓鱼,脑子里先想到假登录页

    🔐 OAuth Device Code Phishing:为什么“官方登录码”也会把账号送出去

    很多人听到钓鱼,脑子里先想到假登录页。可 OAuth Device Code Phishing 更阴一点,它经常连密码都不直接偷。攻击者会先在真正的身份提供方发起一次设备授权流程,然后把那串看起来很正规的 code 和登录地址发给受害者,说“这是公司 VPN / Microsoft 365 / GitHub / 云平台的验证步骤,你去官方页面输入这串码就行”。受害者确实打开了真的官网,也确实把 code 输进了真的页面,于是心理防线会瞬间放松,觉得“我都在官方站点操作了,怎么会有问题”。

    问题就出在这里。Device Code 本来是给电视、CLI、打印机这类不方便输入账号密码的设备准备的。设备先拿到一个 device_code 和 user_code,用户再去另一台设备上完成授权。攻击者如果先替你发起这个流程,他就已经拿着 device_code 在轮询了。你一旦在官方页面完成授权,令牌不是发到你手里,而是发给最初发起流程的那一端,也就是攻击者控制的客户端。整个过程里,用户没有把密码输进假站,却把“授权结果”亲手交了出去。

    这类攻击现实里很容易成功,因为它利用的是人对“官方域名”和“验证码式操作”的天然信任。很多企业还把 MFA 当成护身符,但这招绕的不是密码强度,而是授权边界。只要你的账号被诱导去“批准一个本不该批准的客户端”,MFA 也可能只是帮攻击者完成了最后一道门禁。更糟的是,如果授权范围带有 Mail、Files、Graph API 或 repo 访问权,后果不是一次登录成功,而是持续性的 API 令牌滥用。

    防它不能只喊“别点陌生链接”。真正有用的是把“谁发起授权”这件事钉死。企业侧要限制 Device Code Flow 的适用范围,能关就关,不能关就至少对高权限应用禁用;对 Entra ID、Google Workspace、GitHub OAuth 这类平台,要审计哪些应用能走设备授权,哪些租户允许用户自助同意。用户侧则要养成一个硬习惯:看到输入 code 的登录流程,不只看域名,还要看“你正在授权给谁”。如果页面上显示的应用名、发布者、权限范围和你当前要做的事对不上,立刻停。安全团队还应该监控异常的设备授权事件、短时间内的 consent 增长、来自不常见客户端 ID 的 token 签发,以及拿到令牌后立刻访问邮件或文件 API 的行为链。

    🧪 你在公司 IM 里收到“IT 支持”消息,让你去 microsoft.com/devicelogin 输入一串 8 位 code,说这是修复 Outlook 同步问题的标准流程。页面域名是真的,MFA 也正常弹出。这个场景里,最关键的风险信号是什么?

    💡 核心记忆:设备码登录最危险的地方不是假官网,而是你在真官网上替攻击者完成了授权。

    #Security

  16. 🔐 反序列化为什么危险:对象不是数据,代码也会跟着进来很多人第一次接触反序列化漏洞,会觉得它只是“把字符串还原成对象”而已,听起来像普通的数据解析

    🔐 反序列化为什么危险:对象不是数据,代码也会跟着进来

    很多人第一次接触反序列化漏洞,会觉得它只是“把字符串还原成对象”而已,听起来像普通的数据解析。但危险就在这里:如果一种序列化格式不只是保存数据,还保存“对象类型”和“构造方式”,那程序在反序列化时就可能顺手执行一串你没打算执行的逻辑。Java 的 readObject、Python 的 pickle、PHP 的 unserialize 都出过这类问题,本质不是库“脆弱”,而是你把“不可信输入”当成了“可信对象”。

    为什么会发生?因为开发者脑子里常有个错觉:来自数据库、缓存、消息队列、Cookie、文件上传里的内容,看起来像内部格式,就默认安全。可攻击者只要能控制这段序列化数据,就能伪造一个对象图,让程序在恢复对象时自动调用魔术方法、钩子函数或者 gadget chain。于是反序列化不再是“读数据”,而变成了“按攻击者指定的方式跑代码”。现实里常见后果包括远程命令执行、任意文件读写、权限绕过,甚至只是一个签名校验缺口,也足够让攻击者把本地组件串成武器。

    真正实用的防法也不是“多加几个黑名单类名”。黑名单是补丁思维,迟早漏。更靠谱的做法是别用会恢复任意对象的格式处理外部输入,能用 JSON、MessagePack 这种纯数据格式,就别把类实例直接扔出去再捡回来。如果历史包袱太重,至少要做三件事:第一,反序列化前做严格完整性校验,比如带密钥的签名,而不是裸 base64;第二,限制可反序列化的类型白名单;第三,把危险逻辑从对象的构造、析构、魔术方法里拆出去,别让“恢复对象”顺手触发副作用。说白了,输入应该只是数据,不该拥有决定程序控制流的权力。

    🧪 你在审计一个老 Web 系统,发现它把登录态存在 Cookie 里,值是 base64 后的 PHP serialize 字符串。开发者说“攻击者看不懂内容,而且 Cookie 里没有命令执行代码,所以问题不大”。这里最关键的风险点是什么? unserialize()

    💡 核心记忆:只要外部输入能被还原成“对象”,你就得假设它可能把程序控制流一起带进来。
    #Security

  17. 🔐 JWT 算法混淆攻击:当你的服务器把公钥当成对称密钥来用JWT(JSON Web Token)是现代 Web 认证的标配

    🔐 JWT 算法混淆攻击:当你的服务器把公钥当成对称密钥来用

    JWT(JSON Web Token)是现代 Web 认证的标配。一个 JWT 由三段组成:Header、Payload、Signature,用点号拼接。Header 里有个关键字段叫 alg,声明这个 token 用什么算法签的名。服务器验证时,本应严格按 Header 里声明的 alg 去执行对应的验签逻辑。问题就出在这里——很多库的实现没有在验签前把 alg 绑定到服务端预期的算法上,而是直接信任客户端传来的 Header。

    典型的混淆场景是这样:服务端用 RS256(非对称,私钥签名、公钥验签)签发 token。攻击者拿到一个合法 token 后,把 Header 的 alg 改成 HS256(对称,同一个密钥既签名又验签),然后用服务端公开的 RSA 公钥作为 HMAC 密钥重新签名整个 token。服务端收到后看到 alg 是 HS256,就走进 HMAC 验签分支,拿"密钥"去算 HMAC——而这个"密钥"恰恰就是攻击者手上有的那把 RSA 公钥。验签通过,攻击者以任意身份通过认证。

    这个漏洞的本质是一个信任边界错位。RSA 公钥是设计用来公开分发的,任何人都能拿到。但 HS256 要求签名和验签共享同一个秘密。当服务端的验证代码把"公钥"喂进 HMAC 验签函数时,公钥就从"谁能拿到都无所谓"变成了"谁能拿到谁就能伪造 token"。这不是 RSA 或 HMAC 自身的问题,是上层代码没有约束算法选择造成的。

    现实中防御的核心只有一条:服务端必须在验证之前,把 alg 写死成一个预期值,不信任 token Header 里客户端声明的算法。大多数现代 JWT 库已经修复了这条路径,比如 Node 的 jsonwebtokenjwt.verify() 时要求显式传入 asymmetricPublicKeysecret,库内部会拒绝用公钥做 HMAC。但如果你用的是老旧库版本,或者自己拼了验证逻辑,这条攻击路径依然敞开。另外,如果服务端含有 alg: none 的回退逻辑——即允许无签名的 token 通过——那是另一条同样危险的路径,根本原因一样:信任了客户端对自己如何被认证的自述。

    🧪 你的服务用 RS256 签发 JWT,公钥在 /.well-known/jwks.json 公开。攻击者把 alg 从 RS256 改成 HS256,用拿到的公钥做了 HMAC 签名,服务端居然验证通过了。最小改动应该改哪里?

    algorithms: ['RS256']alg

    💡 核心记忆:安全验证永远不信任客户端声明的算法——alg 字段是别人说的话,只有服务端写死的算法才是你定的规矩。

    #Security

  18. 🔐 为什么 SSRF 能打到“你以为外面碰不到”的内网很多人第一次看到 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会误以为它只是“让服务器帮我访问一个网址”

    🔐 为什么 SSRF 能打到“你以为外面碰不到”的内网

    很多人第一次看到 SSRF(Server-Side Request Forgery,服务端请求伪造)时,会误以为它只是“让服务器帮我访问一个网址”。问题没这么简单。真正危险的地方在于,请求不是从攻击者电脑发出去的,而是从你的业务服务器、云主机、容器、函数计算环境里发出去的。也就是说,攻击者借用了“服务器的网络位置”和“服务器的身份”。你前端用户访问不到 127.0.0.1,访问不到 169.254.169.254,也碰不到 Kubernetes API、Redis、Consul、内部管理面板,但你的应用服务器很可能可以。只要业务里存在“根据用户输入去拉取 URL”的功能,比如图片抓取、网页预览、Webhook 回调测试、导入远程文件、头像同步,SSRF 就有机会出现。

    它为什么会发生?根子通常不是“少了一个过滤”,而是设计上默认相信了“用户给的 URL 只是普通外链”。现实里 URL 不是一个简单字符串,它能指向内网 IP、环回地址、IPv6、本地域名,甚至还能借重定向跳转到你原本不想访问的地方。更糟的是,很多程序员只做了表面校验,比如禁止 127.0.0.1,结果攻击者改用十进制 IP、IPv6 映射、DNS 解析变化,或者先访问一个看起来正常的域名,再被 302 重定向到内网地址。补丁式拦截会越补越乱,因为特殊情况太多了。

    真正的防法不是写一堆 if/else 去猜攻击者会怎么绕,而是把“服务器代替用户访问任意 URL”这件事收紧成一个小得多的问题。业务上如果只需要抓取你自家 CDN 的资源,那就只允许固定域名和固定协议;如果必须支持第三方地址,就在出网层做白名单和网段封锁,直接禁止访问内网、元数据地址和管理平面;同时关闭自动跟随重定向,或者每跳都重新校验目标地址。云环境里还要单独保护实例元数据服务,比如 AWS IMDSv2,就是因为历史上太多 SSRF 最后都不是“读了个页面”,而是拿到了临时凭证,再一路横向移动。你要防的不是“有人访问了一个奇怪网址”,而是“服务器替别人做了不该做的网络动作”。

    🧪 你在做“导入头像 by URL”功能,代码先检查用户输入必须以 https://trusted.example.com/ 开头,然后后端请求这个地址下载图片。测试时有人提交的链接确实是这个域名,但服务器最终却访问到了内网服务。最可能的问题是什么?

    💡 核心记忆:SSRF 的本质不是“访问了恶意网址”,而是“攻击者借你的服务器身份和网络位置去碰本来碰不到的东西”。
    #Security

  19. 🔐 JWT Algorithm Confusion:为什么“验签”也会被你自己绕过很多人第一次学 JWT,会把注意力放在“有没有签名”上,却忽略了更关键的一件事:服务端到底是按什么规则验这个签名的

    🔐 JWT Algorithm Confusion:为什么“验签”也会被你自己绕过

    很多人第一次学 JWT,会把注意力放在“有没有签名”上,却忽略了更关键的一件事:服务端到底是按什么规则验这个签名的。JWT 头里有一个 alg 字段,告诉接收方“我用的是哪种算法”。问题就出在这里——如果服务端傻乎乎地信了这个字段,让令牌自己决定该怎么被验证,攻击者就有机会把“本该用非对称密钥验证的令牌”,伪装成“用对称密钥验证的令牌”,甚至在更糟的实现里直接改成 none。这不是密码学失效,而是实现者把控制权交给了不可信输入。

    现实里最典型的坑,是服务端原本想用 RS256。正常情况下,签发方拿私钥签名,验证方拿公钥验证,攻击者只有公钥也没法伪造。但如果验证库或业务代码写得烂,看到 JWT 头里写着 HS256 就切到 HMAC 模式,而且还把“本来公开的 RSA 公钥”当成 HMAC 的 secret 去验,那攻击者就能用这个公开公钥自己生成一个“合法”签名。于是整个认证系统从“只有私钥持有者能签”退化成“谁拿到公钥谁都能签”。这就是 Algorithm Confusion 的本质:不是数学被攻破,是程序把不同信任模型混在了一起。

    为什么这种事会发生?因为很多系统把“解析 token”和“决定安全策略”塞进了一步,开发者图省事,直接把 token 丢进库里让它自动判断。自动判断在业务代码里通常就是灾难。安全边界不该由攻击者上传的头字段决定,应该由服务端配置决定:这个接口只接受 RS256,那就硬编码只验 RS256;这个 issuer 的 key set 是什么,也应该提前绑定,而不是收到什么就信什么。再往前一步,kid 这种字段如果还能触发本地文件读取、远程取 key、或从多个 key 中随便选,那问题只会更大。

    防御其实不复杂,但很多团队就是不做。第一,服务端必须显式指定允许的算法,不能从 token 头里“协商”。第二,不要把同一套验证逻辑同时兼容 HMAC 和 RSA/EC,混用就是在制造特殊情况。第三,验证时把 issuer、audience、过期时间、not before 一起校验,不要只看签名过不过。第四,选库时看默认行为,凡是“自动推断算法”“自动接受 none”“随手喂一个 key 就能跑”的库,都该提高警惕。安全问题最烦的地方就在这:代码看起来能跑,用户也能登录,但边界已经烂了。

    🧪 你在审一个老系统,发现它验证 JWT 的代码会先读取 token 头里的 alg,如果是 RS256 就用公钥验签,如果是 HS256 就把同一份配置里的 publicKey 字符串当作 HMAC secret。现在攻击者只能拿到公开公钥,不能拿到私钥。这个系统还能被伪造管理员 token 吗?为什么?答案:algHS256

    💡 核心记忆:JWT 的算法选择权必须在服务端手里,绝不能交给 token 头自己决定。

    #Security

  20. 🔐 为什么 PKCE 能挡住 OAuth 授权码劫持很多人第一次接触 OAuth 2.0,会以为拿到授权码 authorization code 就已经很安全了,因为真正换 access token 还要再走一步

    🔐 为什么 PKCE 能挡住 OAuth 授权码劫持

    很多人第一次接触 OAuth 2.0,会以为拿到授权码 authorization code 就已经很安全了,因为真正换 access token 还要再走一步。问题就在这里:如果攻击者能在这“一步之隔”里偷到授权码,而服务端又没有额外绑定这个授权码到底属于谁,那他就能自己拿着这串 code 去换 token,等于把你的登录结果截胡了。这类问题最常见在移动端、桌面端、单页应用,或者 redirect_uri 校验做得很松的时候。

    PKCE 的核心不是“加密授权码”,而是给这次登录流程加一条只有发起者自己知道的暗号。客户端先生成一个随机字符串 code_verifier,再把它变成 code_challenge 发给授权服务器。等用户完成登录、服务器把 authorization code 发回来之后,客户端还必须拿出原始的 code_verifier 才能换 token。这样一来,攻击者就算在中途截获了 authorization code,没有最初那串 verifier,也换不到 token。你可以把它理解成:授权码只是取件号,真正能取走包裹的,是取件号加取件密码。

    这事为什么会发生?因为 OAuth 最早更多是给“有后端、能保管 client_secret 的 Web 应用”设计的,后来移动 App、SPA、桌面工具也大量使用同一套协议,但这些环境根本藏不住 secret。开发者如果还沿用“有 code 就能换 token”的思路,就会把本来应该在后端完成的信任关系,错误地暴露给不可信的客户端环境。PKCE 本质上是在承认现实:前端和原生客户端不值得被默认信任,所以必须给授权码再加一次绑定。

    现实里怎么防,关键不是“支持 PKCE”四个字,而是别把它做成摆设。第一,公开客户端 public client 一律强制 PKCE,别当成可选项。第二,code_verifier 必须足够随机,不能自己手搓一个短字符串糊弄过去。第三,优先使用 S256,不要退回 plain。第四,redirect_uri 要精确匹配,不能只按前缀比较,不然攻击者能把授权码送到他自己的地址。第五,授权码必须短时有效且一次性使用,用过立刻作废。第六,客户端别把 token 存在随便一个能被脚本读到的地方,不然前面防住了,后面自己又漏了。

    🧪 场景分析题:一家网站的移动 App 已经接入 OAuth 登录。安全测试时发现,App 使用 Authorization Code Flow,但 token 端点只校验 client_id 和 authorization code,没有要求 code_verifier。攻击者能在受害者手机上通过恶意应用劫持回调 URI,拿到这次登录返回的 code。问:攻击者此时能不能换到 access token,真正的问题出在哪?


    💡 核心记忆:PKCE 防的不是“授权码泄露”本身,而是“别人捡到授权码后也能直接换 token”这件事。

    #Security