Nghịch lý lưu trữ DPoP: Tại sao Proof-of-Possession trên trình duyệt vẫn là bài toán chưa có lời giải

30 tháng 4, 2026·12 phút đọc

DPoP giúp lấp đầy lỗ hổng bảo mật trong OAuth 2.0 bằng cách ràng buộc token với cặp khóa, nhưng sự im lặng của RFC 9449 về việc lưu trữ khóa trên trình duyệt đã tạo ra một rủi ro nghiêm trọng. Các khóa không thể trích xuất (non-extractable) trong IndexedDB thực tế không ngăn chặn được các cuộc tấn công XSS, biến trình duyệt thành một "oracle" ký số. Mô hình Backend-for-Frontend (BFF) hiện đang là tiêu chuẩn ngành để giải quyết vấn đề này, dù các phương pháp lưu trữ trong bộ nhớ vẫn là lựa chọn thay thế trong những trường hợp hạn chế.

Nghịch lý lưu trữ DPoP: Tại sao Proof-of-Possession trên trình duyệt vẫn là bài toán chưa có lời giải

Nghịch lý lưu trữ DPoP: Tại sao Proof-of-Possession trên trình duyệt vẫn là bài toán chưa có lời giải

Đội ngũ bảo mật của bạn vừa hoàn tất việc tích hợp DPoP. Các khóa riêng tư được lưu trữ trong IndexedDB dưới dạng các đối tượng CryptoKey không thể trích xuất (non-extractable), khiến hàm exportKey() ném ra ngoại lệ. Các byte thô của khóa không thể rời khỏi trình duyệt. Mọi thứ đều vượt qua danh sách kiểm tra kiểm toán — cho đến khi một chuyên gia thử nghiệm xâm nhập thả một tải trọng XSS vào hệ thống. Tải độc này ký các bằng chứng DPoP tùy ý bằng "khóa được bảo vệ" của bạn và đi qua máy chủ tài nguyên mà không gặp trở kháng nào. Khóa chưa bao giờ bị trích xuất. Và nó cũng không cần phải làm như vậy.

Kịch bản này nắm bắt một sự căng thẳng nằm ở trung tâm của cơ chế ràng buộc người gửi mới nhất của OAuth 2.0. DPoP hoạt động đúng như thông số kỹ thuật. Web Crypto API hoạt động đúng như thông số kỹ thuật. Tuy nhiên, đảm bảo bảo mật mà hầu hết các đội ngũ giả định họ nhận được từ sự kết hợp này lại không tồn tại trong ngữ cảnh của trình duyệt.

Từ Bearer Tokens đến Proof-of-Possession

Bearer token có một vấn đề nổi tiếng: bất kỳ ai sở hữu token đều có thể sử dụng nó. Các tuyến đường exfiltration (rò rỉ dữ liệu), bao gồm nhật ký bị xâm phạm, tiện ích mở rộng trình duyệt độc hại, XSS và chuỗi chuyển hướng mở, có nghĩa là việc sở hữu một token thường dễ dàng hơn mức nó nên được.

RFC 9700 (tháng 1 năm 2025), Thực tiễn tốt nhất hiện tại được cập nhật của IETF về bảo mật OAuth 2.0, xác định các token bị ràng buộc bởi người gửi (sender-constrained tokens) là một biện pháp đối phó được khuyến nghị chống lại việc sử dụng token bị đánh cắp. DPoP (RFC 9449, tháng 9 năm 2023) là cơ chế tầng ứng dụng giúp điều này trở nên khả thi cho các máy khách dựa trên trình duyệt mà không yêu cầu TLS lẫn nhau (mTLS) hoặc ràng buộc token tầng truyền tải.

Thay vào đó, DPoP hoạt động hoàn toàn ở tầng ứng dụng. Máy khách tạo ra một cặp khóa bất đối xứng, sau đó ký một bằng chứng DPoP JWT cho mỗi yêu cầu. Máy chủ ủy quyền xác thực bằng chứng và cấp ra một access token chứa một xác nhận cnf với JWK Thumbprint của khóa công khai của máy khách, ràng buộc token với khóa đã yêu cầu nó.

Quy trình DPoPQuy trình DPoP

