网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
“name”: “John Doe”,
“iat”: 1516239022
}
其中sub表示主题,name表示名称,iat表示JWT的签发时间
##### Signature
Signature是使用指定算法对Header和Payload进行签名生成的,用于验证JWT的完整性和真实性,Signature的生成方式通常是将Header和Payload连接起来然后使用指定算法对其进行签名,最终将签名结果与Header和Payload一起组成JWT,Signature的生成和验证需要使用相同的密钥,下面是一个示例Signature
HMACSHA256(base64UrlEncode(header) + “.” +base64UrlEncode(payload),secret)
其中HMACSHA256是使用HMAC SHA256算法进行签名,header和payload是经过Base64编码的Header和Payload,secret是用于签名和验证的密钥,最终将Header、Payload和Signature连接起来用句点(.)分隔就形成了一个完整的JWT,下面是一个示例JWT,其中第一部分是Header,第二部分是Payload,第三部分是Signature,注意JWT 中的每一部分都是经过Base64编码的,但并不是加密的,因此JWT中的信息是可以被解密的
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
##### 在线平台
下面是一个JWT在线构造和解构的平台:
https://jwt.io/

#### 工作原理
##### JWT工作原理
JWT的工作流程如下:
* 用户在客户端登录并将登录信息发送给服务器
* 服务器使用私钥对用户信息进行加密生成JWT并将其发送给客户端
* 客户端将JWT存储在本地,每次向服务器发送请求时携带JWT进行认证
* 服务器使用公钥对JWT进行解密和验证,根据JWT中的信息进行身份验证和授权
* 服务器处理请求并返回响应,客户端根据响应进行相应的操作
##### JKU工作原理
Step 1:用户携带JWS(带有签名的JWT)访问应用

Step 2:应用程序解码JWS得到JKU字段

Step 3:应用根据JKU访问返回JWK的服务器

Step 4:应用程序得到JWK

Step 5:使用JWK验证用户JWS

Step 6:验证通过则正常响应

#### 漏洞攻防
##### 签名未校验
###### 验证过程
JWT(JSON Web Token)的签名验证过程主要包括以下几个步骤:
* 分离解构:JWT的Header和Payload是通过句点(.)分隔的,因此需要将JWT按照句点分隔符进行分离
* 验证签名:通过使用指定算法对Header和Payload进行签名生成签名结果,然后将签名结果与JWT中的签名部分进行比较,如果两者相同则说明JWT的签名是有效的,否则说明JWT的签名是无效的
* 验证信息:如果JWT的签名是有效的则需要对Payload中的信息进行验证,例如:可以验证JWT中的过期时间、发行者等信息是否正确,如果验证失败则说明JWT是无效的
下面是一个使用JAVA进行JWT签名验证的示例代码:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JWTExample {
private static final String SECRET\_KEY \= "my\_secret\_key";
public static void main(String\[\] args) {
// 构建 JWT
String jwtToken \= Jwts.builder()
.setSubject("1234567890")
.claim("name", "John Doe")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
.signWith(SignatureAlgorithm.HS256, SECRET\_KEY)
.compact();
// 验证 JWT
try {
// 分离 Header, Payload 和 Signature
String\[\] jwtParts \= jwtToken.split("\\\\.");
String header \= jwtParts\[0\];
String payload \= jwtParts\[1\];
String signature \= jwtParts\[2\];
// 验证签名
String expectedSignature \= Jwts.parser()
.setSigningKey(SECRET\_KEY)
.parseClaimsJws(jwtToken)
.getSignature();
if (!signature.equals(expectedSignature)) {
throw new RuntimeException("Invalid JWT signature");
}
// 验证 Payload 中的信息
Claims claims \= Jwts.parser()
.setSigningKey(SECRET\_KEY)
.parseClaimsJws(jwtToken)
.getBody();
System.out.println("Valid JWT");
} catch (Exception e) {
System.out.println("Invalid JWT: " + e.getMessage());
}
}
}
在上面的示例代码中使用jwt库进行JWT的签名和验证,首先构建了一个JWT,然后将其分离为Header、Payload和Signature三部分,使用parseClaimsJws函数对JWT进行解析和验证,从而获取其中的Payload中的信息并进行验证,最后如果解析和验证成功,则说明JWT是有效的,否则说明JWT是无效的,在实际应用中应该将SECRET\_KEY替换为应用程序的密钥
###### 漏洞案例
JWT库会通常提供一种验证令牌的方法和一种解码令牌的方法,比如:Node.js库jsonwebtoken有verify()和decode(),有时开发人员会混淆这两种方法,只将传入的令牌传递给decode()方法,这意味着应用程序根本不验证签名,而我们下面的使用则是一个基于JWT的机制来处理会话,由于实现缺陷服务器不会验证它收到的任何JWT的签名,如果要解答实验问题,您需要修改会话令牌以访问位于/admin的管理面板然后删除用户carlos,您可以使用以下凭据登录自己的帐户:wiener:peter
靶场地址:https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature

演示步骤:
Step 1:点击上方的"Access the Lab"访问靶场并登录账户

Step 2:进入到Web界面并登录靶场账户
wiener:peter


登录之后会看到如下一个更新邮箱的界面

Step 3:此时在我们的burpsuite中我们可以看到如下的会话信息

此时查询当前用户可以看到会显示当前用户为wiener:

截取上面中间一部分base64编码的部分更改上面的sub为"administrator"
eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY4Nzc5MDA4M30

构造一个sub为"administrator"的载荷并将其进行base64编码处理:
eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6ImFkbWluaXN0cmF0b3IiLCJleHAiOjE2ODc3OTAwODN9

替换之后重新发送请求:

按照题目要求访问/admin路径,发现两个删除用户的调用接口:

请求敏感链接——删除用户carlos
GET /admin/delete?username=carlos HTTP/1.1

完成靶场的解答:

##### 签名用None
###### 场景介绍
在JWT的Header中alg的值用于告诉服务器使用哪种算法对令牌进行签名,从而告诉服务器在验证签名时需要使用哪种算法,目前可以选择HS256,即HMAC和SHA256,JWT同时也支持将算法设定为"None",如果"alg"字段设为"None",则标识不签名