CSRF là gì? Hiểu bản chất và cách phòng chống từ gốc rễ
Khám phá lỗ hổng bảo mật CSRF (Cross-Site Request Forgery): Bản chất lừa trình duyệt, so sánh với XSS, SameSite cookie và các cơ chế phòng thủ hiện đại.
CSRF là viết tắt của Cross-Site Request Forgery – tạm dịch là “Giả mạo yêu cầu liên trang”. Nghe qua có vẻ mang tính học thuật và phức tạp, nhưng ý tưởng cốt lõi của kiểu tấn công này lại vô cùng đời thường: lừa trình duyệt của bạn thực hiện một hành động ngoài ý muốn mà bạn hoàn toàn không hay biết.
1. Giải thích theo kiểu ELI5 (Dễ hiểu trong 2 phút)#
Hãy tưởng tượng bạn vừa bước vào một câu lạc bộ VIP (ví dụ: tài khoản ngân hàng hoặc mạng xã hội của bạn):
- Khi bạn check-in thành công tại quầy lễ tân, họ đóng cho bạn một con dấu VIP lên tay (đây chính là Session Cookie lưu trong trình duyệt).
- Con dấu này chứng minh bạn là hội viên hợp lệ mà không cần phải xuất trình lại căn cước mỗi khi gọi đồ uống.
- Sau đó, bạn bước ra ngoài và vô tình ghé qua một quầy hàng lạ ven đường (
evil.com). - Người bán hàng tinh quái ở đó viết sẵn một tờ phiếu: “Hãy chuyển 5 triệu đồng từ tài khoản của tôi cho quầy hàng này”, rồi họ bí mật cầm tay bạn (có sẵn con dấu VIP) đập lên tờ phiếu và gửi vào câu lạc bộ VIP.
- Nhân viên câu lạc bộ nhìn thấy con dấu VIP chuẩn xác nghĩ rằng chính bạn yêu cầu giao dịch đó tiền bị trừ ngay lập tức!
Bạn không hề có ý định chuyển tiền, nhưng trình duyệt của bạn đã bị “mượn tay” để đóng dấu xác nhận. Đó chính là CSRF.
flowchart LR
subgraph AttackerSite["Trang web độc hại (evil.com)"]
MaliciousScript["Script ẩn / Form tự động submit\nPOST https://bank.com/transfer"]
end
subgraph UserBrowser["Trình duyệt người dùng (Client)"]
Victim["Người dùng đã đăng nhập\n(Đang giữ Cookie hợp lệ)"]
end
subgraph BankServer["Máy chủ ngân hàng (bank.com)"]
Backend["Xác thực Cookie thành công\n-> Thực hiện chuyển tiền!"]
end
Victim -->|1. Truy cập trang web lạ| MaliciousScript
MaliciousScript -->|2. Kích hoạt request ngầm| Victim
Victim -->|3. Tự động đính kèm Cookie + Gửi request| Backend
2. Hai điều kiện cốt lõi tạo nên lỗ hổng CSRF#
CSRF xảy ra không phải do hacker giải mã được mật khẩu hay đánh cắp được token của bạn. Nó khai thác sự tin tưởng mù quáng giữa trình duyệt và máy chủ:
- Trình duyệt tự động đính kèm Cookie theo Domain: Khi trình duyệt gửi request tới bất kỳ domain nào (ví dụ
bank.com), nó sẽ tự động gom tất cả Cookie thuộc về domain đó để gửi kèm theo (gọi là cơ chế Ambient Credentials). - Server chỉ kiểm tra “Đã đăng nhập chưa?”: Server thấy Cookie hợp lệ là lập tức thực thi hành động, mà quên mất câu hỏi quan trọng: “Request này có thực sự xuất phát từ giao diện website của mình hay đến từ một trang web lạ?”.
3. Phân biệt rõ ràng: CSRF khác XSS như thế nào?#
Rất nhiều lập trình viên nhầm lẫn giữa CSRF và XSS (Cross-Site Scripting). Điểm khác biệt mấu chốt nằm ở:
| Tiêu chí | XSS (Cross-Site Scripting) | CSRF (Cross-Site Request Forgery) |
|---|---|---|
| Bản chất | Chèn và thực thi mã JavaScript độc hại trên trang web mục tiêu | Lừa trình duyệt gửi request trái phép từ một trang web khác |
| Đánh cắp Cookie? | Có thể đọc và đánh cắp Cookie nếu không có cờ HttpOnly | Không đọc hay đánh cắp được Cookie (chỉ mượn danh nghĩa) |
| Cơ chế khai thác | Khai thác lỗ hổng không validate/escape dữ liệu đầu vào | Khai thác cơ chế tự động gửi Cookie của trình duyệt |
[!NOTE] XSS đánh cắp danh tính của bạn; còn CSRF mượn quyền hạn của bạn mà không cần biết danh tính hay mật khẩu của bạn là gì.
4. Kịch bản tấn công thực tế#
Kịch bản 1: Tấn công qua thẻ HTML <img> (GET Request)#
Nếu server cho phép thay đổi trạng thái nhạy cảm qua phương thức GET (vi phạm nguyên tắc RESTful):
<!-- Trình duyệt tự động tải ảnh ngầm và gửi kèm cookie phiên của nạn nhân -->
<img src="https://bank.com/api/transfer?to=hacker&amount=10000000" width="0" height="0" />htmlKịch bản 2: Tấn công qua Form ẩn tự động gửi (POST Request)#
Kể cả khi bạn dùng phương thức POST, kẻ tấn công vẫn có thể tạo form ẩn và dùng JavaScript tự động submit ngay khi người dùng vừa mở trang:
<form id="csrfForm" action="https://social.com/api/delete-account" method="POST">
<input type="hidden" name="confirm" value="true" />
</form>
<script>
// Tự động gửi request ngay khi vừa vào trang
document.getElementById('csrfForm').submit();
</script>html5. Các giải pháp phòng chống CSRF toàn diện#
1. CSRF Token (Synchronizer Token Pattern)#
Đây là phương pháp kinh điển và phổ biến nhất:
- Khi render form, Server tạo ra một chuỗi ngẫu nhiên, bí mật và duy nhất gắn liền với phiên của người dùng.
- Khi người dùng gửi form lên, client phải gửi kèm token này trong form body hoặc header.
- Vì website độc hại bị chặn bởi chính sách Same-Origin Policy (SOP), kẻ tấn công không thể đọc trộm CSRF Token từ trang của bạn, do đó request giả mạo sẽ bị từ chối.
<form action="/transfer" method="POST">
<!-- CSRF Token bí mật được server chèn vào -->
<input type="hidden" name="_csrf" value="d9a8f7b2c3e4..." />
<input type="number" name="amount" />
<button type="submit">Chuyển tiền</button>
</form>html2. Thuộc tính SameSite của Cookie (Lá chắn hiện đại)#
Bạn có thể cấu hình thuộc tính SameSite ngay trên Header Set-Cookie:
SameSite=Strict: Trình duyệt tuyệt đối không bao giờ gửi cookie nếu request xuất phát từ trang web khác. An toàn tuyệt đối nhưng người dùng bấm link từ email/chat sẽ phải đăng nhập lại.SameSite=Lax(Mặc định trên các trình duyệt hiện đại): Không gửi cookie cho các request cross-site dạng POST, form ẩn hay thẻ<img>. Chỉ cho phép gửi cookie với các điều hướng top-level an toàn (như bấm link thẻ<a>).SameSite=None: Cho phép gửi cookie trong mọi bối cảnh cross-site (bắt buộc phải đi kèm cờSecure).
Set-Cookie: sessionId=xyz123; Path=/; Secure; HttpOnly; SameSite=Laxhttp[!TIP] Chỉ cần thiết lập
SameSite=LaxhoặcSameSite=Strictkết hợpSecurevàHttpOnly, bạn đã chặn đứng được phần lớn các cuộc tấn công CSRF cơ bản.
3. Double Submit Cookie (Dành cho SPA & Stateless APIs)#
Nếu server không lưu trữ session (Stateless):
- Server gửi một CSRF token ngẫu nhiên vào một cookie thông thường (ví dụ:
XSRF-TOKEN). - Ứng dụng Frontend (React, Vue, Svelte) đọc giá trị cookie này bằng JavaScript và gắn nó vào Request Header tùy biến (ví dụ:
X-XSRF-TOKEN). - Server nhận request và so sánh giá trị ở Header với giá trị trong Cookie. Nếu khớp nhau Request hợp lệ.
4. Kiểm tra Header Origin, Referer và Sec-Fetch-Site#
Trên máy chủ API, bạn có thể kiểm tra xem nguồn gốc của request có khớp với domain của bạn hay không:
export function verifyOrigin(req, res, next) {
const origin = req.headers['origin'] || req.headers['referer'];
const allowedHost = 'https://mybank.com';
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
if (!origin || !origin.startsWith(allowedHost)) {
return res.status(403).json({ error: 'CSRF Protection: Invalid Request Origin' });
}
}
next();
}javascript6. CSRF trong kỷ nguyên SPA và JWT#
Trong các kiến trúc Single Page Application (SPA) hiện đại:
- Nếu bạn lưu JWT Token trong bộ nhớ RAM hoặc gắn thủ công vào header
Authorization: Bearer <token>, CSRF không còn là mối đe dọa, bởi vì trình duyệt không bao giờ tự động gắn headerAuthorizationkhi có request cross-site. - Tuy nhiên, nếu bạn lưu JWT trong Cookie để tiện quản lý phiên, bạn vẫn hoàn toàn đối mặt với rủi ro CSRF như mô hình Session truyền thống.
7. Tổng kết#
CSRF là minh chứng rõ ràng cho việc: sự tin tưởng mù quáng giữa các hệ thống luôn phải trả giá bằng lỗ hổng bảo mật.
Để bảo vệ ứng dụng của bạn:
- Luôn sử dụng phương thức
POST/PUT/DELETEcho các hành động thay đổi dữ liệu (state-changing). - Thiết lập cờ
SameSite=LaxhoặcSameSite=Stricttrên mọi authentication cookie. - Sử dụng CSRF Token hoặc Custom Request Headers cho các form nhạy cảm.
- Xác thực tiêu đề
OriginvàSec-Fetch-Siteở tầng backend middleware.