Việc áp dụng hệ sinh thái đang tăng tốc. Spring Security 6.5 đã thêm hỗ trợ DPoP vào tháng 5 năm 2025. Keycloak 26.4 đã phát hành tính năng này vào tháng 9 năm 2025. Điều này đặt ra câu hỏi mà RFC 9449 hoàn toàn để lại cho người triển khai thực hiện: trên trình duyệt, khóa ký riêng tư thực sự nằm ở đâu và làm thế nào để bạn giữ nó an toàn trước chính các tập lệnh cần sử dụng nó?

Ràng buộc của trình duyệt: Nơi thông số kỹ thuật kết thúc và rắc rối bắt đầu

RFC 9449 yêu cầu máy khách "tạo ra" một cặp khóa. Nó không nói gì về nơi lưu trữ nó. Đối với các máy khách phía máy chủ, đây không phải là vấn đề — khóa sống trong bộ nhớ quy trình trên một máy mà kẻ tấn công không thể tiếp cận. Đối với một SPA dựa trên trình duyệt, câu hỏi không có câu trả lời sạch sẽ.

localStoragesessionStorage không thể lưu trữ các đối tượng CryptoKey. Khóa phải được tuần tự hóa thành một JWK có thể trích xuất — một chuỗi JSON thuần túy nằm trong khu vực lưu trữ hoàn toàn có thể truy cập được bởi bất kỳ tập lệnh nào chạy trên cùng nguồn gốc. XSS đánh cắp vật liệu khóa thô chỉ trong một dòng code.

IndexedDB với CryptoKey không thể trích xuất là phương pháp được khuyến nghị bởi bản dự thảo hướng dẫn của IETF cho các máy khách OAuth dựa trên trình duyệt. Web Crypto API cho phép bạn tạo một cặp khóa trong đó thuộc tính extractable của khóa riêng tư là false. IndexedDB có thể lưu trữ đối tượng CryptoKey kết quả thông qua thuật toán structured clone. Gọi exportKey() trên khóa này sẽ ném ra InvalidAccessError. Cho đến đây, mọi thứ vẫn tốt.

Tuy nhiên, Web Workers có thể cô lập refresh token khỏi phạm vi cửa sổ của luồng chính, nhưng chúng không tạo ra ranh giới tin cậy cho khóa ký. Bất kỳ tập lệnh nào chạy trên cùng nguồn gốc đều có thể postMessage đến Worker để yêu cầu chữ ký.

Đây là phần khiến các đội ngũ bị bất ngờ: thuộc tính extractable chỉ chặn chính xác hai thao tác: exportKey()wrapKey(). Đó là tất cả. Thông số kỹ thuật của Web Crypto không nói gì về việc ngăn chặn sign(). Một khóa không thể trích xuất là một khóa có byte bạn không thể xuất. Nó không phải là một khóa không thể sử dụng.

Điều này tạo ra một cái bẫy nhận thức. Việc từ chối exportKey() khiến các nhà phát triển hợp lý (nhưng sai lầm) giả định rằng một khóa không thể xuất cũng là một khóa không thể bị lạm dụng. API đang hoạt động chính xác như được chỉ định. Khoảng cách nằm trong mô hình tinh thần của nhà phát triển, không phải trong hành vi của trình duyệt. Đây là nghịch lý lưu trữ: trình duyệt không cung cấp cơ chế nào làm cho khóa vừa có thể sử dụng để ký vừa an toàn trước sự lạm dụng ở cấp độ tập lệnh.

Tấn công kiểu Oracle

Một kẻ tấn công đạt được XSS trên nguồn gốc SPA của bạn không cần phải trích xuất khóa. Họ chỉ cần quyền truy cập vào máy giữ khóa. Cuộc tấn công hoạt động như sau:

  1. Tập lệnh được chèn mở một giao dịch IndexedDB và đọc handle CryptoKey được lưu trữ.
  2. Tập lệnh gọi crypto.subtle.sign() với handle đó, tiêu đề do kẻ tấn công kiểm soát và tải trọng do kẻ tấn công kiểm soát.
  3. Kết quả là một bằng chứng DPoP JWT hợp lệ — được ký chính xác, cho bất kỳ phương thức HTTP và bất kỳ URI đích nào kẻ tấn công chọn.
  4. Kẻ tấn công đính kèm bằng chứng này vào access token bị đánh cắp và gọi máy chủ tài nguyên trực tiếp hoặc ủy quyền yêu cầu thông qua trình duyệt của nạn nhân.

