JWT安全攻防实战:从算法混淆到逻辑漏洞的深度解析

JWT安全攻防实战:从算法混淆到逻辑漏洞的深度解析
1. 从靶场到实战为什么JWT安全是Web安全工程师的必修课如果你正在学习Web安全或者已经是一名渗透测试工程师那么“JWT”这个词你一定不陌生。它频繁出现在现代Web应用的登录认证流程里也同样是CTF比赛和渗透测试中的“常客”。最近在CTFshow的Web入门系列题目特别是345到350关以及BurpSuite官方靶场中围绕JWT的攻击场景被反复提及这绝不是偶然。这恰恰说明JWT的安全问题已经从理论风险变成了一个非常普遍且必须掌握的实战技能点。简单来说JWTJSON Web Token是一种开放标准用于在网络应用环境间安全地传递声明。它通常由三部分组成头部Header、载荷Payload和签名Signature中间用点号分隔形如xxxxx.yyyyy.zzzzz。设计初衷是为了实现无状态的、可扩展的认证。然而正是其“自包含”的特性所有信息都在Token里和灵活的配置选项为攻击者留下了诸多可乘之机。理解JWT不仅要会用更要懂它哪里会“破”。通过分析CTFshow和BurpSuite靶场的具体题目我们可以将这些攻击手法系统化、场景化从而在面对真实世界的黑盒应用时能快速形成有效的测试思路。这篇文章我将以一个“踩过坑”的同行身份带你深入拆解JWT的核心安全机制并复盘在CTFshow Web 345-350以及BurpSuite靶场中遇到的那些典型攻击场景。我不会只给你Payload我会重点讲清楚每一个攻击背后的“为什么”——为什么这种算法可以被攻击为什么这个字段可以被篡改理解了原理你才能举一反三。无论你是正在刷题打CTF的新手还是需要对企业应用进行安全评估的工程师相信这些从靶场中提炼出的实战经验都能给你带来直接的帮助。2. JWT安全基础结构、流程与核心风险点在开始“攻击”之前我们必须先成为“建造者”透彻理解JWT是如何工作的以及它的安全设计依赖于哪些假设。只有知道了堡垒是如何砌成的才能找到最脆弱的那块砖。2.1 JWT的三段式结构与编解码一个典型的JWT看起来是这样的eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c它由三个用点分隔的Base64Url编码字符串组成Header头部: 声明令牌的类型和使用的签名算法。Payload载荷: 包含所谓的“声明”Claims即需要传递的信息如用户ID、角色、过期时间等。Signature签名: 对前两部分编码后的字符串进行签名用于验证消息在传递过程中未被篡改。编解码实操 你完全可以在终端里手动拆解一个JWT。头部和载荷只是Base64Url编码并非加密。这意味着任何人都可以解码并查看其内容。# 示例解码JWT的Payload部分 echo eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ | base64 -d # 输出{sub:1234567890,name:John Doe,iat:1516239022}注意这里使用的是base64 -d但JWT使用的是Base64Url编码它用-和_替代了标准Base64中的和/并且去掉填充符。在线解码工具或编程语言库如Python的base64.urlsafe_b64decode会正确处理这些差异。手动解码时如果遇到错误很可能是填充符或特殊字符的问题。核心风险点1信息暴露。由于载荷是明文编码绝对不要在JWT的Payload中存放任何敏感信息如密码、密钥、身份证号等。它只是被编码不是被加密。2.2 签名与验证安全的核心支柱签名是JWT安全性的基石。它的生成过程通常如下获取编码后的Header和Payloadencoded_header . encoded_payload。使用指定的算法如HS256和一个只有服务器知道的密钥Secret对上述字符串进行签名。将签名附加到Token末尾。验证时服务器用同样的密钥和算法对收到的Header和Payload重新计算签名。将计算出的签名与Token自带的签名进行比对。如果一致说明Token未被篡改同时检查Payload中的声明如exp过期时间是否有效。核心风险点2算法依赖与密钥管理。整个安全模型建立在两个前提下算法是安全的以及密钥是保密的。如果攻击者能够破解算法、窃取密钥或者诱使服务器使用不安全的算法整个认证体系就会崩塌。2.3 常见声明字段与业务逻辑风险Payload中的声明字段承载了业务逻辑这也是攻击的重要入口iss(Issuer)签发者。用于区分不同来源的Token。sub(Subject)主题通常是用户ID。aud(Audience)受众指定Token接收方。exp(Expiration Time)过期时间戳。服务器必须校验。nbf(Not Before)生效时间戳。iat(Issued At)签发时间。jti(JWT ID)令牌唯一标识用于防止重放。自定义字段如admin、role、username等直接用于权限判断。核心风险点3声明篡改与逻辑绕过。如果服务器在验证签名后过于信任Payload中的内容或者校验逻辑存在缺陷攻击者就可能通过篡改Payload并需要解决签名问题来提升权限。例如将admin: false改为admin: true。3. 攻击场景深度剖析从算法攻击到逻辑漏洞理解了基础我们就可以进入正题。下面我将结合CTFshow和BurpSuite靶场的典型题目将JWT攻击手法归类为几个核心场景。每个场景我都会解释原理、提供攻击步骤并分享我在实操中的心得和踩过的坑。3.1 场景一算法混淆攻击这是最常见也是最经典的JWT攻击手法在CTFshow Web入门中多次出现。攻击原理 JWT头部alg字段指明了签名算法。服务器在验证Token时通常会根据这个字段的值来选择对应的验证逻辑。算法混淆攻击的核心在于诱使服务器使用不同于它预期的算法来验证签名。主要有两种子类型alg: none攻击早期一些JWT库的实现存在缺陷当alg设置为none时会跳过签名验证。攻击者可以任意修改Payload然后将alg改为none并去掉签名部分或者签名留空。非对称算法与对称算法混淆这是更常见的情况。非对称算法如RS256使用公私钥对服务器用私钥签名用公钥验证。对称算法如HS256使用同一个密钥进行签名和验证。如果服务器代码存在缺陷比如它期望使用RS256但实际验证时却根据Token头中的alg值动态选择算法那么攻击者就可以将alg改为HS256然后用服务器的公钥作为HS256的密钥去伪造一个签名。因为服务器在验证HS256签名时也会使用公钥作为密钥来计算比对如果代码逻辑是“用公钥去验证”那么攻击者用公钥签名的Token就能通过验证。BurpSuite靶场实战复盘 在BurpSuite的JWT攻击实验里有一关典型地演示了从RS256到HS256的算法混淆。我的攻击步骤如下抓取与分析首先用正常账户登录BurpSuite拦截到包含JWT的请求。解码后发现头部为{alg: RS256, typ: JWT}Payload中包含sub: carlos。获取公钥这是关键一步。服务器的公钥有时会暴露在/jwks.json、/.well-known/jwks.json等端点或者直接硬编码在网页的JavaScript里。在这个靶场中通过访问相关接口很容易就获取到了PEM格式的公钥。修改与重签 a. 将JWT头部改为{alg: HS256, typ: JWT}。 b. 修改Payload将sub: carlos改为sub: administrator。 c. 使用获取到的公钥文件作为密钥用HS256算法对新的头部和载荷进行签名。这里我使用了jwt_tool这个命令行工具非常方便python3 jwt_tool.py 原始JWT -X k -pk public.pem工具会自动尝试各种攻击包括算法混淆并生成伪造的Token。替换与验证将请求中的旧Token替换为新生成的Token发送请求。成功以管理员身份访问了受限资源。实操心得工具选择jwt_tool和jwt.io网站是快速测试的利器。但jwt.io的“密钥”输入框有时对非对称密钥的处理不直观命令行工具更可控。密钥格式注意公钥的格式。有时拿到的是Base64编码的DER格式需要转换成PEM格式才能被工具使用。openssl命令是处理密钥格式转换的好帮手。并非总是有效这种攻击成功的前提是服务器存在漏洞即它使用可被预测或获取的公钥来进行HS256验证。现代安全的JWT库会强制指定预期的算法列表避免这种动态切换。3.2 场景二弱密钥爆破当JWT使用对称算法如HS256时其安全性完全依赖于密钥的强度。如果密钥太弱就可能被暴力破解。攻击原理 HS256签名本质上是一个带密钥的哈希HMAC SHA256。如果攻击者拿到了一个有效的JWT并且知道它是用HS256签名的那么他就可以尝试用常见的、弱密码字典去碰撞签名。如果碰撞成功就意味着找到了密钥从而可以伪造任意Token。CTFshow Web 345-350 实战举例 在这个系列的题目中有一关就是典型的弱密钥。题目可能提示密钥很简单或者通过其他信息暗示密钥的范围比如与网站名、题目名相关。攻击步骤获取有效Token通过正常登录或注册获取一个合法的JWT。使用工具爆破使用jwt_tool或hashcat进行爆破。# 使用 jwt_tool 爆破 python3 jwt_tool.py JWT_TOKEN -C -d /usr/share/wordlists/rockyou.txt # 使用 hashcat 模式 16500 hashcat -m 16500 JWT_TOKEN /usr/share/wordlists/rockyou.txt伪造高权限Token爆破出密钥比如secret后就可以用这个密钥签名一个修改了Payload如admin: true的新Token。注意事项字典质量爆破成功与否极度依赖字典。rockyou.txt是起点但针对CTF可能需要尝试数字、短单词、题目相关词汇等自定义字典。性能考量HMAC爆破在本地进行速度尚可但如果密钥强度足够在线爆破几乎不可能。这通常用于CTF或测试内部弱配置系统。信息收集有时密钥可能写在前端源码、注释、甚至是错误的响应信息里。在尝试爆破前务必先进行彻底的信息收集。3.3 场景三无效签名绕过与KID参数注入这类攻击利用了服务器端签名验证逻辑的不严谨。3.3.1 无效签名绕过有些应用在验证签名时如果验证失败可能会错误地回退到某种“降级”状态或者因为异常处理不当仍然信任解码后的Payload。攻击者可以尝试提供一个完全错误的签名或者删除签名部分观察应用行为是否发生变化。但这在现代框架中已较少见。3.3.2 KID参数注入kidKey ID是JWT头部的一个可选参数用于指示验证签名时应使用哪个密钥。它本意是在服务器轮换多个密钥时指定使用的密钥。问题在于如果服务器获取密钥的方式不安全就可能造成注入。攻击原理 假设服务器通过类似下面的伪代码来获取密钥key get_key_from_database(kid) # 根据kid从数据库查密钥 verify_signature(token, key)如果攻击者将kid参数设置为一个路径遍历或SQL注入的Payload就可能让服务器从一个意想不到的位置读取“密钥”甚至执行命令。路径遍历kid: ../../../../etc/passwd服务器可能将/etc/passwd文件内容当作密钥来验证签名。如果签名部分也是用该文件内容生成的验证就会通过。SQL注入kid: somekey UNION SELECT attacker_controlled_string -- 可能导致数据库返回一个攻击者可控的字符串作为密钥。实操要点在JWT头部添加或修改kid参数。构造一个能让服务器返回可控数据的kid值。用这个“可控数据”作为密钥去签名伪造的JWT。难点在于需要提前知道或猜出服务器处理kid的逻辑。这通常需要结合其他漏洞如LFI、SQLi的线索。3.4 场景四JWK/JKU头部注入这是更高级的攻击涉及JWT的密钥管理机制。JWK(JSON Web Key)直接在JWT头部嵌入一个公钥参数jwk声称用于验证此Token的密钥就是它自己。JKU(JSON Web Key Set URL)在JWT头部提供一个URL参数jku指向一个包含公钥集的JSON文件服务器应从这个地址获取公钥来验证签名。攻击原理 如果服务器盲目信任头部中的jwk或jku字段攻击者就可以jwk注入在Token头部插入自己生成的恶意jwk公钥然后用对应的私钥对Token进行签名。服务器验证时会使用Token内提供的公钥从而验证通过。jku注入将jku指向一个攻击者控制的服务器该服务器返回一个包含攻击者公钥的JWKS集合。服务器会从这个URL获取并信任其中的公钥。BurpSuite靶场案例 有一关需要利用jku注入。步骤比算法混淆更复杂生成一对RSA公私钥。在攻击者自己的服务器上或利用BurpSuite的Collaborator功能模拟一个外网服务托管一个JWKS文件内容包含上一步生成的公钥。伪造一个JWT头部包含alg: RS256和jku: http://attacker-server.com/keys.jsonPayload修改为高权限用户。用自己生成的私钥对这个JWT进行签名。服务器在验证时会去访问jku指向的URL获取公钥并用它来验证签名从而通过。踩坑记录URL可达性服务器必须能访问到你提供的jkuURL。在本地测试或某些隔离环境可能需要使用localhost、内网地址或像Burp Collaborator这样的外部交互工具来证明“服务器确实发起了请求”。JWKS格式jku指向的必须是一个符合JWKS格式的JSON文件结构有严格规定写错了服务器会解析失败。一个典型的JWKS文件如下{ keys: [ { kty: RSA, e: AQAB, kid: attack-key-1, n: modulus_in_base64... } ] }缓存与频率真实环境中服务器可能会缓存JWKS文件或者对jku域名有白名单限制这增加了攻击难度。4. 实战流程与工具链系统化的JWT测试方法面对一个黑盒目标如何进行系统化的JWT安全测试以下是我总结的一套流程结合了手动测试和自动化工具。4.1 信息收集与Token分析定位Token使用BurpSuite代理流量关注Authorization: Bearer token头以及Cookie常命名为session,token,jwt、POST请求体或URL参数中的JWT。解码与观察将Token复制到jwt.io或使用jwt_tool解码仔细分析头部alg是什么是否有kid、jku、jwk等参数载荷有哪些声明自定义字段是什么如user,role,isAdmin。exp时间是否合理签名是否明显异常如很短、全是数字4.2 自动化扫描与初步探测使用jwt_tool进行快速安全评估python3 jwt_tool.py JWT_TOKEN运行此命令会进行一系列检查并给出一个报告提示可能的脆弱点如Token是否可被解码。是否接受alg: none。签名是否验证通过修改Payload看服务器是否拒绝。4.3 分场景手动验证根据自动化扫描的提示和信息收集的结果有针对性地进行测试测试none算法修改头部alg为none删除签名发送请求观察响应。测试算法混淆 a. 尝试将RS256改为HS256。 b. 寻找公钥常见位置/jwks.json,/.well-known/, 前端JS。 c. 用找到的公钥作为HS256的密钥伪造Token测试。测试弱密钥如果alg是HS256/HS384/HS512使用弱口令字典进行爆破。测试kid参数 a. 添加或修改kid参数。 b. 尝试路径遍历Payload../../../etc/passwd、../../../../windows/win.ini等。 c. 尝试SQL注入Payload OR 11等。 d. 用猜测的“密钥”重新签名测试。测试jku/jwk注入 a. 检查服务器是否接受这些参数在头部添加一个无效的看看反应。 b. 如果接受搭建恶意JWKS端点可用Python Flask快速搭建或使用Burp Collaborator。 c. 生成RSA密钥对构造包含恶意jku或jwk的Token用私钥签名。4.4 业务逻辑漏洞挖掘即使密码学层面是安全的业务逻辑也可能出错过期时间exp篡改尝试修改为一个未来的时间戳。如果服务器不校验或校验逻辑有误Token可能永久有效。重放攻击捕获一个高权限操作如添加管理员的请求重复发送多次观察是否都生效。服务器应通过jti或业务流水号防止重放。声明歧义JWT库在解析JSON时如果遇到重复的Key行为可能不同。有些库会取第一个有些取最后一个。可以尝试在Payload中插入两个相同的Key如user:admin和user:guest观察服务器以哪个为准。5. 防御指南开发与运维的安全实践作为安全工程师我们不仅要懂攻击更要能提出防御方案。以下是从这些攻击场景中反推出的关键防御措施。5.1 开发侧安全的JWT实现指定并固定算法在验证Token时不要依赖Token头中的alg字段。应在代码中显式指定期望的算法列表。例如如果你只用RS256就在验证函数里写死algorithmRS256这样即使攻击者改成HS256或none也会被拒绝。# 错误示例动态从header读取算法 # decoded jwt.decode(token, verifyTrue) # 依赖header里的alg # 正确示例显式指定算法 decoded jwt.decode(token, keypublic_key, algorithms[RS256]) # 只接受RS256彻底校验所有声明必须校验exp过期时间、nbf生效时间、iss签发者、aud受众等关键声明。不要只验证签名就相信所有内容。谨慎处理头部参数如果不需要kid、jku、jwk功能就在验证时忽略或拒绝包含这些参数的Token。如果需要使用kid确保其值在一个严格的白名单内并且获取密钥的过程安全如从安全的配置存储中读取而非拼接路径或SQL查询。绝对不要根据jku或jwk动态地从不可信的URL获取密钥。如果必须使用应对域名有严格的白名单限制并对获取的密钥进行强验证。使用强密钥并安全存储对称密钥HS256必须足够长且随机如32字节的随机数。非对称私钥必须妥善保管绝不出现在客户端或版本库中。公钥可以公开但要防止被用于算法混淆攻击见第1点。避免敏感信息存入PayloadJWT Payload是明文只存放必要的、非敏感的用户标识信息。5.2 运维与架构侧密钥轮换制定并执行密钥轮换策略。这样即使一个密钥泄露影响范围也有限。轮换时要注意新旧Token的平滑过渡。设置合理的Token有效期访问令牌Access Token有效期宜短如15-30分钟配合刷新令牌Refresh Token使用减少Token泄露带来的风险。实施令牌黑名单/白名单对于关键操作或需要立即吊销权限的情况如用户登出、管理员禁用账户可以考虑维护一个短期的令牌黑名单或者在服务端缓存一部分关键声明进行二次校验。这在一定程度上牺牲了无状态性但提升了安全性。全面的输入验证与输出编码将JWT视为用户输入。在解码后对Payload中的数据进行验证和清理防止后续的逻辑处理中出现注入等问题。依赖库安全使用成熟、活跃、经过安全审计的JWT库如Java的auth0/java-jwtPython的PyJWT并保持更新。避免使用不再维护或有已知漏洞的库。6. 工具、资源与后续学习路径核心工具BurpSuite JWT Editor插件渗透测试必备。插件提供了可视化的JWT编解码、修改、签名和攻击功能与Burp的Repeater、Intruder无缝集成极大提升测试效率。jwt_tool命令行下的瑞士军刀集成了编码、解码、扫描、多种攻击模式爆破、混淆、注入等是自动化测试和CTF解题利器。jwt.io在线调试器用于快速解码、验证和手动构造Token适合初步分析。hashcat当需要大规模、高性能的弱密钥爆破时hashcat的Mode 16500是针对JWT的利器。学习资源PortSwigger Web Security Academy (BurpSuite官方靶场)其中的JSON Web Token模块是绝佳的、由浅入深的实践环境。CTFshow平台Web入门及后续的题目提供了大量贴近实战的JWT漏洞场景是巩固知识的练兵场。OWASP JWT Cheat Sheet了解JWT安全最佳实践的权威指南。我的个人体会JWT安全的学习是一个“理解密码学基础 - 掌握协议机制 - 熟悉攻击模式 - 洞察逻辑漏洞”的递进过程。最开始可能只会用工具跑一下none攻击但当你真正理解了非对称加密和签名验证的原理才能看透算法混淆的本质。而业务逻辑漏洞的挖掘则需要你将JWT视为整个应用数据流的一部分而不仅仅是一个加密字符串。多动手在靶场里反复练习尝试用不同的方法解同一道题并思考“如果我是开发者怎么防住这种攻击”这种攻防思维的切换是提升安全能力的核心。最后记住一个原则永远不要信任客户端传来的任何数据JWT的签名只是验证了数据完整性其内容的有效性和业务合规性必须由服务端进行严格的、二次的校验。