JWT安全攻防实战:从原理到算法混淆、弱密钥爆破与防御
1. 从一次渗透测试中的“意外”发现说起最近在复盘一个内部靶场环境时遇到了一个关于JWTJSON Web Token的典型场景让我想起了之前“陇剑杯”网络安全竞赛中一道非常经典的题目。那道题没有复杂的漏洞链也没有高深的免杀技巧核心就是考察对JWT这一现代Web应用身份验证“基石”的深入理解。很多刚接触安全的朋友包括当时的我都曾以为JWT就是一个加密过的、不可篡改的字符串拿到手只能干瞪眼。但事实恰恰相反JWT的设计哲学是“签名”而非“加密”这为安全测试人员打开了一扇充满可能性的窗户。今天我就结合这道竞赛题把JWT从原理到攻击面掰开揉碎了讲清楚让你下次再遇到时能像老师傅一样一眼看穿其中的门道。简单来说JWT就是一个用于在各方之间安全传输信息的“令牌”。它由三部分组成头部Header、载荷Payload和签名Signature中间用点号.分隔形如xxxxx.yyyyy.zzzzz。它被广泛用于单点登录SSO、API鉴权和分布式会话管理。这道题的核心就是利用我们对JWT各部分处理逻辑的误解或配置缺陷去伪造一个拥有更高权限的令牌。这不仅仅是CTF的技巧在真实的渗透测试和红队评估中对JWT的审计和测试是Web应用安全中不可或缺的一环。2. JWT的结构拆解远不止“三个点”那么简单要攻击一个东西首先得彻底理解它。JWT的三个部分每一部分都藏着玄机。2.1 头部Header算法声明与格式把戏头部通常是一个JSON对象经过Base64Url编码后形成JWT的第一部分。它最关键的字段是alg用于声明签名所使用的算法。常见的值有HS256使用HMAC SHA-256的对称加密算法。这意味着签名和验证使用同一个密钥secret。这是最常用但也最需要保护密钥的场景。RS256使用RSA SHA-256的非对称加密算法。使用私钥private key签名使用公钥public key验证。公钥可以安全分发更适合分布式场景。ES256使用ECDSA的椭圆曲线数字签名算法同样是非对称的。none一个特殊值表示“无签名”。在某些早期的JWT库实现中如果服务器配置不当允许alg为none的令牌通过验证这将导致严重的安全漏洞。除了alg头部还可能包含typ类型通常为JWT、kid密钥ID用于在多个密钥中指定一个等字段。这里第一个攻击点就出现了算法混淆攻击Algorithm Confusion Attack。如果服务器端代码在验证签名时逻辑是“从令牌头部读取alg字段然后用该算法去验证签名”那么攻击者就可以将alg改为none或者从RS256改为HS256从而绕过验证。例如服务器本应使用RS256非对称公钥是公开的。攻击者将头部改为{alg:HS256,typ:JWT}然后用服务器的公钥作为HMAC的密钥secret去伪造签名。如果服务器验证逻辑有缺陷它会用公钥作为HMAC密钥去验证这个签名从而误判令牌有效。2.2 载荷Payload承载信息的“声明集”载荷同样是一个JSON对象包含所谓的“声明”Claims。声明分为三种注册声明Registered Claims预定义的一些有特定含义的声明非强制但建议使用。例如iss签发者sub主题aud接收方exp过期时间Expiration Time这是一个时间戳Unix epoch。nbf生效时间iat签发时间公共声明Public Claims可以自定义但为避免冲突应定义在IANA JSON Web Token Registry或使用防冲突命名空间如包含一个域名。私有声明Private Claims在提供方和消费方之间约定使用的自定义声明用于传递业务信息如username、role、userid等。载荷部分经过Base64Url编码后成为JWT的第二部分。这里的关键在于载荷本身是未加密的仅做了编码。任何人都可以轻松地将eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Header和eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQPayload解码看到原始内容。因此绝对不要在JWT的载荷中存放任何敏感信息如密码、信用卡号等。攻击面也随之而来如果服务器仅仅依赖JWT中的userid或role字段来判断权限而没有在服务端进行二次校验那么篡改载荷并相应重签就能直接实现越权。2.3 签名Signature完整性的守护者签名是JWT安全的核心。它的生成方式取决于头部声明的算法。对于HS256签名是这样生成的HMACSHA256(base64UrlEncode(header) . base64UrlEncode(payload), secret)对于RS256则是RSASHA256(base64UrlEncode(header) . base64UrlEncode(payload), private_key)签名的目的是验证消息在传输过程中未被篡改。验证方使用头部声明的算法和相应的密钥对称算法的secret或非对称算法的公钥对“头部.载荷”部分重新计算签名并与JWT中的第三部分进行比对。如果一致则证明令牌有效且未被修改。3. 实战攻击手法详解以“[陇剑杯 2021]jwt”为例理解了原理我们来看实战。这类题目的常见解题路径往往围绕以下几个关键攻击面展开。我们假设题目环境是一个Web应用登录后获得一个JWT目标是提升权限如从普通用户user提升为管理员admin。3.1 第一步信息收集与令牌解码拿到题目首先用浏览器开发者工具或Burp Suite抓取登录后的请求找到Authorization: Bearer your_jwt_token这个Header或者Cookie中的jwt、token字段拿到JWT字符串。接着进行解码。虽然可以手动Base64Url解码但更推荐使用工具如在线网站jwt.io注意不要在真实敏感令牌上使用不可信的在线工具。命令行工具jwt-tool。Burp Suite扩展JSON Web Tokens。解码后我们重点关注头部alg是什么有没有不常见的字段如jwk、kid载荷有哪些声明特别是自定义的username、role、isAdmin等。exp字段的值是多少一个时间戳它过期了吗假设我们解码后得到Header: {alg: HS256, typ: JWT} Payload: {sub: user123, username: guest, role: user, exp: 1698765432}显然我们的目标是修改username或role为管理员身份。3.2 攻击面一弱密钥Weak Secret爆破如果算法是HS256、HS384等对称算法那么密钥secret的强度至关重要。许多开发者在测试或初期会使用弱密钥如secret、password、123456甚至是空字符串。jwt-tool工具内置了强大的爆破功能。使用命令python3 jwt_tool.py your_jwt_token -C -d /path/to/wordlist.txt-C代表“Crack”-d指定字典文件。工具会尝试用字典中的每一个词作为secret去验证签名。如果爆破成功它会直接输出正确的secret。拿到secret后我们就可以用任何JWT库或jwt.io网站修改载荷并用这个secret重新生成合法的签名。实操心得爆破字典的选择很重要。除了常见的弱口令字典可以尝试结合目标应用名称、公司名、项目代号等生成专属字典。有时密钥就是dev、test、changeme这类简单单词。3.3 攻击面二算法混淆攻击CVE-2015-9235等这是历史悠久的经典漏洞。如果服务器端的JWT验证库存在逻辑缺陷可能会接受alg: none的令牌。攻击步骤修改头部为{alg: none, typ: JWT}。修改载荷如将role: user改为role: admin。将签名部分即第三个点号后的内容直接删除或者置空。将修改后的Header.Payload.注意最后有一个点号提交给服务器。另一种混淆是RS256到HS256。如果服务器公钥可获取有时通过/jwks.json端点或网页源码泄露且服务器验证逻辑有缺陷攻击步骤为获取服务器RSA公钥通常为PEM格式。修改JWT头部将alg从RS256改为HS256。修改载荷。使用这个公钥作为HMAC的secret对新的Header.Payload进行HS256签名。发送伪造的令牌。使用jwt-tool可以自动化尝试多种混淆攻击python3 jwt_tool.py your_jwt_token -X a-X a代表“Exploit - All tests”它会自动尝试none算法、混淆攻击等多种方式。3.4 攻击面三无效签名绕过“None”漏洞的变种有些服务器端的验证逻辑可能只检查签名是否存在或者解析JWT结构失败时默认通过。我们可以尝试签名格式错误比如签名部分不是Base64Url编码包含、/等非法字符。签名部分长度不对。在签名后附加额外的点号或字符如Header.Payload.Signature.或Header.Payload.Signature.extra。这些手法的成功率取决于后端使用的具体JWT库及其版本和配置。3.5 攻击面四密钥文件泄露与JKU/JWK/KID滥用这是更高级的攻击面常出现在配置不当或对JWT扩展特性理解不深的场景。JKU头部中的jku参数是一个URL指向一个包含验证密钥的JSON密钥集JWKS。如果攻击者能控制这个URL通过SSRF、域名劫持或上传功能就可以指向自己控制的恶意JWKS从而使用自己的私钥签发任意令牌。JWK头部中直接嵌入一个jwk参数包含用于验证的公钥。攻击者可以替换成自己的公钥。KIDkid是密钥标识符用于在服务器的多个密钥中选择一个。攻击可能包括目录遍历如果kid参数未经过滤像../../../../etc/passwd这样的值可能导致服务器使用文件内容作为密钥可能被预测或利用。SQL注入如果kid用于从数据库查询密钥可能引发SQL注入。命令注入极少数情况下kid可能被用于动态加载密钥导致命令注入。对于“[陇剑杯 2021]jwt”这类题目往往需要综合判断。例如题目可能提示“密钥就在服务器上”结合弱密钥爆破和kid路径遍历如kid: /proc/self/cmdline泄露进程信息进而找到密钥文件路径来解题。3.6 攻击面五时间戳攻击exp, nbf, iat载荷中的exp、nbf、iat都是时间戳。服务器库通常会验证exp是否过期和nbf是否已生效。攻击可能包括时钟偏移利用如果服务器时间配置不同步存在较大偏移可能使一个本应过期的令牌仍然有效。篡改时间戳直接修改exp为一个未来的时间戳。但这需要同时能绕过签名验证通常需要结合其他漏洞如弱密钥、算法混淆一起使用。4. 工具链与手动操作不只是点按钮虽然jwt-tool是瑞士军刀但理解手动过程能加深认知。这里以“弱密钥爆破成功后的令牌伪造”为例展示完整流程。场景我们通过爆破发现目标的HS256密钥是supersecret。原始令牌eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwidXNlcm5hbWUiOiJndWVzdCIsInJvbGUiOiJ1c2VyIiwiZXhwIjoxNjk4NzY1NDMyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c解码分析使用Pythonpyjwt库或在线工具Header:{alg: HS256, typ: JWT}Payload:{sub: user123, username: guest, role: user, exp: 1698765432}修改载荷我们将role: user改为role: admin。注意exp如果已过期也需要改为一个未来的时间戳如9999999999。新的Payload JSON为{ sub: user123, username: guest, role: admin, exp: 9999999999 }手动生成新令牌使用Pythonimport jwt import time # 已知密钥 secret supersecret # 构造新的载荷 payload { sub: user123, username: guest, role: admin, exp: 9999999999 # 一个很远的未来时间 } # 生成新的JWT使用HS256算法和已知密钥 new_token jwt.encode(payload, secret, algorithmHS256) print(new_token)运行后会得到一个新的、使用正确密钥签名的令牌。替换与测试在Burp Suite中用这个新令牌替换原请求中的令牌重放请求。观察响应看是否成功获取了管理员权限的访问例如访问/admin页面或响应中返回了更多数据。踩坑记录在手动编码时务必确保JSON格式完全正确没有多余的逗号字符串使用双引号。一个常见的错误是exp的值没有引号在JSON中它是数字但在某些库的encode函数中载荷是字典类型库会自动处理。如果手动拼接字符串进行Base64编码则必须严格遵守JSON格式。5. 防御视角开发中如何避免这些坑作为攻击者我们寻找漏洞作为开发者或安全工程师我们则要堵上这些漏洞。以下是一些关键防御措施使用强算法和强密钥优先使用非对称算法如RS256、ES256。私钥妥善保存在服务器端公钥可以安全分发。如果必须使用对称算法HS256密钥必须是高强度的随机字符串如通过密码学安全随机数生成器生成并像保护密码一样保护它绝不能硬编码在客户端或前端代码中。严格验证算法在服务器端验证签名时不要依赖客户端提供的alg头。应该有一个应用配置明确指定期望接受的算法列表如只接受RS256。在验证时使用配置中指定的算法和对应的密钥去验证而不是读取令牌头中的alg值。这是防止算法混淆攻击的根本方法。全面验证声明必须验证exp、nbf、iat。验证iss签发者是否可信。验证aud受众是否包含本服务。对于自定义声明如role、userid必须在服务端进行二次校验。例如根据userid从数据库或缓存中查询用户最新的权限信息而不是完全信任JWT中的role字段。JWT应作为会话状态的“引用”而非“权威数据源”。安全处理JKU/JWK/KID如果使用jku或jwk必须严格验证URL是否来自可信的白名单域名并对获取的密钥进行完整性验证。对kid参数进行严格的输入验证防止路径遍历、SQL注入等攻击。使用最新的、经过安全审计的库避免使用已过时或有已知漏洞的JWT库。关注社区安全公告及时更新。设置合理的令牌生命周期访问令牌Access Token有效期宜短如15分钟配合刷新令牌Refresh Token使用减少令牌泄露后的风险窗口。6. 拓展思考JWT在真实红队评估中的位置在真实的渗透测试中JWT漏洞很少孤立存在。它往往是横向移动或权限提升链条中的一环。我们可能需要结合其他漏洞来获取攻击JWT的初始条件信息泄露通过源码泄露、目录遍历、错误配置的.git目录等找到硬编码的JWT密钥或公钥/私钥文件。SSRF利用服务器端请求伪造让服务器从内网或攻击者控制的地址获取JWKSjku从而注入恶意公钥。逻辑漏洞例如注册或密码重置功能处可能允许我们设置自己的username为admin如果JWT的username直接来自用户输入且未做过滤然后再通过JWT重放获得管理员上下文。中间件配置问题某些API网关或反向代理如Kong, APISIX在配置JWT验证插件时如果配置不当也可能引入类似alg: none的漏洞。因此拿到一个JWT后不要仅仅盯着令牌本身。要思考这个令牌从哪里来登录接口、OAuth回调服务器用什么库验证密钥可能存储在哪里是否有其他接口或页面泄露了关键信息这种关联性思维才是将CTF技巧转化为实战能力的关键。回到“[陇剑杯 2021]jwt”这道题它像是一个精致的微缩景观集中展示了JWT最常见的安全问题。通过手动或工具辅助的逐步测试——从解码观察、尝试none算法、爆破弱密钥、检查kid路径遍历到最终伪造高权限令牌——我们完成了一次完整的JWT安全审计流程。掌握它你不仅能在CTF中得分更能在真实的Web应用安全评估中多一双发现漏洞的锐利眼睛。记住令牌只是表象背后的验证逻辑和系统配置才是真正的战场。