Tấn công OracleTấn công Oracle

Hệ thống mật mã của trình duyệt trở thành một oracle ký số. Kẻ tấn công không bao giờ chạm vào các byte khóa thô. Một lần nữa, họ không cần phải làm như vậy.

DPoP nonces cho phép máy chủ ủy quyền kiểm soát tính mới của bằng chứng, nhưng nonces không thay đổi phương thức tấn công Oracle: kẻ tấn công tạo ra từng bằng chứng theo yêu cầu với nonce hiện tại, tạo ra các bằng chứng hợp lệ, không thể phát lại theo thời gian thực.

Những ràng buộc này khiến các triển khai DPoP dựa trên trình duyệt lâm vào thế khó. Phương pháp lưu trữ được khuyến nghị dễ bị tấn công Oracle trong các trình duyệt Chromium và hoàn toàn bị hỏng trong Firefox. Đây là lý do tại sao ngành công nghiệp phần lớn đã chuyển sang việc chuyển vấn đề hoàn toàn ra khỏi trình duyệt.

Mô hình BFF: Tiêu chuẩn hiện tại của ngành

Hướng dẫn của IETF cho các ứng dụng dựa trên trình duyệt trình bày các mẫu kiến trúc của nó "theo thứ tự giảm dần của bảo mật". Mô hình Backend-for-Frontend (BFF) đứng đầu — kiến trúc được xếp hạng cao nhất cho OAuth dựa trên trình duyệt.

Mô hình này tái cấu trúc ranh giới tin cậy. Thay vì SPA hoạt động như một máy khách OAuth công khai, một thành phần phía máy chủ, BFF, hoạt động như một máy khách bảo mật. BFF tạo ra cặp khóa DPoP trong bộ nhớ quy trình của chính nó, xử lý việc trao đổi mã ủy quyền với thông tin đăng nhập máy khách, quản lý access token và refresh token, và tạo ra các bằng chứng DPoP cho mỗi yêu cầu API đi. Trình duyệt không bao giờ thấy vật liệu khóa hay token. Nó chỉ giữ một cookie HTTP-only, Secure, SameSite ánh xạ đến phiên của BFF.

Sự dịch chuyển ranh giới tin cậy với BFFSự dịch chuyển ranh giới tin cậy với BFF

Cải thiện bảo mật mang tính phân loại, không phải gia tăng. XSS trên SPA vẫn có thể ủy quyền yêu cầu thông qua phiên trình duyệt hoạt động của nạn nhân. Nhưng bề mặt tấn công thu hẹp đáng kể: kẻ tấn công không thể trích xuất token, không thể giả mạo bằng chứng DPoP và không thể gọi crypto.subtle.sign() vì không có CryptoKey trong trình duyệt để ký.

BFF cũng ngăn chặn một cuộc tấn công khó khăn hơn mà DPoP phía máy khách hoàn toàn không thể giải quyết được. Đó là cuộc tấn công thu nhận token mới (fresh token acquisition attack), nơi XSS khởi chạy một luồng Mã ủy quyền âm thầm trong một iframe ẩn, tạo ra cặp khóa DPoP của kẻ tấn công và nhận các token hoàn toàn mới được ràng buộc với khóa đó. Vì BFF là một máy khách bảo mật, việc trao đổi mã ủy quyền yêu cầu thông tin đăng nhập máy khách mà kẻ tấn công không có.

Các đánh đổi và phương án thay thế

Các đánh đổi là có thật và đáng được nói rõ ràng. BFF là một thành phần cơ sở hạ tầng bổ sung để triển khai, mở rộng và giám sát. Nó thêm một bước nhảy mạng giữa SPA và máy chủ tài nguyên.

