目录

  1. 1. jwt
  2. 2. 多语言的 jwt 实现
    1. 2.1. Python
    2. 2.2. Java SpringBoot
  3. 3. 实战
    1. 3.1. ctfshow web345
    2. 3.2. ctfshow web346
    3. 3.3. ctfshow web347

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

jwt

2023/6/4 Web jwt
  |     |   总文章阅读量:

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

image-20230604231713983

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

image-20230604231928369

发现alg是None算法,无加密

于是这里直接修改sub的值为admin(因为一开始是user,很明显要修改为admin)

然后选一下上面的加密算法(好像都可以)

image-20230604232140896

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

image-20230604234306500


ctfshow web346

None算法绕过签名

和上题一样获取cookie进行解码

image-20230605110806698

发现是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

image-20230605112022265


ctfshow web347