JSON web tokens (JWT) attack - Tấn công JWT (phần 5)
IV. Algorithm confusion attacks
Algorithm Confusion attacks trong JWT (JSON Web Tokens) là một phương thức tấn công nhắm vào việc lợi dụng sự nhầm lẫn hoặc thiếu sót trong việc xác định thuật toán (algorithm) được sử dụng để ký hoặc xác minh JWT (Tham khảo thêm CVE-2016-5431/CVE-2016-10555). Trước hết, chúng ta sẽ tìm hiểu về hai dạng thuật toán thông dụng trong JWT: Thuật toán đối xứng (symmetric algorithms) và bất đối xứng (asymmetric algorithms).
1. Thuật toán đối xứng và bất đối xứng trong JWT
Thuật toán đối xứng (Symmetric algorithms) sử dụng cùng một khóa (secret key) cho cả quá trình ký (sign) và xác minh (verify) JWT. Một ví dụ tiêu biểu là HS256 (HMAC-SHA256), trong đó HMAC (Hash-based Message Authentication Code) được sử dụng để ký và xác minh chữ ký. Với thuật toán đối xứng, cả server và client đều biết khóa bí mật.
import jwt
# Tạo JWT bằng thuật toán HS256
payload = {'user_id': 123, 'username': 'john.doe'}
secret_key = 'my_secret_key'
jwt_token = jwt.encode(payload, secret_key, algorithm='HS256')
print('JWT:', jwt_token)
# Xác minh JWT bằng thuật toán HS256
try:
decoded_token = jwt.decode(jwt_token, secret_key, algorithms=['HS256'])
print('Decoded Token:', decoded_token)
except jwt.InvalidTokenError:
print('Invalid Token')
Mặt khác, thuật toán bất đối xứng (Asymmetric algorithms) sử dụng một cặp khóa gồm khóa riêng tư (private key) và khóa công khai (public key) để thực hiện quá trình ký và xác minh JWT. Khi server tạo JWT, nó sẽ sử dụng khóa riêng tư để ký chữ ký. Sau đó, client sử dụng khóa công khai tương ứng để xác minh chữ ký. Ví dụ về thuật toán bất đối xứng RS256 (RSA-SHA256):
import jwt
# Tạo JWT bằng thuật toán RS256
payload = {'user_id': 123, 'username': 'john.doe'}
private_key = open('private_key.pem').read()
public_key = open('public_key.pem').read()
jwt_token = jwt.encode(payload, private_key, algorithm='RS256')
print('JWT:', jwt_token)
# Xác minh JWT bằng thuật toán RS256
try:
decoded_token = jwt.decode(jwt_token, public_key, algorithms=['RS256'])
print('Decoded Token:', decoded_token)
except jwt.InvalidTokenError:
print('Invalid Token')
Trong JWT, thuật toán được chỉ định trong phần header của token. Nếu thuật toán không được xác định hoặc không được hỗ trợ, quá trình xác minh chữ ký sẽ thất bại.
Việc chọn thuật toán phù hợp là rất quan trọng để đảm bảo tính toàn vẹn và bảo mật của JWT. Thuật toán đối xứng và bất đối xứng đều có ưu điểm và hạn chế riêng, và lựa chọn thuật toán phụ thuộc vào ngữ cảnh và yêu cầu bảo mật của ứng dụng.
2. Sai sót dẫn đến lỗ hổng algorithm confusion attack
JWT cho phép các thuật toán khác nhau được sử dụng để ký và xác minh chữ ký. Tuy nhiên, khi không kiểm tra hoặc kiểm tra không chính xác thuật toán được chỉ định trong phần header của JWT, người tấn công có thể thay đổi thuật toán để tận dụng các lỗ hổng liên quan đến việc triển khai hoặc xử lý JWT.
Xem xét đoạn code sau:
function verify(token, secretOrPublicKey){
algorithm = token.getAlgHeader();
if(algorithm == "RS256"){
// Use the provided key as an RSA public key
} else if (algorithm == "HS256"){
// Use the provided key as an HMAC secret key
}
}
Chương trình trên chấp nhận xác thực JWT do người dùng cung cấp trong cả hai trường hợp token sử dụng thuật toán RS256 (bất đối xứng) và HS256 (đối xứng). Khi đó, nếu ứng dụng bị lộ RSA public key, kẻ tấn công có thể chuyển thể sang dạng PEM, mã hóa Base64 và sử dụng như secret key của thuật toán đối xứng (Điều ngược lại cũng có thể thực hiện).
Bạn đọc có thể luyện tập kỹ thuật tấn công này trong bài lab JWT authentication bypass via algorithm confusion.
Sử dụng công cụ dò quét đường dẫn file (Ví dụ Dirsearch), dễ dàng nhận thấy trang web tồn tại đường dẫn /jwks.json
chứa danh sách các bộ public keys.
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "8f5a0479-7077-4d1e-966d-68b5f6115e65",
"alg": "RS256",
"n": "yvto63Vh_UjvqS5RcmU4xvweFCWMSq-mAAOrvjotDl8KLYxk4ulK71MLhiKw_-9Vo_7nZPqcNidJju20Jj-GbZq0HeJ7A7dAGI4dCFwYuG18mMVJF-lM9ch2BjCjQyf3YwhshtrOvSwUEt6DIvR-lu9FdPTj3aMwch79TqiANkqatOpnqerLqNs9lJmERd0FxgIuSwc1U82DXrqXxLCIZEY97GdXppjs33lkDCY1oq209w56Z4abkAEY6sIapBRMP1R9IP_CV-ieAnwXOzYKMKyJ2udMMDoYv_4Znze-dgaQSA2Q_4gIxEZE6GbcMOp1wtGlIyx-FTW_eXh4BHMR6Q"
}
Sau khi đăng nhập, nhận thấy JWT sử dụng thuật toán bất đối xứng RS256:
Có thể dự đoán ứng dụng truy cập tới đường dẫn /jwks.json
tìm kiếm bộ public key tương ứng với kid
trong JWT và xác thực người dùng.
Chúng ta sẽ chuyển thể bộ public key của thuật toán bất đối xứng RS256 sang chuỗi secret key của thuật toán đối xứng HS256.
Tại extension JWT Editor Keys > chọn New RSA Key > Sao chép bộ public key trên và dán vào phần key > Chuyển sang dạng PEM trong Key Format:
Tiếp theo, thực hiện mã hóa Base64 giá trị này:
Sử dụng jwt.io, dán JWT người dùng wiener
vào phần Encoded > Thay đổi tham số alg
thành HS256 > Dán secret key (dạng Base64) vào ô SIGNATURE > Trang web thông báo Signature Verified:
Chứng tỏ secret key chính xác, bây giờ có thể chỉnh sửa giá trị sub
thành người dùng tùy ý. Thay đổi thành "sub": "administrator"
và thay vào Request, nhận thấy Response trả về thông báo đăng nhập thành công người dùng administrator:
Trong trường hợp chúng ta không thể thu thập thông tin về bộ public key của ứng dụng, chúng ta hoàn toàn có thể sử dụng một số công cụ tìm thử sai và tìm ra bộ public key của ứng dụng (trong trường hợp giá trị n
nhỏ), ví dụ chương trình sig2n.py
do Portswigger gợi ý: https://github.com/silentsignal/rsa_sign2n/blob/release/sig2n.py.
Chương trình dựa vào hai JWT sinh ra sau hai lần đăng nhập khác nhau, tính toán một hoặc nhiều giá trị tiềm năng của n
để kiểm tra chúng có khớp với giá trị của n
trong bộ khóa của máy chủ hay không, từ đó trả về chuỗi JWT giả mạo cùng với bộ public key giả mạo tương ứng.
Trong hình trên, Tampered JWT là chuỗi JWT giả mạo và Base64 encoded x509 key là bộ public key tương ứng.
Với kỹ thuật tấn công algorithm confusion sử dụng bộ public key giả mạo trong thuật toán bất đối xứng (RS256) để thu được secret key trong thuật toán đối xứng (HS256), bạn đọc có thể luyện tập trong bài lab JWT authentication bypass via algorithm confusion with no exposed key.
Có thể dễ dàng ngăn chặn phương thức tấn công này bằng cách chỉ cho phép sử dụng một loại thuật toán mã hóa duy nhất, đồng thời tăng độ phức tạp của secret key.
3. Challenge CTF
Xin giới thiệu tới bạn đọc một thử thách CTF mô phỏng trường hợp trang web chỉ chấp nhận thuật toán bất đối xứng RS256, tuy nhiên, do bộ public key sử dụng không đủ mạnh, dẫn tới kẻ tấn công có thể sử dụng một số công cụ từ public key thu được private key. Source code challenge như sau (lưu ý cần sử dụng bộ key có tính chất "week"):
from flask import Flask, request
import jwt, time, os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
private_key = open('priv').read()
public_key = open('pub').read()
flag = open('flag.txt').read()
@app.route("/get_token")
def get_token():
return jwt.encode({'admin': False, 'now': time.time()}, private_key, algorithm='RS256')
@app.route("/get_flag", methods=['POST'])
def get_flag():
try:
payload = jwt.decode(request.form['jwt'], public_key, algorithms=['RS256'])
if payload['admin']:
return flag
except:
return ":("
@app.route("/")
def sauce():
return "%s" % open(__file__).read()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Hướng giải challenge có thể tham khảo tại https://ctftime.org/writeup/30541.
Các tài liệu tham khảo
All rights reserved