jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

How Browsers Work · Part 11 — The Security Model

The browser's defenses: the same-origin policy, CORS, Content Security Policy, cookie protections (SameSite/HttpOnly), the sandbox and site isolation, and how they combine to contain hostile code.

Mỗi trang bạn mở chạy code không đáng tin — app của bạn, quảng cáo bên thứ ba, script analytics, widget nhúng — trên cùng máy với tab email, ngân hàng, và file cục bộ. Việc của trình duyệt không phải tin bạn; mà là giam giữ code đó để một origin bị chiếm quyền không đọc được dữ liệu site khác hay thoát ra HĐH.

Phần 1–10 đã đi qua kiến trúc, render, JavaScript, bộ nhớ, mạng, và lưu trữ — tất cả giả định một thế giới đối địch. Phần này là mô hình bảo mật: các lớp phòng thủ biến “trang web ngẫu nhiên trên laptop” từ thảm họa thành thứ có thể sống sót.

Defense in depth (simplified)
─────────────────────────────────────────────────────────────
  HTTPS / mixed-content blocking     → transport integrity
  Sandbox + Site Isolation (Part 1)  → OS/process boundary
  Same-Origin Policy (SOP)           → default: no cross-origin reads
  CORS                               → server opt-in to cross-origin reads
  CSP                                → restrict what code/styles can run/load
  Cookie flags (HttpOnly/SameSite)   → limit token theft & CSRF
  iframe sandbox / COOP+COEP         → isolate documents & enable SAB
─────────────────────────────────────────────────────────────

1. Mối đe dọa: bạn chạy code đối địch trên máy cục bộ

Web app xử lý input không đáng tin bằng JS/Wasm/GPU API trong sandbox nhưng vẫn có capability mạnh do browser cấp. Kẻ tấn công nhắm tới:

  • Đánh cắp dữ liệu liên site — đọc cookie, localStorage, hoặc DOM của tab khác.
  • CSRF — lừa trình duyệt gửi request đã xác thực mà user không hề muốn.
  • XSS — tiêm script chạy với đặc quyền của site bạn.
  • Lộ bộ nhớ — lỗi kiểu Spectre cho code một origin đọc bộ nhớ origin khác trong cùng tiến trình.
  • Thoát sandbox — phá renderer để chạm filesystem hay mạng trực tiếp.

Không cơ chế đơn lẻ nào chặn hết. Trình duyệt xếp chồng phòng thủ nhiều lớp: sandbox tiến trình (Phần 1), ranh giới origin, header opt-in từ server, và chính sách do developer kiểm soát.


2. Same-Origin Policy (SOP): bức tường mặc định

Một origin là bộ scheme + host + port:

https://app.example.com:443/page   → origin: https://app.example.com
http://app.example.com/page        → DIFFERENT (scheme)
https://api.example.com/data         → DIFFERENT (host)
https://app.example.com:8443/page    → DIFFERENT (port, if non-default)

Hai URL cùng origin chỉ khi cả ba khớp. SOP là quy tắc nền của trình duyệt: JavaScript ở origin A không được đọc dữ liệu từ origin B trừ khi có ngoại lệ rõ ràng.

SOP chặn gì (ví dụ):

ActionBị chặn?
Read another origin’s DOM via iframe.contentDocument
Read another origin’s localStorage
fetch('https://other.com/api') and read the response bodyCó (trừ khi CORS cho phép)
Load <img src="https://other.com/secret.png">Không — nhúng được phép; đọc pixel cross-origin bị hạn chế
Submit a <form> POST to another originKhông — điều hướng/gửi form được phép

SOP bất đối xứng: bạn thường gửi dữ liệu cross-origin (form, <img>, <script src>), nhưng không đọc phản hồi từ JS trừ khi bên kia hợp tác. Bất đối xứng đó là lý do CSRF tồn tại và CORS cần cho API.

Khi SOP chặn đọc, script không được access body/DOM trực tiếp và DevTools thường báo security error. Tuy vậy, timing, load/error và nhiều side channel vẫn tạo lớp XS-Leaks riêng; SOP là nền bắt buộc, không phải chứng minh rằng cross-origin state không thể rò bằng bất kỳ tín hiệu nào.


