Browser security model: same-origin, CORS, CSRF, XSS, CSP, cookie
Tiếp đi: bảo mật phía browser. Mình hay lẫn CORS, CSRF, XSS với nhau. Chúng liên quan gì nhau trong shop-app?
Cấu hình CORS/CSRF trong Spring Security đã có ở bài 4.4.9. Bài này dựng bức tranh chung: cả bốn thứ đều xoay quanh một luật của browser.
1. Bài toán
https://shop.example (web React)
https://api.shop.example (REST API, session cookie hoặc token)
https://evil.example (trang của attacker)
2. Vấn đề: browser tự gửi cookie, và chạy mọi script trên trang
- User đang đăng nhập shop, mở evil.example. Trang evil gửi form tới api.shop.example: browser tự đính kèm cookie → CSRF.
- Attacker để lại review chứa
<script>. Ai xem trang product cũng chạy script đó trên origin shop → XSS, đọc được mọi thứ.
3. Giải pháp: hiểu từng lớp
Same-origin policy: origin = scheme + host + port. Script ở origin A không đọc được response từ origin B. Lưu ý: SOP chặn đọc, không chặn gửi.
| Cơ chế | Chống gì | Ai cấu hình |
|---|---|---|
| CORS | Không chống tấn công; là cách nới SOP có kiểm soát | API server |
| CSRF token / SameSite | Trang khác lợi dụng cookie của user | Server + cookie |
| Output encoding | XSS | Code render |
| CSP | Giảm thiệt hại XSS | Response header |
| HttpOnly / Secure | JS đọc cookie / gửi qua HTTP thường | Cookie |
XSS ba loại:
- Stored: lưu vào DB (review, tên shop), hiển thị cho người khác.
- Reflected: nằm trong URL, server phản chiếu lại (
/search?q=<script>). - DOM-based: JS phía client tự chèn (
innerHTML = location.hash).
4. Code từng bước
Bước 1: CORS chỉ cho origin của mình
Access-Control-Allow-Origin: https://shop.example (không dùng * khi có credentials)
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization, Idempotency-Key
Access-Control-Max-Age: 600
Vary: Origin
Bước 2: cookie
Set-Cookie: SESSION=abc; Path=/; Secure; HttpOnly; SameSite=Lax
Set-Cookie: XSRF-TOKEN=k9; Path=/; Secure; SameSite=Lax (JS đọc được để gửi header)
Bước 3: Code Java, security headers (CORS/CSRF chi tiết xem 4.4.9):
http.headers(h -> h
.contentSecurityPolicy(csp -> csp.policyDirectives(
"default-src 'self'; script-src 'self'; object-src 'none'; " +
"frame-ancestors 'none'; base-uri 'self'; img-src 'self' https://cdn.shop.example"))
.httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000).includeSubDomains(true))
.contentTypeOptions(Customizer.withDefaults()) // X-Content-Type-Options: nosniff
.referrerPolicy(r -> r.policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)));
Chống XSS gốc: render bằng template/React tự escape, không dùng dangerouslySetInnerHTML/th:utext với dữ liệu user; nếu cần HTML (mô tả product) thì sanitize bằng allowlist (OWASP Java HTML Sanitizer).
Bước 4: test
# preflight
curl -i -X OPTIONS https://api.shop.example/v1/orders \
-H "Origin: https://evil.example" -H "Access-Control-Request-Method: POST"
# -> không có Access-Control-Allow-Origin => browser chặn
curl -sI https://shop.example | grep -i content-security-policy
5. Cách hoạt động: CORS preflight
Browser (shop.example) api.shop.example
│ fetch POST JSON + Authorization
│ ── OPTIONS /orders ─────────────────────> │ Origin, Access-Control-Request-*
│ <── 204 Allow-Origin: shop.example ────── │
│ ── POST /orders (thật) ─────────────────> │
│ <── 201 + Allow-Origin ────────────────── │ JS được đọc response
Request "simple" (GET, form POST urlencoded) KHÔNG preflight:
vẫn tới server và vẫn chạy! CORS chỉ chặn JS đọc kết quả.
=> CORS không chống CSRF.
6. So sánh: SameSite
| SameSite | Cookie gửi khi request từ site khác | Ghi chú |
|---|---|---|
| Strict | Không bao giờ | Bấm link từ email vào cũng mất session |
| Lax (mặc định Chrome) | Chỉ điều hướng top-level GET | Chặn được phần lớn CSRF qua POST |
| None | Luôn (bắt buộc Secure) | Cần cho iframe/3rd-party, phải có CSRF token |
Chú ý: "site" = eTLD+1. shop.example và api.shop.example là same-site nhưng khác origin.
7. Khi nào nên dùng / không nên dùng
- Web dùng session cookie: CSRF token + SameSite=Lax + HttpOnly + Secure.
- API cho mobile/partner dùng
Authorization: Bearer: không có cookie tự gửi, CSRF không áp dụng; CORS không cần (không phải browser). - CSP: bắt đầu bằng
Content-Security-Policy-Report-Onlyđể xem vi phạm, rồi mới enforce. - Không dùng
Allow-Originphản chiếu mọi Origin kèm credentials: tương đương tắt SOP.
Nếu đã có CSP script-src 'self' thì script inline của attacker không chạy được. Vậy mình bỏ bớt escape cho nhanh được không?
Không nên. CSP là lớp phòng thủ thứ hai, không phải lớp chính:
- Attacker vẫn chèn được HTML không có script: form giả đăng nhập, link lừa đảo, CSS đánh cắp dữ liệu.
- Nếu có endpoint JSONP hoặc file JS do user upload trên cùng origin,
'self'vẫn bị lợi dụng. - Dev thêm
'unsafe-inline'để "sửa nhanh" một thư viện là CSP mất tác dụng. - Browser cũ hoặc trang thiếu header.
Lớp 1: escape theo ngữ cảnh (HTML, attribute, JS, URL) <- chặn từ gốc
Lớp 2: CSP (nonce/hash, không unsafe-inline) <- giảm hậu quả
Lớp 3: HttpOnly cookie <- XSS không đọc được session
Web của shop-app đang lưu access token ở localStorage. Nếu bị XSS thì mất gì, và chuyển sang cookie HttpOnly thì hết lo không?
Bạn hiểu gần đúng, chỉnh một điểm: HttpOnly giảm thiệt hại, không xóa được XSS.
| localStorage | Cookie HttpOnly | |
|---|---|---|
| XSS đọc token mang đi | Có, dùng ở máy attacker tới khi hết hạn | Không |
| XSS gọi API ngay trên trang | Có | Vẫn có (browser tự gửi cookie) |
| CSRF | Không bị | Phải chống |
Mô hình phổ biến cho SPA hiện nay là BFF (backend-for-frontend): token nằm ở server, browser chỉ giữ session cookie HttpOnly + SameSite + CSRF token. Access token ngắn hạn giới hạn thiệt hại nếu lộ.
Phỏng vấn hỏi phân biệt CORS, CSRF, XSS và CSP thì trả lời sao?
Q1. CORS có chống được CSRF không?
🇻🇳 Tiếng Việt:
Không. Same-origin policy chặn JS đọc response khác origin, CORS là cách server nới luật đó cho origin tin cậy. Request đơn giản như form POST vẫn tới server và vẫn chạy, chỉ là JS không đọc được kết quả. Chống CSRF cần CSRF token và cookie SameSite.
🇬🇧 English:
No. The same-origin policy stops scripts from reading cross-origin responses, and CORS is how a server relaxes that rule for trusted origins. Simple requests like a form POST still reach the server and execute; only the response is hidden from the script. CSRF needs anti-CSRF tokens and SameSite cookies.
Q2. XSS có mấy loại, phòng chống thế nào?
🇻🇳 Tiếng Việt:
Stored, reflected và DOM-based. Phòng chính là escape output theo ngữ cảnh và sanitize HTML bằng allowlist. Thêm CSP không cho inline script, dùng nonce, và cookie HttpOnly để giảm hậu quả.
🇬🇧 English:
Stored, reflected and DOM-based. The primary defense is context-aware output encoding and allowlist-based HTML sanitizing. Then a CSP without inline scripts, using nonces, plus HttpOnly cookies to limit the impact.
Q3. Các thuộc tính cookie quan trọng?
🇻🇳 Tiếng Việt:
Securechỉ gửi qua HTTPS,HttpOnlyđể JS không đọc được,SameSite=LaxhoặcStrictđể không gửi trong request cross-site, giảm CSRF.SameSite=Nonephải kèm Secure và vẫn cần CSRF token.
🇬🇧 English:
Securesends the cookie only over HTTPS,HttpOnlyhides it from JavaScript, andSameSite=LaxorStrictkeeps it out of cross-site requests, which mitigates CSRF.SameSite=Nonerequires Secure and still needs a CSRF token.
Cách nhớ ngắn gọn
- SOP chặn đọc, không chặn gửi. CORS nới SOP, không phải khiên.
- CSRF = lợi dụng cookie tự gửi → token + SameSite.
- XSS = chạy script trên origin mình → escape trước, CSP sau.
- Cookie: Secure + HttpOnly + SameSite.
Tự kiểm tra
Khi nào browser gửi preflight?
Request không "simple": method khác GET/HEAD/POST, có header tùy chỉnh (Authorization), hoặc Content-Type như application/json.
Vì sao không dùng Allow-Origin: * cùng credentials?
Browser cấm; còn phản chiếu mọi Origin kèm credentials thì bất kỳ site nào cũng đọc được dữ liệu user.
API chỉ dùng Bearer token trong header có cần CSRF token không?
Không, browser không tự đính token. Nhưng cần chống XSS để token không bị đánh cắp.
Stored XSS khác reflected XSS chỗ nào?
Stored lưu vào DB và ảnh hưởng mọi người xem; reflected nằm trong request (URL) và phải lừa nạn nhân bấm link.
Triển khai CSP an toàn thế nào?
Bắt đầu với Report-Only, thu báo cáo vi phạm, sửa, rồi enforce. Tránh unsafe-inline.