Đối với một số mô hình triển khai, BFF không chỉ tốn kém — mà là không thể. Các tiện ích mở rộng trình duyệt không có máy chủ nào cả. Các tiện ích của bên thứ ba được nhúng trong trang của tổ chức khác không có quyền kiểm soát phần phụ trợ của máy chủ lưu trữ. Các PWA ưu tiên ngoại tuyến trên cơ sở hạ tầng chỉ CDN có thể gặp các ràng buộc hợp đồng chống lại tính toán phía máy chủ.

Đối với những trường hợp như vậy, một phương pháp "không duy trì" (zero-persistence) có thể được xem xét. Cặp khóa DPoP được tạo ra thông qua crypto.subtle.generateKey() với extractable: false và được giữ trong một biến JavaScript có phạm vi mô-đun. Nó không bao giờ được ghi vào IndexedDB hay bất kỳ kho lưu trữ bền vững nào. Khi trang tải lại, tab đóng hoặc người dùng điều hướng đi, khóa sẽ biến mất.

Quy trình bắt tay Lazy Re-BindingQuy trình bắt tay Lazy Re-Binding

Kẻ tấn công đạt được XSS vẫn có thể truy cập vào handle trong bộ nhớ và gọi sign() trong phiên hiện tại. Tuy nhiên, phạm vi ảnh hưởng thay đổi. Khóa không thể tồn tại qua việc tải lại trang. Kẻ tấn công không thể thiết lập một vị trí vững bền qua các phiên. Kết hợp với Chính sách Bảo mật Nội dung (CSP) nghiêm ngặt, cửa sổ tấn công thu hẹp lại trong thời gian tồn tại của bối cảnh thực thi tập lệnh được chèn.

Lựa chọn mô hình phù hợp

Không có viên đạn bạc nào tồn tại. Quyết định phụ thuộc vào những gì kiến trúc của bạn có thể hỗ trợ và những mối đe dọa bạn ưu tiên.

Mô hình BFF: Tốt nhất cho các đội ngũ có cơ sở hạ tầng phần phụ trợ hiện có, yêu cầu quy định về quản lý token phía máy chủ hoặc dung sai thấp đối với rủi ro phía trình duyệt. Ngăn chặn cả Tấn công Oracle và Tấn công thu nhận token mới. Đánh đổi: chi phí cơ sở hạ tầng và độ trễ.

Chỉ bộ nhớ / Không duy trì: Phù hợp cho các tiện ích mở rộng trình duyệt, tiện ích nhúng, trang web tĩnh chỉ CDN hoặc các đội ngũ có CSP trưởng thành. Giới hạn phơi nhiễm Oracle liên tục. Đánh đổi: XSS cùng phiên vẫn hiệu quả; cần điều phối đa tab; việc thu nhận token mới không bị ngăn chặn.

IndexedDB với khóa không thể trích xuất: Có thể chấp nhận được khi kết hợp với phòng thủ theo chiều sâu — CSP nghiêm ngặt, Subresource Integrity và Trusted Types. Đội ngũ phải chấp nhận Tấn công Oracle là một rủi ro còn sót lại.

Kết luận

DPoP lấp đầy một khoảng trống thực sự trong OAuth 2.0. Các token bị ràng buộc bởi người gửi là một nâng cấp có ý nghĩa so với bearer token cho bất kỳ máy khách nào có thể triển khai chúng. Nhưng sự im lặng của RFC 9449 về lưu trữ khóa trình duyệt tạo ra nhu cầu đưa ra một quyết định kiến trúc mà mỗi đội ngũ phải đối mặt một cách có chủ ý — không có mặc định an toàn nào hoạt động ở mọi nơi.

Mô hình BFF là lựa chọn an toàn nhất hiện nay cho các ứng dụng dựa trên trình duyệt. Các phương pháp chỉ bộ nhớ cho thấy triển vọng cho các triển khai thực sự bị hạn chế nhưng mang lại những đánh đổi đòi hỏi các hoạt động vận hành trưởng thành và mô hình hóa mối đe dọa nghiêm ngặt.

Cho đến khi đó, nghịch lý vẫn tồn tại. DPoP cung cấp cho bạn bằng chứng sở hữu. Trình duyệt không cho bạn nơi nào an toàn để đặt những gì bạn sở hữu.

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