Browser security model: same-origin, CORS, CSRF, XSS, CSP, cookie

4 câu hỏi~25 phútTừ khóa: same-origin policy, origin, preflight, CSRF, SameSite, HttpOnly, Secure, stored XSS, reflected XSS, DOM XSS, CSP, nonce

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

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
CORSKhông chống tấn công; là cách nới SOP có kiểm soátAPI server
CSRF token / SameSiteTrang khác lợi dụng cookie của userServer + cookie
Output encodingXSSCode render
CSPGiảm thiệt hại XSSResponse header
HttpOnly / SecureJS đọc cookie / gửi qua HTTP thườngCookie

XSS ba loại:

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

SameSiteCookie gửi khi request từ site khácGhi chú
StrictKhô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 GETChặn được phần lớn CSRF qua POST
NoneLuô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

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:

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.

localStorageCookie HttpOnly
XSS đọc token mang điCó, dùng ở máy attacker tới khi hết hạnKhông
XSS gọi API ngay trên trangCóVẫn có (browser tự gửi cookie)
CSRFKhô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:

Secure chỉ gửi qua HTTPS, HttpOnly để JS không đọc được, SameSite=Lax hoặc Strict để không gửi trong request cross-site, giảm CSRF. SameSite=None phải kèm Secure và vẫn cần CSRF token.

🇬🇧 English:

Secure sends the cookie only over HTTPS, HttpOnly hides it from JavaScript, and SameSite=Lax or Strict keeps it out of cross-site requests, which mitigates CSRF. SameSite=None requires Secure and still needs a CSRF token.

Cách nhớ ngắn gọn


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.