WebGoat JWT漏洞实战:逻辑越权与签名绕过深度解析

📅 2026/7/3 7:44:22
WebGoat JWT漏洞实战:逻辑越权与签名绕过深度解析
1. 项目概述从靶场实战到漏洞原理的深度拆解如果你正在学习Web安全尤其是认证与授权相关的漏洞那么OWASP WebGoat这个“故意不安全的”Web应用靶场绝对是你绕不开的实战演练场。最近我花了些时间专门研究了WebGoat中关于JWTJSON Web Token的关卡特别是被大家讨论得比较多的第六关和第十一关。这两个关卡一个侧重“逻辑越权”一个侧重“签名绕过”看似独立实则共同构成了对JWT安全机制核心风险的绝佳诠释。网上虽然有一些通关步骤但往往语焉不详只告诉你“点这里改那里”至于背后的原理、为什么这么做能成功、在实际渗透测试或代码审计中如何发现并利用这类问题却很少深入展开。这篇文章我就以一个安全研究员的视角带你从头到尾“盘”一遍这两个关卡。我们的目标不仅仅是“过关”而是通过靶场这个显微镜彻底理解JWT在设计和实现中可能出现的逻辑缺陷与配置错误。我会详细拆解每一步操作背后的HTTP请求、JWT结构变化并解释其对应的安全原理。无论你是刚接触JWT安全的新手还是想巩固相关知识的老手相信这篇结合了实战操作与原理剖析的指南都能让你有所收获。你会发现通关的关键不在于记住某个神奇的字符串而在于理解服务器验证JWT的“逻辑”在哪里出现了断层。2. JWT安全基础与WebGoat环境准备在深入关卡之前我们必须统一“语言”。JWT本质上是一个紧凑的、自包含的字符串用于在各方之间安全地传输信息。它由三部分组成以点号分隔Header.Payload.Signature。Header通常包含令牌类型“JWT”和所使用的签名算法如HMAC SHA256HS256或RSARS256。它会被Base64Url编码。Payload包含声明Claims即关于实体通常是用户和其他数据的语句。常见的声明有sub用户ID、username、role、exp过期时间等。它同样会被Base64Url编码。Signature用于验证消息在传输过程中没有被篡改。对于使用HMAC算法的令牌签名是这样创建的HMACSHA256(base64UrlEncode(header) “.” base64UrlEncode(payload), secret)。服务器在收到JWT后会重新计算签名并与令牌中的签名部分进行比对。如果一致且令牌未过期则信任Payload中的内容。WebGoat环境搭建要点 我推荐使用Docker运行最新版WebGoat这能避免很多本地环境依赖问题。使用命令docker run -p 8080:8080 -p 9090:9090 webgoat/webgoat即可快速启动。访问http://localhost:8080/WebGoat即可开始。创建一个练习课程找到“JWT Tokens”相关章节第六关JWT Token Tampering和第十一关JWT signing key就是我们今天的主角。注意在实战开始前请务必安装好Burp Suite或OWASP ZAP这类拦截代理工具并配置好浏览器代理。我们后续所有的请求分析、令牌篡改和重放都将依赖它。这是Web安全测试的“标配”也是理解流量细节不可或缺的一环。3. 第六关深度解析JWT令牌篡改与逻辑越权这一关的核心目标是在不修改JWT签名的情况下通过篡改Payload负载来提升权限访问普通用户本无法访问的功能。这直指一个关键问题服务器是否真的在验证签名后完全信任并使用了Payload中的数据3.1 关卡场景与初步侦察进入第六关通常会呈现一个简单的用户界面可能是一个查看个人资料的页面或者一个带有用户身份标识的功能点。你的第一个任务是作为一个普通用户比如webgoat登录并抓取这个请求。操作步骤实录使用代理工具拦截浏览器发送的HTTP请求。找到包含Authorization请求头的请求其值通常为Bearer 你的JWT令牌。将这个完整的JWT令牌复制出来。使用像 jwt.io 这样的在线解码器或者Burp Suite自带的Decoder模块将令牌进行Base64Url解码查看其Header和Payload。首次解码分析 你可能会看到类似这样的Payload{ sub: webgoat, username: webgoat, role: USER, exp: 1743456789 }这表明当前令牌声明用户是webgoat角色是USER。关卡的任务很可能是让你以admin或类似高权限身份执行某个操作。3.2 漏洞原理脆弱的验证逻辑为什么我们可以篡改Payload理想的安全流程是服务器用密钥验证JWT签名。验证通过后服务器从自己的数据库或会话存储中根据令牌标识如sub里的用户ID重新查询该用户的详细信息包括角色。但存在漏洞的实现逻辑可能是服务器用密钥验证JWT签名。验证通过后服务器直接相信了Payload中的role声明并基于这个值进行授权判断。这就是“逻辑越权”。服务器在验证签名的逻辑链上出现了断层它验证了“这个令牌是合法的由我签发”但没有进一步校验“令牌中的角色信息是否与签发时一致或是否与当前用户的实际状态一致”。攻击者一旦获得一个合法签名的令牌就可以任意修改Payload中的role字段例如改为ADMIN由于签名部分没有变我们没动而服务器只验证签名不校验Payload内容与数据库的同步性就会错误地授予高权限。3.3 实操篡改与利用理解了原理操作就清晰了。我们的目标是生成一个签名有效、但Payload中角色被修改的JWT。关键点我们不能直接修改Payload后重新计算签名因为我们没有服务器的密钥。我们必须利用服务器“只验签名不二次校验数据”的逻辑缺陷。实操步骤复制原令牌将拦截到的原始JWT令牌完整复制。解码与修改在jwt.io上粘贴令牌。在Payload部分将role: USER修改为role: ADMIN具体角色名需根据关卡提示可能是ADMINISTRATOR等。注意在jwt.io上修改后左侧的编码输出会实时变化但签名会显示“Invalid Signature”这是正常的因为我们没有密钥。构造新请求关键的一步来了。我们不需要一个“有效”签名我们需要的是原装签名。正确做法是分别对原令牌的Header和修改后的Payload进行Base64Url编码然后与原始的Signature部分拼接。原始令牌Header.Payload.Signature新令牌NewHeader(可能不变).NewPayload(已修改).OriginalSignature由于Header中的算法等信息通常不变所以NewHeader就是原Header。我们只需要重新编码修改后的Payload。手动拼接使用Burp Suite的Decoder模块可以方便地完成 a. 将原Header部分解码确认无误后再次编码通常不变直接复制原Header字符串即可。 b. 将修改后的PayloadJSON字符串进行Base64Url编码在Decoder中选择“Encode as Base64”同时注意勾选“URL safe”。 c. 复制原始的Signature部分。 d. 用点号.将这三部分拼接起来[encoded_header].[encoded_modified_payload].[original_signature]。重放请求在代理工具中找到之前拦截的请求将Authorization头的值替换为我们新拼接的JWT令牌然后发送请求。如果关卡设计正确服务器在验证签名通过后直接读取了新Payload中的role: ADMIN并据此授予了权限你就能看到通关成功的提示。实操心得这一步最容易出错的地方在于Base64Url编码。很多在线工具或库的默认Base64编码会包含/和而JWT标准要求使用URL安全的Base64编码用-和_替换和/并省略填充符。Burp Decoder的“URL safe”选项或Python的base64.urlsafe_b64encode()函数可以正确处理。如果拼接后令牌无法验证首先检查编码是否正确。4. 第十一关深度解析JWT签名密钥破解与算法混淆第十一关的难度和意义更上一层楼。它不再假设服务器存在逻辑缺陷而是挑战JWT安全机制的另一个基石签名算法和密钥的安全性。这一关通常要求你伪造一个管理员令牌但服务器会严格验证签名。你需要找到方法“破解”或“绕过”签名验证。4.1 场景分析与信息收集进入第十一关你可能会获得一个功能比如“刷新令牌”或“获取密钥”。首先要仔细阅读关卡描述和页面上的所有提示。有时密钥Secret可能以注释形式存在于前端JS代码中或者通过一个弱权限的API端点泄露。侦察步骤遍历所有请求使用代理工具记录下从页面加载到每个交互动作的所有HTTP请求。检查响应内容关注API响应、JavaScript文件、HTML注释寻找任何包含“secret”、“key”、“password”、“signing”等关键词的字符串。分析令牌同样获取一个当前用户的合法JWT令牌在jwt.io上解码。重点观察Header中的alg字段。常见的有HS256使用对称密钥一个Secret进行签名和验证。RS256使用非对称密钥对私钥签名公钥验证。4.2 漏洞原理算法混淆攻击与弱密钥本关的核心漏洞通常指向以下两种之一或结合1. 算法混淆攻击 如果服务器代码存在缺陷它可能信任客户端提交的JWT Header中指定的算法alg。攻击者可以将alg从RS256非对称改为HS256对称。服务器配置不当可能会这样处理当看到alg: HS256时它使用自己的RSA公钥作为HMAC的密钥去验证签名。由于公钥通常是公开或可获取的攻击者就可以用这个公钥作为HMAC的密钥来签署一个篡改后的令牌。服务器用同样的公钥当作HMAC密钥去验证结果就会匹配。2. 弱密钥或密钥泄露 对于HS256算法如果签名密钥Secret强度太弱如secret、password、123456或者密钥通过其他途径如目录遍历、配置文件泄露、GitHub信息泄露被获取那么攻击者就可以用这个密钥为任意Payload生成合法的签名。4.3 实操破解两种路径的实战假设通过侦察你发现了一个获取密钥的端点GET /WebGoat/JWT/secret返回了一个密钥victory-secret。路径A已知密钥直接伪造在jwt.io右侧的“VERIFY SIGNATURE”区域填入获取到的密钥victory-secret。在Payload中将username修改为adminrole修改为ADMIN。左侧的Encoded区域会自动生成一个带有有效签名的新JWT令牌。用这个新令牌替换请求中的Authorization头发送即可通关。路径B算法混淆攻击更常见如果找不到密钥但发现服务器使用了RSA且存在算法混淆漏洞则按以下步骤操作获取服务器的RSA公钥。它可能存在于/jwks.json、/.well-known/jwks.json端点或以其他方式泄露。使用获取到的公钥文件如public.pem。使用工具如jwt_tool或编写脚本将令牌的Header中的alg改为HS256并用RSA公钥作为HMAC的密钥对篡改后的Payloadrole: ADMIN进行签名。# 使用 jwt_tool 示例 python jwt_tool.py 原始JWT -X k -pk public.pem工具会生成一个篡改后的、算法为HS256、使用公钥签名的令牌。用这个新令牌重放请求。注意事项算法混淆攻击成功的前提是服务器端JWT库配置存在缺陷错误地允许了算法覆盖。现代安全的库如java-jwt、pyjwt通常要求显式指定预期的算法从而防御此类攻击。但在遗留系统或配置不当的应用中这仍是一个严重的威胁。5. 核心工具链与手动技巧详解工欲善其事必先利其器。除了Burp Suite以下工具和手动技巧能极大提升你测试JWT漏洞的效率。5.1 必备工具推荐Burp Suite扩展JWT Editor这是Burp Suite的必备插件。它可以直接在Proxy历史记录或Repeater中解码、编辑、重新签名JWT。它支持手动输入密钥、从PEM文件加载密钥、甚至集成弱密钥字典进行暴力破解。图形化操作非常方便。命令行神器jwt_tool一个功能强大的Python工具。除了基本的解码、篡改它还能测试算法混淆-X a。使用字典暴力破解弱密钥-C -d wordlist.txt。扫描多个攻击向量-t。它是我在自动化测试和深度评估时的首选。在线平台jwt.io最适合快速解码、查看结构、进行简单的修改和签名验证当你知道密钥时。它的交互式界面非常适合学习和快速验证。5.2 手动分析与编码技巧尽管有工具理解手动过程能加深理解手动Base64Url编码/解码编码先确保你的JSON是紧凑格式无多余空格换行然后进行标准的Base64编码最后将结果中的替换为-/替换为_并去掉尾部的所有。import base64 import json payload {sub: 123, role: admin} payload_str json.dumps(payload, separators(,, :)) # 紧凑格式 encoded base64.urlsafe_b64encode(payload_str.encode()).decode().rstrip() print(encoded)解码将-和_换回和/并补上直到字符串长度是4的倍数再进行Base64解码。import base64 import json encoded_payload eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ # 补全等号 padding 4 - len(encoded_payload) % 4 if padding ! 4: encoded_payload * padding decoded_bytes base64.urlsafe_b64decode(encoded_payload) print(json.loads(decoded_bytes))签名验证逻辑模拟 理解服务器如何验证你就能更好地构造攻击。对于HS256import hmac import hashlib import base64 def sign_hmac_sha256(header_b64, payload_b64, secret): message f{header_b64}.{payload_b64}.encode() signature hmac.new(secret.encode(), message, hashlib.sha256).digest() return base64.urlsafe_b64encode(signature).decode().rstrip() # 示例验证签名是否匹配 original_token header.payload.signature header_b64, payload_b64, original_sig original_token.split(.) computed_sig sign_hmac_sha256(header_b64, payload_b64, weak_secret) if computed_sig original_sig: print(密钥匹配)6. 从靶场到实战漏洞挖掘与防御建议通过WebGoat的练习我们掌握了攻击手法。那么在真实的渗透测试或代码审计中如何发现和防御这些漏洞呢6.1 实战漏洞挖掘思路信息收集检查所有API请求的Authorization头寻找JWT。检查前端代码JS中是否硬编码了密钥或令牌。尝试访问/jwks.json/.well-known/openid-configuration等标准端点。使用目录扫描工具寻找可能泄露配置或密钥的文件如.git.envconfig.properties。测试逻辑越权第六关类获取一个低权限用户的JWT。修改Payload中的ID、用户名、角色、邮箱等字段保持签名不变重放请求。观察响应是403 Forbidden说明有二次校验还是200 OK并返回了高权限数据存在漏洞。测试签名安全性第十一关类弱密钥爆破使用jwt_tool或hashcat配合常见弱密钥字典如secretpassword123456 应用名称等进行爆破。算法混淆测试将alg改为none如果服务器允许无签名或从RS256改为HS256尝试用获取到的公钥作为密钥进行签名。密钥泄露检查审查错误信息有时密钥错误会返回不同的提示。尝试使用已知的、来自其他类似应用或开源项目的默认密钥。6.2 针对开发者的安全防御指南作为开发者如何避免引入这些漏洞永远不要信任客户端声明这是黄金法则。JWT的Payload用于传输信息但关键的业务权限决策如角色、访问控制列表必须在服务器端基于从可信数据源如数据库查询到的实时信息进行二次验证。JWT中的role字段可以作为一个初始提示或缓存但不能是唯一依据。使用强算法并显式声明在验证JWT时永远显式指定预期的算法。例如在java-jwt中使用JWT.require(Algorithm.RSA256(publicKey)).build()而不是一个接受任何算法的通用验证器。这从根本上杜绝了算法混淆攻击。保护你的密钥对于HS256使用足够长且随机的密钥如通过安全的随机数生成器生成并将其作为机密信息存储在环境变量或安全的密钥管理服务中绝不能写在代码或配置文件中提交到代码仓库。对于RS256妥善保管私钥确保公钥的获取端点不被滥用。设置合理的令牌有效期使用短期的访问令牌Access Token 例如15分钟和配合使用刷新令牌Refresh Token机制减少令牌泄露后的风险窗口。实施令牌吊销机制对于重要的操作或已知的令牌泄露需要有机制能将特定令牌加入黑名单或使其失效。进行安全代码审计在代码审查中重点关注JWT验证逻辑。检查是否有从JWT直接读取权限信息并用于授权判断的代码。7. 常见问题排查与进阶思考在实际操作WebGoat或进行真实测试时你可能会遇到一些棘手的情况。问题1我修改了Payload并拼接了原签名但服务器返回“Invalid Token”或“Signature Invalid”。可能原因A编码错误。这是最常见的原因。确保你的Base64Url编码是正确的特别是/-和//_的替换以及的去除。使用Burp Decoder或可靠的库来操作。可能原因B服务器校验了签名以外的字段。有些服务器会校验令牌的iss签发者、aud受众或exp过期时间。请确保你修改Payload时没有破坏这些字段的格式或值。检查原始Payload确保所有必需的字段都存在且有效。可能原因C关卡设计或环境问题。确保你使用的是最新版WebGoat有些旧版本的关卡可能存在Bug。也可以尝试清除浏览器缓存和Cookie重新开始课程。问题2我找到了一个疑似密钥的字符串但在jwt.io上验证签名时仍然显示无效。可能原因A密钥格式不对。对于HS256密钥就是一个字符串。但对于RS256你需要的是PEM格式的私钥或公钥。确认你获取的是哪种算法的密钥。可能原因B密钥不正确。你找到的字符串可能不是签名密钥而是其他用途的密码。尝试用jwt_tool进行字典爆破使用更全面的弱密钥列表。可能原因C令牌已过期。如果令牌的exp字段时间已过服务器会直接拒绝无论签名是否有效。检查并修改exp为一个未来的时间戳需重新签名。进阶思考除了HS256/RS256和none还有其他攻击面吗是的。例如针对kid密钥ID头的攻击。如果服务器使用kid参数从数据库或文件中动态选择验证密钥攻击者可能通过kid进行路径遍历如kid../../../../etc/passwd或SQL注入让服务器使用一个攻击者已知的密钥文件来验证签名。这要求测试者对JWT头部有更全面的审查。WebGoat的JWT关卡是一个绝佳的起点它抽象并演示了现实世界中最常见的两类JWT安全问题信任边界混淆第六关和密码学机制滥用第十一关。通关之后真正的学习在于将这种攻击者思维带入你对任何API、任何登录系统的审视中。下次当你看到Authorization: Bearer头时不妨想想如果我来测试我会从哪入手这份好奇心和拆解欲正是安全研究员最宝贵的特质。