[P2] Nhập môn Authentication: Cookie và Session Authentication
Tiếp nối sự thành công của [P1] Nhập môn authentication: Basic Authentication, phần 2 này chúng ta sẽ đi tìm hiểu về Cookie và Session là gì. Cách áp dụng Cookie và Session trong việc xác thực người dùng.
Trong bài viết này cũng như cả series này thì mình đều dùng Nodejs để ví dụ demo nhé. Các bạn học PHP, Java, .Net thì nó cũng có những thứ tương tự thôi.
Đây là những kiến thức mà các bạn sẽ đạt được sau khi đọc xong bài này
-
Hiểu rõ cookie cũng như cơ chế hoạt động của nó
-
Demo hack 1 website dùng cookie bằng phương pháp CSRF
-
Cách bảo mật cho website nếu website bạn dùng cookie
-
Session authentication là gì? Flow và Ưu nhược của nó
Oke, đầu tiên thì chúng ta tìm hiểu cookie nhé ✊
🥇 Cookie là gì?
Cookie là một file nhỏ được lưu trữ trên thiết bị user. Cookie thường được dùng để lưu thông tin về người dùng website như: tên, địa chỉ, giỏ hàng, lịch sử truy cập, mật khẩu (à dù có thể lưu mật khẩu được nhưng đừng lưu nhé, lỡ bị hacker hack, nó lấy đc mật khẩu là toang đấy!)...
Cookie được ghi và đọc theo domain.
Ví dụ khi bạn truy cập vào website cá nhân của Được https://phamhongduc.com
, và server mình trả về cookie thì trình duyệt của bạn sẽ lưu cookie cho domain phamhongduc.com
.
Khi bạn gửi request đến https://phamhongduc.com
(bao gồm việc bạn enter url vào thanh địa chỉ hay gửi api đến) thì trình duyệt của bạn tìm kiếm có cookie nào của https://phamhongduc.com
không và gửi lên server https://phamhongduc.com
.
Nhưng nếu bạn truy cập vào https://google.com
thì google sẽ không đọc được cookie bên https://phamhongduc.com
, vì trình duyệt không gửi lên.
☠️ Lưu ý: Nếu bạn đang ở trang https://google.com
và gửi request đến https://phamhongduc.com
thì trình duyệt sẽ tự động gửi cookie của https://phamhongduc.com
lên server của https://phamhongduc.com
, đây là một lỗ hổng để hacker tấn công CSRF. Để tìm hiểu thêm về kỹ thuật tấn công và cách khắc phục thì các bạn đọc thêm ở những phần dưới nhé.
Một website có thể lưu nhiều cookie khác nhau, ví dụ profile, cart, history, ...
Bộ nhớ của cookie có giới hạn, nên bạn không nên lưu quá nhiều thông tin vào cookie. Thường thì một website chỉ nên lưu tối đa 50 cookie và tổng cộng kích thước của các cookie trên website đó không nên vượt quá 4KB.
Đến đây mình sẽ làm rõ một số vấn đề về cookie như sau
🥈 Cookie được lưu trữ ở đâu?
Nó lưu trong 1 cái file, file này thì được lưu ở trên ổ cứng của bạn. Vậy nên là bạn tắt trình duyệt, shutdown máy tính đi mở lại thì nó vẫn còn đấy.
Ví dụ Truy cập file cookie trên macOS
Trên macOS, các file cookie được lưu trữ trong thư mục của trình duyệt web bạn đang sử dụng. Cụ thể:
-
Các file cookie của Google Chrome được lưu trữ tại:
/Users/<username>/Library/Application Support/Google/Chrome/Default/Cookies
-
Các file cookie của Firefox được lưu trữ tại:
/Users/<username>/Library/Application Support/Firefox/Profiles/<profile folder>/cookies.sqlite
-
Các file cookie của Safari được lưu trữ tại:
/Users/<username>/Library/Cookies/Cookies.binarycookies
Thường thì không ai vào đây xem đâu, vì nó là file nhị phân, bạn không thể đọc được nó. Chúng ta sẽ dùng trình duyệt để xem nhé.
🥈 Làm sao để ghi dữ liệu lên cookie của trình duyệt?
Có 3 cách để ghi dữ liệu lên cookie
-
Khi bạn truy cập vào 1 url hoặc gọi 1 api, server có thể set cookie lên máy tính của bạn bằng cách trả về header
Set-Cookie
trong response. -
Bạn có thể dùng javascript để set cookie lên máy tính của bạn thông qua
document.cookie
-
Bạn có thể dùng trình duyệt, mở devtool lên và set cookie lên máy tính của bạn
🥈 Làm sao để đọc dữ liệu từ cookie?
Khi bạn truy cập vào 1 url hoặc gọi 1 api, trình duyệt sẽ tự động gửi cookie lên server. Nhớ là tự động luôn nha, bạn không cần làm gì cả.
Ngoài ra bạn có thể dùng Javascript để đọc cookie của bạn, ví dụ
Lưu ý là nếu cookie được set HttpOnly thì bạn không thể đọc được cookie bằng Javascript đâu nhé.
Hoặc mở devtool lên xem 😂
Lưu ý là cookie lưu ở trang nào thì trình duyệt sẽ gửi cookie trang đó lên server nha. Nếu cookie của https://facebook.com
thì không có chuyện bạn vào https://phamhongduc.com
và mình đọc được cookie facebook của bạn đâu.
🥈 Demo ví dụ Cookie bằng NodeJs
Nãy giờ nắm đủ lý thuyết rồi hen, giờ demo thực hành thôi.
Dưới đây là đoạn code Node.js để tạo 1 cookie, khi bạn chạy đoạn code Node.Js này lên, truy cập vào http://localhost:3000/set-cookie
thì trình duyệt sẽ lưu cookie với tên username
và giá trị John Doe
trong 1 giờ (3600000 ms).
Khi bạn truy cập vào http://localhost:3000/get-cookie
thì trình duyệt sẽ gửi cookie lên server, server sẽ đọc cookie và trả về trang tương ứng cho bạn.
Đoạn code trên các bạn có thể test bằng axios gọi API với method GET nhé. Chứ nhiều bạn nghĩ là cookie chỉ dùng cho server side rendering truyền thống, không áp dụng được cho RESTful API thì toang 😂, nó vẫn dùng bình thường nhá.
🥈 Một số lưu ý quan trọng khi sử dụng cookie
🥈 HttpOnly
Khi set HttpOnly
cho một cookie của bạn thì cookie đó sẽ không thể đọc được bằng Javascript (tức là không thể lấy cookie bằng document.cookie
được). Điều này giúp tránh được tấn công XSS.
Tân công XSS hiểu đơn giản là người khác có thể chạy được code javascript của họ trên trang web của bạn. Ví dụ bạn dùng một thư viện trên npm, người tạo thư viện này cố tình chèn một đoạn code javascript như sau
Khi bạn deploy website, user truy cập vào website của bạn, thì đoạn code trên sẽ chạy và gửi cookie của user về cho kẻ tấn công (người tạo thư viện). Nếu cookie chứa các thông tin quan trọng như tài khoản ngân hàng, mật khẩu, ... thì user đã bị hack rồi.
Để set HttpOnly
cho cookie, bạn chỉ cần thêm option httpOnly: true
vào cookie như sau
🥈 Secure
Khi set Secure cho một cookie của bạn thì cookie đó chỉ được gửi lên server khi bạn truy cập vào trang web bằng https. Điều này giúp tránh được các lỗ hổng MITM (Man in the middle attack).
Man-in-the-middle (MITM) là một kỹ thuật tấn công mạng, trong đó kẻ tấn công can thiệp vào kết nối giữa hai bên và trộn lẫn thông tin giữa họ. Khi bị tấn công, người dùng thường không nhận ra được sự can thiệp này. Ví dụ bạn dùng wifi công cộng, kẻ tấn công có thể đọc được dữ liệu bạn gửi đi.
Để set Secure cho cookie, bạn chỉ cần thêm option secure: true
vào cookie như sau
🥈 Tấn công CSRF
Lợi dụng cơ chế khi request trên một url nào đó, trình duyệt sẽ tự động gửi cookie lên server, kẻ tấn công có thể tạo một trang web giả mạo, khi user truy cập vào trang web giả mạo và thực hiện hành động nào đó, trình duyệt sẽ tự động gửi cookie lên server, kẻ tấn công có thể lợi dụng cookie này để thực hiện các hành động độc hại.
Ví dụ:
Chúng ta dùng facebook để post bài lên newfeeeds tại url là https://facebook.com
. Tất nhiên là muốn post được bài viết thì facebook sẽ kiểm tra bạn đã đăng nhập hay chưa thông qua cookie bạn gửi lên lúc bạn nhấn nút Đăng bài.
Dưới đây là đoạn code mô phỏng cách server facebook hoạt động khi bạn nhấn nút Đăng bài.
(Mô phỏng lỗi thôi chứ facebook không có lỗi này đâu nhé 😂)
File server facebook.js
chạy tại http://localhost:3000
File server hacker.js
chạy tại http://http://127.0.0.1:3300
(sở dĩ mình không để link localhost
vì nó sẽ cũng domain với cái server facebook trên, nên chúng ta sẽ không test được)
🥉Demo tấn công CSRF
Đây là hướng dẫn tấn công CSRF dựa trên ví dụ ngay bên trên nha.
-
Bước 1: Tạo 2 file
facebook.js
vàhacker.js
như trên -
Bước 2: Chạy 2 file server trên 2 terminal khác nhau
-
Bước 3: Truy cập vào
http://localhost:3000/login
để đăng nhập vào facebook -
Bước 4: Quay trở lại trang chủ
http://localhost:3000
để xem đăng nhập thành công chưa -
Bước 5: Mở 1 tab mới truy cập vào
http://127.0.0.1:3300
và nhấn và nút Click vào đây để xem full video -
Bước 6: Quay lại tab facebook, F5 và xem kết quả, bạn sẽ thấy bên website facebook đã bị hack
🥈 Cách phòng chống tấn công CSRF
Ở đây mình sẽ có một số cách phòng chống như sau
✅Cách 1: Sử dụng thuộc tính SameSite=Strict
cho cookie
Các bạn mở lại code server facebook.js
và sửa lại như thế này nhé
Với SameSite=Strict
thì cookie sẽ không được gửi đi nếu request không phải là request từ trang web hiện tại. Ví dụ như ở trên thì cookie sẽ không được gửi đi nếu request đến từ http://127.0.0.1:3300
Lưu ý với SameSite
Quy tắc quyết định 2 site có phải là same không nó phức tạp hơn bạn nghĩ.
Ví dụ như 2 site https://subdomain1.phamhongduc.com
và http://phamhongduc.com
được coi là same site vì cùng public suffix phamhongduc.com
Nhưng 2 site https://phamhongduc.github.io
và https://phd.github.io
thì không được coi là same site vì khác public suffix, ở đây các bạn có thể hiểu github.io
nó giống như cái tên miền com
rồi.
Để hiểu rõ hơn về samesite thì mình khuyên các bạn nên đọc những bài này
✅Cách 2: Sử dụng CSRF Token:
CSRF token là một chuỗi ngẫu nhiên được tạo ra để bảo vệ khỏi tấn công Cross-Site Request Forgery (CSRF). Khi người dùng yêu cầu truy cập tài nguyên, server sẽ tạo ra một token và gửi nó về cho người dùng. Khi người dùng gửi yêu cầu tiếp theo, họ phải bao gồm token này trong yêu cầu của mình. Nếu token không hợp lệ, yêu cầu sẽ bị từ chối. Điều này giúp ngăn chặn kẻ tấn công thực hiện các yêu cầu giả mạo.
Để áp dụng CSRF Token cho facebook.js
server thì các bạn sửa code thành như dưới đây
✅Cách 3: Sử dụng CORS:
Cross-Origin Resource Sharing (CORS) là một cơ chế để ngăn chặn các yêu cầu từ các tên miền khác nhau. Bằng cách thiết lập CORS, bạn có thể chỉ cho phép các yêu cầu từ các tên miền cụ thể hoặc từ tất cả các tên miền. Ví dụ như ở trên thì nếu server facebook chỉ cho phép các yêu cầu từ tên miền http://localhost:3000
thì hacker sẽ không thể tấn công được.
Thêm cái này vào facebook.js
🥈 Single Page Application có bị tấn công CSRF không?
Câu trả lời là có! Nhưng hiếm khi xảy ra trừ khi bạn chủ động set SameSite=None
cho cookie của bạn.
Như các bạn thấy thì CSRF nghĩa là một request được thực hiện trên một trang web hacker. Nãy giờ chúng ta chỉ ví dụ với cơ chế GET POST truyền thống, chứ không phải REST API phổ biến như chúng ta thao tác ngày nay.
Với REST API thì để gửi một request đến http://localhost:3000/status
trên trang web http://127.0.0.1:3300
chúng ta có thể dùng fetch API như dưới đây.
Lúc này cookie của http://localhost:3000
sẽ không được gửi lên http://localhost:3000/status
đâu, vì nếu các bạn không set SameSite khi server trả về thì mặc định trình duyệt sẽ ngầm hiểu đây là SameSite=Lax
.
Mà với SameSite=Lax
thì chỉ cho phép gửi cookie đối với những request mà reload lại page (ví dụ request trong form method post truyền thống ở các ví dụ trên), còn mấy cái fetch
, XMLHttpRequest
hay axios
thì nó không gửi cookie đâu.
Còn nếu bạn set SameSite=none
(khi đó phải thêm secure=true
nữa browsers nó mới chập nhận cái samesite none này) thì khỏi nói luôn, hacker có thể thay đổi data của bạn nếu bạn truy cập trang web của hacker.
🥈 Tóm lại thì làm sao bảo vệ website khỏi CSRF?
Nếu bạn không dùng cookie thì không cần quan tâm, vì no cookie no CSRF.
Nếu bạn sài combo REST API và SPA thì đầu tiên là phải thiết lập cors, httpOnly=true
, secure=true
, SameSite=Strict
hoặc SameSite=Lax
.
Cẩn thận với SameSite=Strict
:
Vì nếu bạn set SameSite=Strict
thì khi bạn đăng nhập vào example.com
rồi. Bây giờ bạn click vào đường link example.com
trên trang web khác thì trình duyệt sẽ không gửi cookie đâu, dẫn đến việc dù bạn đã đăng nhập lúc nãy nhưng vẫn bị chuyển về trang login vì bị cho là chưa đăng nhập.
Cái này thường xảy ra khi website của bạn là website theo MPA truyền thống, còn nếu là SPA thì không sao cả, vì hầu như các SPA chúng ta đều gọi request và gửi cookie lên server thông qua fetch
hay XMLHttpRequest
(tức là đã redirect đến trang) chứ không phải ngay khi click vào đường link.
Cá nhân mình nghĩ không cần phải dùng thêm CSRF token nữa, vì nó chỉ làm cho cơ chế xác thực của bạn phức tạp hơn thôi. Như trên là đủ rồi.
🥇 Session Authentication
Oke, mọi người đã nắm vững cookie chưa nhỉ 🤪? Nếu chưa thì đọc lại phần trên đi nhé.
Trước khi tìm hiểu về session authentication thì chúng ta cần phải hiểu về session trước đã.
🥈 Session là gì?
Session là phiên lưu trữ trên server để quản lý thông tin liên quan đến mỗi người dùng trong quá trình tương tác với ứng dụng.
Session được lưu trữ trên server, còn cookie được lưu trữ trên client. Nhớ rõ điều này nha.
Session có thể được lưu ở dạng file, database, cache, memory, ... tùy vào cách thiết kế server như thế nào.
🥈 Session Authentication là gì?
Session Authentication là một cơ chế xác thực người dùng bằng cách sử dụng session.
Khi người dùng đăng nhập thành công, server sẽ tạo ra một session mới và gửi session id đó về cho client thông qua cookie (thường là cookie thôi chứ không nhất thiết, client có thể lưu vào local storage cũng được). Client sẽ gửi nó lên server mỗi khi thực hiện một request. Server kiểm tra session id này có tồn tại hay không, nếu có thì xác thực thành công, không thì xác thực thất bại.
🥈 Flow hoạt động của Session Authentication
Hình dưới đây là luồng hoạt động của phương pháp xác thực bằng session.
Luồng hoạt động của Session Authentication
-
Client gửi request vào tài nguyên được bảo vệ trên server. Nếu client chưa được xác thực, server sẽ trả lời với một lời nhắc đăng nhập. Client gửi username và password của họ cho server.
-
Server xác minh thông tin xác thực được cung cấp so với cơ sở dữ liệu người dùng. Nếu thông tin xác thực khớp, server tạo ra một Session Id duy nhất và tạo một session tương ứng trong bộ nhớ lưu trữ phía server (ví dụ: ram, database, hoặc file nào đó).
-
Server gửi Session Id cho client dưới dạng cookie, thường là với tiêu đề Set-Cookie.
-
Client lưu trữ cookie.
-
Đối với các yêu cầu tiếp theo, client gửi cookie chứa Session Id lên server.
-
Server kiểm tra Session Id trong cookie so với dữ liệu session được lưu trữ để xác thực người dùng.
-
Nếu được xác nhận, server cấp quyền truy cập vào tài nguyên được yêu cầu. Khi người dùng đăng xuất hoặc sau một khoảng thời gian hết hạn được xác định trước, server làm vô hiệu phiên
🥈 Code mô phỏng Session Authentication
Dươi đây là code mô phỏng session authentication bằng express.js node.js
🥈 Ưu nhược điểm của Session Authentication
🥉Ưu điểm
-
Dễ triển khai, hầu như mấy framework web hiện nay đều giúp bạn thực hiện session authentication một cách cực kỳ dễ dàng chỉ với vài dòng code
-
Bảo mật thông tin người dùng. Như bạn thấy đấy, người dùng chỉ lưu một cái chuỗi ngẫu nhiên (session id) trên máy mình và gửi nó lên server qua mỗi request, nên mấy cái thông khác như username, password, ... không bị lộ ra ngoài
-
Toàn quyền kiểm soát phiên làm việc của người dùng. Vì mọi thứ bạn lưu trữ ở server nên bạn có thể đăng xuất người dùng bất cứ khi nào bạn muốn bằng việc xóa session id của họ trong bộ nhớ lưu trữ phía server.
🥉Nhược điểm
-
Việc toàn quyền kiểm soát vừa là ưu điểm cũng vừa là nhược điểm của session authentication. Vì bạn phải lưu trữ thông tin phiên làm việc của người dùng nên bạn phải có một bộ nhớ lưu trữ phía server. Ví dụ bạn lưu trữ trên RAM thì không thể chia sẻ cho các server khác được (dính DDOS hay restart server lại mất hết), lưu trữ trên database thì lại tốn kém thêm chi phí, bộ nhớ,...
-
Bộ nhớ lưu trữ session sẽ phình to rất nhanh vì mỗi khi có một người dùng đăng nhập thì bạn lại phải lưu trữ một session id mới, cái này phình to nhanh lắm đấy 😂
-
Tốc độ chậm, vì mỗi request đến server, server điều phải kiểm tra trong bộ nhớ lưu trữ xem session id có hợp lệ hay không. Nếu bạn lưu trữ trên database thì tốc độ sẽ chậm hơn nữa.
-
Khó khăn trong việc scale ngang server. Ví dụ khi server lớn lên, bạn phải có nhiều server để chịu tải hơn, thì việc chia sẻ session id giữa các server là một vấn đề khó khăn, kiểu gì bạn cũng phải tìm cái gì đó chung giữa các server như database chung chẳn hạn. Lại database, nếu nó lớn lên lại tìm cách scale database 🥲
Scale ngang là chúng ta mở rộng quy mô hệ thống bằng cách thêm các server mới vào hệ thống, thay vì nâng cấp server hiện tại lên một cấu hình cao hơn.
Scale dọc là chúng ta mở rộng quy mô hệ thống bằng cách nâng cấp server hiện tại lên một cấu hình cao hơn.
🥇 Đón chờ phần kế tiếp
Phần 2 này dài gấp 3 lần phần 1 😂, mong rằng qua bài này mình đã giúp các bạn hiểu rõ hơn về Cookie cũng như Session là gì, quan trọng hơn là cơ chế Session Authentication hoạt động như thế nào.
Phần 3 mình sẽ giới thiệu với các bạn về một cơ chế xác thực người dùng khác, đó là JWT Authentication. Cơ chế này sẽ giúp chúng ta giải quyết được một số nhược điểm của Session Authentication.
Hẹn gặp lại các bạn ở phần 3 nhé 😘
Nguồn bài viết: https://duthanhduoc.com