JWT代表JSON Web Token,它是一种用于安全地在不同实体之间传递信息的开放标准(RFC 7519)。JWT通常用于身份验证和授权领域,以及在网络应用程序和服务之间传递声明(claims)信息。
JWT的常见用途包括在身份验证流程中生成令牌,将用户信息传递给Web应用程序,以及在不同的服务之间进行身份验证和授权。由于JWT是自包含的,不需要在服务器端存储会话信息,因此它们适用于分布式系统和微服务架构。
JWT的结构是一个紧凑的、自包含的文本字符串,它由三个部分组成,这些部分使用点号(.)分隔开来,分别是Header、Payload和Signature
头部通常包含了关于令牌的元信息,例如使用的加密算法。这部分使用Base64编码,但未加密。header中有两个指定的字段:alg和typ
{
"alg": "HS256",
"typ": "JWT"
}
alg(Algorithm):指定用于对JWT进行签名的加密算法。在这里,使用了HS256,它代表HMAC SHA-256算法,一种常见的对称加密算法。
typ(Type):指定令牌的类型,通常设置为"JWT"表示这是一个JSON Web Token。
载荷(Payload):载荷包含了一些声明(claims),这些声明描述了实体(通常是用户)和其他数据。有三种类型的声明:
{
"sub": "1234567890",
"name": "tuboshusec",
"iat": 1697790142
}
在这个示例中,JWT的载荷部分是一个JSON对象,包含了一些声明(claims):
签名(Signature):签名部分用于验证令牌的完整性和真实性。它使用头部中指定的加密算法(如HMAC SHA256或RSA)对头部和载荷部分进行签名,以确保它们在传输过程中未被篡改。
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
这个示例使用HMAC SHA-256算法生成JWT的签名,其中:
3DyCO9ZQpXGbp7ZhSJxKQAWKz-dDWy2oGmjNRhzd_6I
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InR1Ym9zaHVzZWMiLCJpYXQiOjE2OTc3OTAxNDJ9.f9B4-JRPyMGMiL2syuhuB9k0mZV4mhTN72MZesAn-tQ
这是一个完整的JWT,包括头部、载荷和签名部分。JWT的实际用途是在身份验证和授权流程中传递信息,并确保令牌的完整性和真实性。在实际应用中,密钥将用于生成和验证签名部分。可以看到JWT是base64编码的,并不是加密的,所以JWT可以被解密。
该网站可进行JWT在线加解密
https://jwt.io/
JWT的工作原理允许在不同服务之间传递身份验证和授权信息,而无需在服务器端存储会话状态。这使得JWT在分布式系统和微服务架构中非常有用。同时,使用正确的安全实践和保护密钥是确保JWT安全性的关键部分。
在java中JWT库可以很容易实现JWT签名和验证
使用JWT库要在Maven或Gradle中添加依赖,在Maven中,可以将以下依赖添加到pom.xml文件中:
io.jsonwebtoken
jjwt
0.9.1
这是一个简单的JWT签名和验证的示例代码:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.Claims;
public class JwtExample {
// 密钥,实际应用中应该保护好密钥,不要硬编码在代码中
private static final String SECRET_KEY = "mySecretKey";
public static void main(String[] args) {
// 创建JWT
String jwt = createJWT("1234567890", "John Doe");
System.out.println("Generated JWT: " + jwt);
// 验证JWT
Claims claims = parseJWT(jwt);
if (claims != null) {
System.out.println("Subject: " + claims.getSubject());
System.out.println("Name: " + claims.get("name"));
} else {
System.out.println("JWT verification failed.");
}
}
// 创建JWT
private static String createJWT(String subject, String name) {
return Jwts.builder()
.setSubject(subject)
.claim("name", name)
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
// 验证JWT
private static Claims parseJWT(String jwt) {
try {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
// 验证失败
return null;
}
}
}
在上面的示例代码中使用jwt库进行JWT的签名和验证,首先构建了一个JWT,然后将其分离为Header、Payload和Signature三部分,使用parseClaimsJws函数对JWT进行解析和验证,从而获取其中的Payload中的信息并进行验证.
我们这里用burpsuite的靶场进行学习:
靶场地址:https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature
这是提示,告诉我们要做什么
账号密码为wiener/peter
登录
bp里有这样的数据包
可以看到session是一个标准的JWT形式,直接解密看看
我们把中间紫色部分即payload部分sub用户改成administrator,然后编码一下替换原版的payload部分,注意编码的时候别有空格哦
题目要求访问/admin路径
有两个删除用户的接口,进行删除操作
删除成功,这道题就是我们利用JWT可以被解密的特性,伪造了administrator用户的JWT,实现从普通用户到administrator权限的一个越权操作。
靶场地址:
https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-flawed-signature-verification
还是一样的要求,要删除carlos用户,和上一题一样的操作,登录进来先
看似和上题一样的数据包,尝试替换administrator的payload,伪造JWT去请求/admin
这里返回了401,这里就是和上一题不一样的地方,解决方法也很简单。去解密一下JWT的第一部分即header部分
将alg至为none再进行编码替换
再进行一步URL编码
此时再替换header,并将SIGNATURE签名去掉,只留下header和payload部分
这时返回200,也返回了删除用户的接口,拿着伪造后的JWT访问接口
成功通关,这一关是利用了如果"alg"字段设为"None",则标识不签名,这样一来任何token都是有效的,设定该功能的最初目的是为了方便调试,但是若不在生产环境中关闭该功能,攻击者可以通过将alg字段设置为"None"来伪造他们想要的任何token,接着便可以使用伪造的token冒充任意用户登陆网站
***************未完待续