3. CORS: server opt-in cho phép đọc cross-origin

Cross-Origin Resource Sharing (CORS) là protocol để server nói: “Tôi cho phép https://app.example.com đọc phản hồi này bằng JavaScript”. CORS nới lỏng hạn chế read của SOP/Fetch cho caller được chỉ định; nó không phải cơ chế authentication hay firewall cho server.

Request đơn giản vs preflight

Với request cross-origin “đơn giản” (method, header, content-type nhất định), trình duyệt gửi request trực tiếp rồi kiểm tra header phản hồi sau. Với phần còn lại, trình duyệt trước hết gửi OPTIONS preflight xin phép:

Browser (https://app.example.com)

    ├─ OPTIONS /api/data          ← preflight (if needed)
    │     Origin: https://app.example.com
    │     Access-Control-Request-Method: POST
    │     Access-Control-Request-Headers: Content-Type, Authorization

    ◄─┤ 204 No Content
    │     Access-Control-Allow-Origin: https://app.example.com
    │     Access-Control-Allow-Methods: POST, GET
    │     Access-Control-Allow-Headers: Content-Type, Authorization

    ├─ POST /api/data             ← actual request
    │     Origin: https://app.example.com

    ◄─┤ 200 OK
          Access-Control-Allow-Origin: https://app.example.com
          (body visible to JS ✓)

Header phản hồi chính:

HeaderVai trò
Access-Control-Allow-OriginOrigin nào được đọc phản hồi (* hoặc origin cụ thể)
Access-Control-Allow-CredentialsCho phép expose response khi credentials flag là true (true; Allow-Origin không được là *)
Access-Control-Allow-MethodsMethod được phép trong preflight
Access-Control-Allow-HeadersHeader request được phép trong preflight
Access-Control-Max-AgeThời gian cache kết quả preflight

Phía client, fetch(url, { credentials: 'include' }) yêu cầu gửi/nhận credentials theo cookie/SameSite/third-party policy. Với simple request, cookie có thể đã được gửi trước khi browser thấy header phản hồi; nếu response thiếu Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin cụ thể, JS chỉ nhận network error/không được đọc body. Preflight tự nó không mang credentials theo Fetch spec; server opt-in rồi browser mới gửi actual credentialed request. Xem Fetch/CORS credential flow.

Hiểu lầm CORS phổ biến

  • “CORS bảo vệ API của tôi” — Không. CORS được enforce trong trình duyệt để bảo vệ user khỏi site độc đọc phản hồi. curl bỏ qua CORS hoàn toàn.
  • “Lỗi CORS nghĩa là request thất bại” — Thường server đã nhận và xử lý request; trình duyệt chỉ giấu phản hồi khỏi JS.
  • “Thêm Access-Control-Allow-Origin: * sửa mọi thứ” — Không chạy với credentials, và cho phép mọi site đọc phản hồi công khai trong trình duyệt — có thể ổn với API public nhưng không với dữ liệu riêng.

4. Content Security Policy (CSP): thu hẹp bề mặt tấn công

Nếu XSS tiêm <script>alert(document.cookie)</script>, SOP không cứu được — script chạy với tư cách origin của bạn. Content Security Policy bảo trình duyệt nguồn script, style, ảnh, kết nối, và frame nào được phép.

Gửi qua HTTP header là đầy đủ nhất; <meta http-equiv="Content-Security-Policy"> không hỗ trợ mọi directive như frame-ancestors và không có Report-Only:

Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{RANDOM_PER_RESPONSE}' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  object-src 'none';

Để cho phép inline script mà không dùng 'unsafe-inline', dùng nonce (ngẫu nhiên mỗi phản hồi, gắn trên từng <script>) hoặc hash nội dung script chính xác:

<!-- Header for this response contains: script-src 'nonce-r4nd0m...' -->
<script nonce="r4nd0m...">
  initApp(); // allowed — nonce matches
</script>
<script>
  evil(); // blocked — no nonce
</script>

Nonce phải khó đoán và mới cho mỗi response; đừng lấy ID request tuần tự hay reuse hằng số. Policy dựa nonce/hash + 'strict-dynamic' chống XSS tốt hơn host allowlist dễ bị bypass; xem strict CSP guide.

Chế độ Report-Only (Content-Security-Policy-Report-Only) gửi report mà không chặn — hữu ích để quan sát trước khi enforce. report-uri có hỗ trợ legacy rộng hơn nhưng đã deprecated; Reporting API/report-to support khác nhau, nên kiểm tra browser target và endpoint thực tế.

CSP không thay thế contextual escaping/sanitization và Trusted Types ở DOM sink phù hợp; nó là defense-in-depth. Ưu tiên strict CSP, tránh 'unsafe-inline'/'unsafe-eval' nếu không có lý do và threat review rõ.


Cookie (Phần 10) mang token phiên — mục tiêu hàng đầu cho đánh cắp và giả mạo. Ba cờ quan trọng nhất:

FlagTác dụng
HttpOnlyJS không đọc cookie (document.cookie) — giảm đánh cắp token qua XSS
SecureCookie chỉ gửi qua HTTPS
SameSiteKiểm soát gửi cross-site: Strict; Lax cho top-level navigation dùng safe method; None cho phép cross-site nhưng bắt buộc Secure và vẫn chịu browser policy
Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax

Cookie không ghi SameSite thường được browser xử lý gần như Lax, nhưng một số user agent có “Lax-allowing-unsafe” ngắn cho cookie mới đặt. Lax chặn cookie trên phần lớn cross-site subrequest/POST, nhưng vẫn cho safe top-level navigation; endpoint đổi state bằng GET, attack cùng site và browser cũ vẫn là lỗ hổng. Vì vậy SameSite là defense-in-depth, không thay CSRF token hoặc kiểm tra Origin/Fetch Metadata cho action nhạy cảm.

HttpOnly chặn đọc chuỗi cookie từ JS; XSS cùng origin vẫn có thể gọi API với cookie được browser tự gắn và đọc response. Dùng prefix __Host- khi phù hợp (Secure; Path=/; không Domain) để giảm cookie shadowing, và vẫn cần ngăn XSS, CSRF cùng authorization phía server.


6. Sandbox, Site Isolation, và ranh giới tiến trình

Nhắc Phần 1: renderer bị sandbox và không có quyền filesystem/socket tổng quát. Capability hẹp đi qua IPC tới browser/network/storage service; broker/service phải validate renderer message vì process gửi được coi là không đáng tin.

Site Isolation đi xa hơn: với full isolation (mặc định trên Chrome desktop), tài liệu từ site khác nhau (scheme + registrable domain) dùng renderer process riêng, kể cả iframe qua OOPIF. Mức isolation có thể khác trên thiết bị hạn chế tài nguyên. Đây là defense-in-depth trước Spectre/renderer compromise: browser cố không gửi response cross-site nhạy cảm vào process không cần chúng.

Before Site Isolation          After Site Isolation
─────────────────────          ────────────────────
Tab: bank.com                  Tab: bank.com  → Renderer A
  └─ iframe: evil.com              └─ iframe: evil.com → Renderer B (OOPIF)
     (same process ✗)                 (separate process ✓)

Thuộc tính sandbox trên <iframe> hạn chế thêm việc nội dung nhúng làm — không form, không script, không điều hướng top, v.v., tùy token bạn bỏ qua:

<!-- Heavily restricted embed -->
<iframe
  src="https://untrusted.example/widget"
  sandbox="allow-scripts allow-same-origin"
></iframe>

Nếu functionality cho phép, bỏ allow-same-origin để frame nhận opaque origin; bỏ allow-scripts để chặn script. Coi sandbox như danh sách capability — chỉ cấp token embed thật sự cần. Đặc biệt, với nội dung cùng origin với parent, kết hợp cả allow-scriptsallow-same-origin bị khuyến cáo tránh vì frame có thể gỡ attribute sandbox trên element rồi reload, làm mất bảo vệ.


7. Cô lập cross-origin, HTTPS, và mixed content

Một số API mạnh (SharedArrayBuffer, timer độ phân giải cao, performance.measureUserAgentSpecificMemory nơi hỗ trợ) cần cross-origin isolation. Cấu hình phổ biến:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

COOP tách browsing-context group theo opener policy. Với COEP: require-corp, subresource cross-origin ở no-cors mode phải cho phép qua CORP; resource tải ở CORS mode phải qua kiểm tra CORS. COEP: credentialless là lựa chọn khác cho no-cors resource, gửi chúng không kèm credentials. Kiểm tra crossOriginIsolated === true; Permissions Policy vẫn có thể chặn capability. Xem COEP semantics.

Ở tầng vận chuyển, HTTPS cung cấp mã hóa và toàn vẹn cho connection. Với mixed content, browser hiện tự nâng nhiều request ảnh/audio/video từ HTTP lên HTTPS; loại còn lại như script, stylesheet, iframe, fetch() và font bị chặn. URL IP có rule chặt hơn. upgrade-insecure-requests giúp migrate URL nội bộ, còn HSTS buộc navigation/request tương lai tới host dùng HTTPS sau khi policy được học/preload.

Lỗi bảo mật trong DevTools thường truy về stack này: SOP chặn đọc, thiếu header CORS, CSP chặn inline script, hoặc mixed content bị nâng cấp/chặn. Ánh triệu chứng vào lớp trước khi nới chính sách một cách mù quáng.


8. Phòng thủ nhiều lớp: các lớp kết hợp thế nào

Defense-in-depth không phải một chuỗi tuyến tính cho mọi attack. Mỗi threat đi qua lớp khác:

HTML injection / XSS
  → contextual escaping + sanitization + Trusted Types + strict CSP
  → HttpOnly hides cookie string, but executed same-origin JS can still act as user

Cross-site request (CSRF)
  → SameSite + CSRF token + Origin/Fetch-Metadata checks + safe HTTP methods

Cross-origin read/embed
  → SOP/Fetch + CORS/CORP + frame-ancestors / iframe sandbox

Spectre or renderer compromise
  → Site Isolation + response filtering → renderer OS sandbox → IPC validation

Frontend/backend cùng kiểm soát output encoding, CSP, cookie, CORS, CSRF và embed policy; browser enforce policy và kiến trúc process. Lưu ý CSP không chặn gì nếu site không gửi policy. Với iframe có sandbox, capability bị chặn trừ token được cấp; với cross-origin read, SOP/Fetch chặn trừ protocol như CORS cho phép.


Tóm tắt

  • Trình duyệt chạy code không đáng tin cạnh dữ liệu nhạy cảm; bảo mật là giam giữ, không phải tin tưởng.
  • Same-Origin Policy là mặc định: không đọc cross-origin từ JS; nhúng và gửi form thường vẫn được phép.
  • CORS cho server opt-in đọc cross-origin qua header Access-Control-*; request đơn giản vs preflight; credentials cần ghép cặp rõ — CORS nới SOP, không bảo vệ server khỏi client ngoài trình duyệt.
  • CSP giảm XSS khi dùng policy strict dựa nonce/hash; nonce phải random mỗi response. Report-Only giúp rollout nhưng không enforce.
  • Cookie HttpOnly, Secure, SameSite__Host- giảm một số đường token theft/CSRF; XSS cùng origin vẫn có thể thực hiện request đã xác thực.
  • Sandbox + Site Isolation (Phần 1) cùng iframe sandbox, COOP/COEP, và quy tắc HTTPS/mixed-content tạo phòng thủ nhiều lớp chống lộ bộ nhớ và thoát sandbox.

Phần tiếp theo: Capstone 1 — gom nền tảng Phần 1–11 thành quy trình đọc Performance trace và ánh triệu chứng vào nguyên nhân gốc. Phần 13–22 tiếp tục các lớp internals chuyên sâu.