+6

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

image.png

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

image.png

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.

image.png

image.png

{
  "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:

image.png

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:

image.png

Tiếp theo, thực hiện mã hóa Base64 giá trị này:

image.png

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:

image.png

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:

image.png

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.

image.png

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

Viblo
Let's register a Viblo Account to get more interesting posts.