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

Bài cho 1 form login.

Sau đó login với guest

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ả:

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:

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

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>

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`:

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

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`:

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ả:

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

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

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

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:

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:

Token: `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODU5MzU0NzcsIm5iZiI6MTY4NTkzNTQ3NywianRpIjoiZGIwZmQwYjAtZjM1Zi00MjEzLWI5MTEtZWRmZmNhODdlN2Q2IiwiZXhwIjoxNjg1OTM1NjU3LCJpZGVudGl0eSI6ImFkbWluIiwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.aqfOmz1hmOdxdz3C5Fg3OksrfQ0ZnYUedP-s9g_CzVg`
Thử dùng nó trong `/admin` qua header `Authorization: Bearer <token>`:

`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ó:

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:

Đó 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ả:

> 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>


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 đó:

Decode JWT:

Đầ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`:

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`

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

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

> 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>

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:

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

Decode nó xem thử:

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.

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`.

Kết quả:

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

Oke bắt đầu vào:

Có chỗ login bằng JWT:

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

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:

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).

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


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

> 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>


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:

Decode JWT:

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ì:

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):

Key tạo bằng burp:

Sẽ được:

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

Kết quả:

> 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 ^^

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>, ...