IV. Tấn công Algorithm Confusion trong JWT
Tấn công Algorithm Confusion trong JSON Web Tokens (JWT) là một phương thức tấn công nhằm lợi dụng sự nhầm lẫn trong việc xác định thuật toán được sử dụng để ký và xác minh JWT. Đặc biệt, các lỗ hổng này đã được ghi nhận trong các CVE như CVE-2016-5431 và CVE-2016-10555. Trong phần này, chúng ta sẽ tìm hiểu về hai loại thuật toán chính trong JWT: thuật toán đối xứng và thuật toán bất đối xứng.
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 cho cả quá trình ký và xác minh JWT. Một ví dụ phổ biến là HS256 (HMAC-SHA256), trong đó sử dụng HMAC để đảm bảo tính toàn vẹn của thông điệp. Với thuật toán này, cả server và client đều biết khóa bí mật.
python
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')
Thuật toán bất đối xứng (Asymmetric algorithms) sử dụng một cặp khóa bao gồm khóa riêng và khóa công khai để thực hiện ký và xác minh JWT. Khi server tạo JWT, nó sẽ sử dụng khóa riêng để ký, và client sẽ sử dụng khóa công khai để xác minh.
Ví dụ với thuật toán RS256 (RSA-SHA256):
python
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 định nghĩa hoặc không được hỗ trợ, quá trình xác minh sẽ thất bại. Việc chọn thuật toán đúng là cần thiết để bảo vệ tính toàn vẹn và bảo mật của JWT.
2. Các sai sót dẫn đến lỗ hổng tấn công Algorithm Confusion
JWT cho phép việc sử dụng nhiều thuật toán khác nhau để ký và xác minh. Tuy nhiên, nếu không kiểm tra hoặc kiểm tra không chính xác thuật toán trong header của JWT, kẻ tấn công có thể khai thác lỗ hổng bằng cách thay đổi thuật toán được sử dụng.
Xem đoạn mã sau đây:
javascript
function verify(token, secretOrPublicKey){
algorithm = token.getAlgHeader();
if(algorithm == "RS256"){
// Sử dụng key cung cấp như một public key RSA
} else if (algorithm == "HS256"){
// Sử dụng key cung cấp như một secret key HMAC
}
}
Đoạn mã trên cho phép xác thực JWT từ người dùng với cả hai thuật toán RS256 và HS256. Nếu ứng dụng lộ public key RSA, kẻ tấn công có thể chuyển đổi nó sang định dạng PEM và sử dụng mã hóa Base64 để vận dụng cho thuật toán đối xứng.
Người đọc có thể thử nghiệm kỹ thuật tấn công này trong bài lab JWT authentication bypass via algorithm confusion.
Sử dụng các công cụ quét, đơn giản có thể thấy ứng dụng có đường dẫn /jwks.json
chứa danh sách các public keys.
json
{
"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 và phát hiện JWT dùng thuật toán RS256, có thể dự đoán ứng dụng sẽ truy cập /jwks.json
tìm public key tương ứng với kid
để xác thực.
Bằng cách chuyển đổi public key từ RS256 sang dạng secret key cho HS256, người tấn công có thể dễ dàng thực hiện mã hóa để đạt được mục tiêu.
3. Thử thách CTF
Chúng tôi xin giới thiệu 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, nhưng với public key yếu, cho phép kẻ tấn công tìm ra private key. Mã nguồn thử thách như sau:
python
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 dẫn giải thử thách có thể tham khảo tại CTF Time.