
什么是JWTJWTJSON Web Token是一种开放标准RFC 7519定义了一种紧凑、自包含的、以 JSON 对象形式安全传输信息的方式。该信息之所以可以被验证和信任是因为它经过了数字签名。JWT 可以使用密钥HMAC或公/私钥RSA/ECDSA进行签名。虽然 JWT 本身主要定义的是数据格式和声明方式但实际应用中最常见的是经过签名的 JWT即 JWS也可以选择加密的 JWT即 JWE来保证机密性。JWT 为什么会成为认证与授权边界JWTJSON Web Token之所以能取代传统的 Session-Cookie 模型成为现代分布式系统、微服务架构以及云原生应用中事实上的认证与授权边界核心在于它实现了“信任的去中心化”。它从传统的SessionCookie转变到了一种无状态的身份认证用户的身份信息Who、过期时间When、权限角色What全部打包在 Payload 里。后端服务收到 JWT 后不需要查询任何数据库只要在本地用 CPU 算一下哈希或用公钥验签就能在极短的时间内确认用户的合法性。它让服务真正做到了无状态。在RSA的非对称加密的加持下它可以安全的执行这种无状态的认证虽然在数学角度它是绝对安全的但是在各种系统的JWT认证的配置逻辑中往往会出现逻辑问题而这些逻辑问题一旦出现导致JWT被绕过也就是这个边界一旦被突破就会JWT正是因为它承载了绝对的信任所以一旦它的验证机制被绕过后果就是灾难性的全线崩溃。Burp 在 JWT 测试里承担什么角色在 JWT 测试中Burp Suite 扮演着“中间人解剖刀”与“自动化欺骗引擎”的核心角色。它不仅能实时拦截流量将晦涩的 Base64URL 瞬间可视化为明文结构让你直观洞察数据的越权边界更重要的是它替你接管了繁琐的加密运算、格式重组和动态签名过程。这让你能从枯燥的底层密码学拼凑中彻底解放出来将火力完全聚焦于挖掘算法混淆、逻辑绕过等高维安全漏洞。而且portswigger已经针对JWT当前的主要攻击流程做了一系列的Lab环境我们可以通过测试这些Lab环境来快速掌握JWT各种绕过机制。https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature第一类服务端根本没有正确验证签名[Lab: JWT authentication bypass via unverified signature]这是最基础的第一个Lab服务器使用JWT但是不会验证签名也就是说服务器虽然采用JWT进行认证和授权但是却不去检验JWT是否被篡改因为JWT的载荷与头部是透明的如果后端的服务器不去验证签名也就把认证和授权的生杀大权都交个了前端你在参数中怎么改服务器就会怎么认这个Lab非常简单大家可以把重点放到Burp如何调试JWT中开启Lab环境直接在Burp内置的浏览器中打开这个环境由于这个环境是针对我们原生登录portswigger账号开启的独特链接所以不必担心在内置浏览器中操作与你的账号问题打开页面点击页面右侧导航部分的My account登录Lab提示中的账号密码我的是wienerpeter这个开启拦截在浏览器中请求/admin这个链接你会发现拦截中出现绿色的流量这就是带有JWT的流量链接你可以在JSON Web Token中看到针对JWT的操作面板这里可以直观的看到整个JWT的base64编码以及头部和载荷的JSON格式数据你观察一下原始的base64JWT数据可以看到它们是以 . 分隔的三部分刚好对应头部、载荷、签名你在头部和载荷输入框输入的修改的内容Burp会实时的帮你在JWT的原始base64编码处帮你同步这个过程非常平滑。回到这个Lab根据提示服务器不会验证JWT中的签名也就意味着你可以直接修改载荷的sub字段原始值应该是你的用户名你可以直接修改为administrator然后放行流量你可以看到admin面板它非常简单而任务目标就是删掉其中的一个用户你可以点击删除但是由于修改JWT提交验证并不会让你持久化你的权限你需要重复这个上述过程才能完成最终操作操作完成后会提示你完成了这个Lab。[Lab: JWT authentication bypass via flawed signature verification]第二个Lab环境看描述它也是一个签名验证存在问题的环境但是区别于第一台它是接受没有签名的JWT而前者是接受有问题的签名这两者看着第二者是更容易的一个但是注意接受没有签名的JWT也是要严格验证的事实上这是一个实打实的配置错误或者说是漏洞它并不是刻意的设置允许JWT不去验证签名而是通过修改它的alg来实现使用空签名我们将alg的算法设置为none在实际的验证中验证时发现算法时none这代表使用空算法也就是接受签名为空的JWT而不像前者压根不验证签名将alg算法设置为空的目的往往是在已经确定身份的场景下不去验证确定的用户而这才是这个Lab实际的缺陷流程同前文打开环境登录账号打开拦截访问/admin在JWT设置面板将sub设置为administrator注意这里一定要先设置用户仍后点击攻击菜单选择none标志攻击也就是菜单中的第二个在弹出的选项中点击确定即可你会发现在JWT的第二个 . 后面的内容全面消失了因为算法选择了空已经不会产生签名了但是 . 还是要保留的不能破坏JWT的结构随后继续就可以发现请求成功了按照前文的方式删掉用户即可第二类服务端要验签但验证依据被攻击者控制Lab: JWT authentication bypass via weak signing key这是一个被弱密钥绕过的JWT认证往往是采用对称加密时出现的情况具体是可以通过提取整个JWT原文由于JWT的签名机制允许我们可以在本地使用暴力工具测试出密钥如果确定目标或者目标疑似使用弱签名密钥的情况下可以在一段时间内得到结果在得到密钥后我们可以直接构建新的JWT认证。首先同上文打开环境登录拦截请求/admin这时候你可以观察这个JWT面板中的头部可以发现它使用了HS256也就是使用对称加密在这个练习环境中由于提示你已经知道了这是一个弱密钥但是如果在其他环境中看到HS256作为alg就可以引起你的警觉可以尝试暴力破解密钥我们可以使用HashCat来完成这个过程首先在上一步的JWT面板完全复制JWTbase64的原文数据到本地为jwt.txt我们可以使用使用一个字典我们可以使用官方提示中给到的字典试着把密钥跑出来由于这个过程就是将载荷和头部的哈希摘要做加密仍后和签名对比完全取决于机器性能且没有限制这也是我们尝试的原因之一可以看到结果会很快被破解它大概长这样在原JWT数据的:后方就是跑出来的弱密钥我们可以使用这个密钥构建自己的签名并且可以通过服务器的验证我们首先将这个签名转换为Base64在Burp的Decoder中将Encode as 切换为Base64就可以拿到结果在打开JWTEditor面板点击New Symmetric Key点击Generate生成一个标准的JSON结构接着使用编码后的Base64编码替换到k的值这样就可以生成一个标准的对称密钥5.在JWT面板中将sub改为administrator后点击签名选择我们刚才生成的签名随后放开请求就会发现我们已经成功被认证了。Lab: JWT authentication bypass via jwk header injection这是 JWT 漏洞里非常经典的一个高危缺陷jwk (JSON Web Key) 头部注入漏洞。简单来说这个漏洞就是服务器会轻信JWT中的jwk字段这是一个头部字段其中会包含一个完整的密钥服务器会使用这个字段的RSA密钥来验证签名所以我们要做的就是修改JWT的载荷部分将sub字段改为administrator后使用Burp的攻击菜单中的嵌入JWK密钥将我们自己的密钥结合当前的JWT头部和载荷生成对应的签名发送给目标服务器而目标服务器会利用JWK中我们自己的密钥验证签名这样就可以完成绕过。同上述过程我们拦截一个访问/admin的流量这时候我们通过JWTEditor生成一个标准的RSA密钥在面板中点击New RSA Key在其中点击Generate生成一份标准的RSA密钥即可。在JWT面板中将sub改为administrator之后点击攻击在菜单中选择Embedded JWK嵌入JWK之后选择我们之前生成的RSA密钥即可注意嵌入JWK之前需要修改sub到我们想要的权限。放行流量即可完成绕过。Lab: JWT authentication bypass via jku header injection这个使用JKU来绕过是和上述类似的角度只是JWK是携带密钥而JKU是让目标验证时通过是一个URL中的密钥的进行验证也就是说这个绕过原理也是认证端轻信与来自前端的字段。使用JWTEditor面板建一个RSA密钥使用之前的也是可以的右击这个新建的密钥Copy Public Key as JWK复制公钥为 JWK 格式在Lab环境提供的一个Exploit Server环境中配置一个这样的环境主要是修改Body中的内容具体为{ keys: [ {这里粘贴你刚才复制的 JWK 公钥数据} ] }要记录这里使用的URL和公钥数据中的kid字段3. 登录Lab环境拦截一个请求admin的流量并且在JWT面板修改头部加入JKU字段为之前配置页面的那个URLkid值为公钥中的值之后在点击下方的签名Sign选择我们复制公钥的那个RSA密钥4. 放行流量就可以实现绕过了Lab: JWT authentication bypass via kid header path traversal我们已经通过JKU借助外部的URL来完成密钥的替换实现绕过但是很多情况下内部的系统是不能在外网下载拷贝公钥验证签名的这时候我们可以借助kid参数进行目录遍历在正常的业务逻辑中kid (Key ID) 头部通常被后端用来在一个数据库或文件系统中查找对应的密钥。比如 “kid”: “key-01”后端可能会去读取 keys/key-01.pem 这个文件来验证签名。但是如果后端代码没有对 kid 的值进行严格的过滤比如没有防御 …/我们就可以利用目录遍历Path Traversal让服务器去读取系统中的任意文件作为密钥。核心攻击思路既然我们不知道正确的密钥文件内容那我们能不能让服务器去读取一个“我们知道内容的文件”在 Linux 系统中有一个永远为空的黑洞文件/dev/null。如果我们把 kid 设置为 …/…/…/…/…/…/…/dev/null服务器就会把这个空文件读取出来作为对称加密HS256的 Secret。此时Secret 就变成了一个空值Null Byte我们只需要在本地用一个空值去签名就能完美骗过服务器。在JWTEditor面板建立一个‘空’密钥New Symmetric Key新建对称密钥点击Generate按钮生成一个标准的JSON结构修改好这个结构将其中的k字段改为AA 这是十六进制 00 的 Base64 编码这是为了兼容性通常要求传入一个 Base64 编码的空字节Null Byte。拦截一个/admin的请求流量在JWT编辑面板中将kid值改为…/…/…/…/…/…/…/dev/null这是一个空文件不会有任何内容之后在修改sub为administrator点击Sign签名选择那个我们构建的空密钥的对称加密放行流量即可实现绕过。第三类算法混淆如何把公钥体系变成你的签名密钥Lab: JWT authentication bypass via algorithm confusion这是一种没有严格限制alg字段引发的漏洞问题更多细节可以看alg的安全问题当然直接原因仍然可以归纳为服务器轻信了前端的字段没有严格验证的问题具体是我们可以修改alg的字段将RS256改为HS256仍后利用目标服务器的RSA算法的公钥这往往是公开的将它的公钥作为我们的对称加密的密钥加密我们当前JWT的载荷和头部作为签名发送给目标由于RSA的验证签名就是使用公钥也就是后端验证的key是RSA的公钥但是由于alg已经被我修改为HS256了服务端就会使用对称加密来验证我们的签名而这个密钥就是这个key从而实现绕过。我们先访问Lab环境下的/jwts.html这是目标环境的公钥地址我们可以从中得到RSA的公钥信息复制其中keys中的内容使用一个转换器来转为pem格式这里可以通过Burp实现也可以通过在线工具https://8gwifi.org/jwkconvertfunctions.jsp我们复制转换完的Pem格式的公钥在Burp的Decoder中将pem格式的公钥转换为base64格式使用这个转换为的base格式的公钥在JWTEditor面板中的New Symmetric Key建立对称密钥点击Generator按钮生成一个标准的JSON格式替换其中k字段的内容为base64的公钥拦截一个/admin的请求流量在JWT编辑面板中修改头部的alg为HS256修改载荷中的sub为administrator点击Sign签名选择我们刚才生成的那个对称签名选择不修改头部信息放行流量实现绕过防御启示作为安全边界的守门员在实现 JWT 认证时必须遵循“零信任”原则强制算法白名单永远不要依赖 JWT 头部声明的 alg 字段来决定使用什么算法验证服务端必须在代码中硬编码强制规定接受的算法如仅接受 RS256。拒绝不受信任的输入慎用或直接禁用 jwk、jku 头部。如果必须使用 kid 指定密钥绝对不能将其直接拼接到文件路径或 SQL 语句中必须使用严格的映射白名单。密钥管理是生命线对于对称加密HS256务必使用长且高熵的随机字符串作为 Secret彻底杜绝 Hashcat 的离线字典爆破。总结经过上述三个大类、七个真实的 Lab 环境实战我们可以清晰地得出一个关于 JWT 安全的残酷真相JWT 的脆弱性往往不在于底层的密码学数学模型而在于开发者“轻信”的业务实现逻辑。回顾我们演示的攻击路径JWT 的绕过本质上都抓住了服务端配置的以下几个致命弱点“不作为”的验签服务端压根不验证签名或者盲目接受了 algnone 的空签名指令。“引狼入室”的信任域服务端在验签时错误地信任了由客户端攻击者提供的验证依据。无论是直接嵌入的 jwk 公钥、外部指定的 jku 链接还是通过 kid 进行的目录遍历读取空文件本质上都是攻击者自己给自己颁发了“验证证书”。“移花接木”的算法混淆服务端未能严格绑定加密算法与密钥类型的对应关系导致非对称加密的公钥被降级滥用为对称加密的密码。希望这篇JWT测试指南不仅能帮你掌握渗透测试中的 JWT 绕过技巧更能让你在日常的系统架构与代码审计中敏锐地嗅出那些隐藏在认证边界上的逻辑暗雷