jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Web Security for Frontend Devs · Part 8 — Secure Headers & HTTPS/TLS

HTTPS/TLS, HSTS, mixed content, the production security-header stack (CSP, COOP/COEP, Permissions-Policy, SRI), static-host `_headers` examples, and audit tooling — with exercises.

Phần 8/10 trong series Web Security for Frontend Devs. Trước: Tiếp:

Đây là Phần 8 của series 10 bài về những kiến thức bảo mật web mà mọi frontend dev nên biết — và chủ động phòng tránh. Mỗi phần giải thích một mối đe dọa thật, cho xem code lỗ hổng, rồi cách sửa, và kết thúc bằng bài tập.

Phần 7 khóa ai được nhúng UI bằng frame-ancestorsX-Frame-Options. Phần này là nền transport và header: HTTPS mọi nơi, HSTS, phần còn của bộ header bảo mật, và SRI để CDN bị xâm không đổi script lặng lẽ. Phần lớn là header phản hồi HTTP set tại host, CDN, hoặc reverse proxy — không chỉ gắn trong React.


HTTPS và TLS: sàn, không phải tính năng

TLS (người ta vẫn gọi “SSL”) cho ba tính chất trên đường truyền giữa trình duyệt và server:

  1. Bảo mật nội dung — ciphertext trên mạng; kẻ nghe lén không đọc được body hay cookie.
  2. Toàn vẹn — sửa đổi bị phát hiện; attacker trên đường không thay JS của bạn bằng malware lặng lẽ.
  3. Xác thực — chứng chỉ chứng minh bạn tới đúng host thật (trừ cert sai cấp và lỗi người dùng).

Không HTTPS, ai đó trên cùng Wi‑Fi, đường ISP, hay router bị xâm có thể đọc và sửa traffic — kể cả chèn <script> vào HTML trên đường. HTTP thuần đã lỗi thời cho site có đăng nhập, thanh toán, PII, hay admin; coi http:// chỉ là đích redirect, không phải URL app chính thức.

Browser TLS 🔒 response headers · Strict-Transport-Security· Content-Security-Policy· X-Content-Type-Options· Referrer-Policy· X-Frame-Options Server HTTPS everywhere + headers set the browser's security defaults
TLS encrypts the channel; response headers (HSTS, CSP, X-Content-Type-Options, …) set the browser's security defaults for every navigation

Frontend vẫn quan trọng: dùng URL tương đối hoặc https:// trong markup, bật HTTPS tự động trên static host, không ship link asset http:// trên production.


HSTS: ép HTTPS sau lần ghé đầu

Lần request đầu tới site vẫn có thể là http:// nếu user gõ hostname, bookmark cũ, hay link downgrade. HSTS bảo trình duyệt: với host này (và tùy chọn subdomain), chỉ dùng HTTPS trong max-age giây tới.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
PieceVai trò
max-ageThời gian trình duyệt nhớ “chỉ HTTPS”
includeSubDomainsÁp cho subdomain
preloadĐủ điều kiện preload list trong trình duyệt — lần đầu không qua HTTP

SSL stripping là tấn công HSTS chặn: attacker chặn lần http:// đầu và proxy HTTP trong khi nói HTTPS phía trên. Có HSTS, trình duyệt tuân thủ nâng cấp hoặc chặn trước khi form đăng nhập tải qua cleartext.

Lưu ý preload:gửi hstspreload.org khó hoàn tác — phải chạy HTTPS đúng trên mọi host trong danh sách hàng tháng trước khi gỡ lan rộng. Chỉ thêm preload khi mọi subdomain sẵn sàng và bạn kiểm soát TLS end-to-end.


Mixed content: trang HTTPS, tài nguyên HTTP

Trang tải qua https:// không được kéo nội dung active qua http://. Trình duyệt phân chặn được (script, stylesheet, iframe, XHR) hay nâng cấp/hiển thị (image, audio ở chế độ hiện đại).

Mẫu lỗ hổng

<script src="http://cdn.example.com/lib.js"></script>
<link rel="stylesheet" href="http://cdn.example.com/app.css" />

Sửa:URL theo protocol vẫn sai trên HTTPS; dùng HTTPS hoặc path tương đối gốc:

<script src="https://cdn.example.com/lib.js"></script>
<link rel="stylesheet" href="/assets/app.css" />

Gửi upgrade-insecure-requests trong CSP (xem Phần 3) để link http:// cũ tự nâng cấp khi có thể:

Content-Security-Policy: upgrade-insecure-requests; default-src 'self'

Audit bằng panel Issues DevTools và HTML checker CI — lỗi mixed content hay chỉ lộ ở staging khi ai đó copy URL embed cũ.


Bộ header bảo mật

Các header này không thay CSP Phần 3, framing Phần 7, hay validate server — chúng xếp chồng default trình duyệt độc lập.

CSP (tóm tắt)

CSP là tường lửa trong trình duyệt cho script, style, frame, và đích mạng — chi tiết ở Phần 3. Thói quen production tối thiểu: default-src 'self', script-src chặt với nonce/hash, object-src 'none', và frame-ancestors khớp Phần 7.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-PLACEHOLDER'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

X-Content-Type-Options

Chặn trình duyệt đoán MIME response thành ngữ cảnh thực thi (vd upload text/plain thành HTML).

X-Content-Type-Options: nosniff

Kèm Content-Type đúng trên download và API; nosniff rẻ và hỗ trợ rộng.

Referrer-Policy

Điều khiển mức URL trước (path, query, token trong query) rò rỉ khi điều hướng và request tài nguyên phụ.

Referrer-Policy: strict-origin-when-cross-origin

Điều hướng same-origin gửi URL đầy đủ; cross-origin HTTPS chỉ gửi origin; hạ xuống HTTP không gửi referrer. Tùy chọn chặt hơn (no-referrer, same-origin) hữu ích trên URL OAuth và reset mật khẩu có secret trong query.

Framing

Phần 7 là nguồn chính cho clickjacking; triển khai cả CSP hiện đại và XFO legacy khi migrate:

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Route chỉ embed dùng allowlist hẹp trên route đó — không SAMEORIGIN toàn site nếu admin phải DENY.

Permissions-Policy

Trước là Feature-Policy — tắt API mạnh mặc định (camera, mic, geolocation, payment, usb, …) trừ khi bạn cho phép rõ:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Dùng () để tắt mọi origin; self hoặc (https://trusted.example) để cho phép hẹp. Bổ sung CSP: CSP nói thứ gì được nạp; Permissions-Policy nói thứ gì được chạy trong trang.

Cô lập cross-origin

Tấn công kiểu Spectre đọc bộ nhớ cross-origin trong cùng process. Trình duyệt giảm nhẹ bằng cô lập cross-origin — process riêng (hoặc biên chặt) để dữ liệu nhạy cảm không nằm cạnh nội dung attacker kiểm soát.

HeaderMục đích
Cross-Origin-Opener-Policy: same-originContext duyệt mới không giữ tham chiếu chung tới cửa sổ bạn cross-origin
Cross-Origin-Embedder-Policy: require-corpTài nguyên cross-origin phải opt-in qua CORP hoặc CORS, không thì không nạp
Cross-Origin-Resource-Policy: same-originAsset khai báo ai được nhúng/tiêu thụ trong ngữ cảnh cross-origin

Khi COOP + COEP đúng, window.crossOriginIsolated === true và API như SharedArrayBuffer cùng timer độ phân giải cao lại khả dụng theo quy tắc kiểm soát. Nâng cao; chỉ áp khi cần API đó hoặc cô lập rõ — COEP cấu hình sai gãy widget bên thứ ba không gửi CORP.

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

Cache-Control trên phản hồi nhạy cảm

Trình duyệt và CDN có thể lưu HTML hoặc JSON đã xác thực trên cache đĩa — rủi ro thiết bị dùng chung.

Cache-Control: no-store
Pragma: no-cache

Asset marketing tĩnh vẫn max-age dài + immutable; tách policy theo route.


SRI

Thẻ script từ CDN chạy với toàn quyền origin bạn (Phần 1). CDN hoặc path bị xâm, attacker đổi file — SRI bắt trình duyệt băm byte và từ chối lệch.

<script
  src="https://cdn.example.com/lib/v3.2.1/app.min.js"
  integrity="sha384-PLACEHOLDER_BASE64_DIGEST"
  crossorigin="anonymous"
></script>

Sinh digest lúc build (openssl dgst -sha384 -binary file.js | openssl base64 -A hoặc plugin bundler). crossorigin="anonymous" bắt buộc cho fetch CDN bật CORS dùng trong kiểm integrity. SRI không giúp script inline — kèm CSP hash/nonce (Phần 3). Chuỗi supply-chain tiếp ở Phần 9.


Ví dụ gộp: _headers static host

Nhiều static host (Netlify, Cloudflare Pages, v.v.) map file _headers hoặc config headers sang header phản hồi — giống add_header nginx hoặc rule CDN. Vài header không set ổn qua <meta http-equiv> (gồm HSTS và thực tế framing/isolation) — cấu hình ở edge.

/*
  Strict-Transport-Security: max-age=31536000; includeSubDomains
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()
  Cross-Origin-Opener-Policy: same-origin
  X-Frame-Options: DENY
  Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-PLACEHOLDER'; style-src 'self' 'nonce-PLACEHOLDER'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests
  Cache-Control: public, max-age=0, must-revalidate

/account/*
  Cache-Control: no-store
  Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-PLACEHOLDER'; frame-ancestors 'none'

Chỉnh CSP theo môi trường; bắt đầu Content-Security-Policy-Report-Only trước khi enforce (Phần 3).


Báo cáo và công cụ audit

Scan externally after deploy:

  • điểm chữ trên header phổ biến.
  • hướng dẫn HTTP/TLS và header rộng hơn.

Báo cáo vi phạm CSP — gửi tới collector khi ở chế độ report-only:

Content-Security-Policy-Report-Only: script-src 'self'; report-uri https://reports.example.com/csp

Stack hiện đại ưu tiên Reporting-Endpoints + report-to (kế thừa Report-To cũ); kiểm tra CDN hỗ trợ trước khi phụ thuộc production. Cảnh báo khi spike — vi phạm script-src đột ngột thường là thử XSS hoặc deploy lỗi.


Checklist frontend

Trước khi coi series bảo mật “đã deploy”:

  • URL chính là https://; HTTP redirect 301 sang HTTPS; HSTS bật với max-age hợp lý.
  • Không có script/stylesheet/iframe http:// production; CSP có upgrade-insecure-requests nếu còn URL cũ.
  • Stack header ở CDN/server, không chỉ meta framework.
  • CSP Phần 3 enforce (hoặc report-only có monitor); framing Phần 7 trên route nhạy cảm.
  • Script CDN có SRI + crossorigin; lockfile và review dependency ở Phần 9.
  • Phản hồi account và API JSON gửi Cache-Control: no-store khi cần.
  • Quét định kỳ securityheaders.com / Observatory sau đổi infra.

Bài tập / Exercises

1. Mỗi thứ một câu: confidentiality, integrity, authentication của TLS chống gì trên Wi‑Fi công cộng?

Lời giải

Confidentiality — kẻ nghe lén không đọc cookie phiên hay body phản hồi. Integrity — attacker không đổi bundle JS thành malware mà trình duyệt không phát hiện. Authentication — bạn nói chuyện với server sở hữu cert cho hostname đó, không phải kẻ giả danh local tầm thường.

2. Site thêm HSTS như trên nhưng http://legacy.example.com vẫn phục vụ admin qua HTTP. Gì hỏng, thứ tự sửa?

Lời giải

Trình duyệt có HSTS (nhất preload) từ chối hoặc nâng cấp legacy.example.com — admin không vào được hoặc lệch hành vi. Thứ tự sửa: (1) HTTPS trên legacy với cert hợp lệ, (2) xác minh mọi subdomain qua HTTPS, (3) deploy HSTS không preload đến khi ổn, (4) mới cân nhắc preload.

3. Thêm SRI cho script CDN dưới và nêu một directive CSP vẫn chặn inline nếu CDN sạch nhưng XSS chèn <script>alert(1)</script>.

<script src="https://cdn.example.com/analytics.js"></script>
Lời giải
<script
  src="https://cdn.example.com/analytics.js"
  integrity="sha384-PLACEHOLDER_DIGEST_FROM_BUILD"
  crossorigin="anonymous"
></script>

Ví dụ CSP: script-src 'self' 'nonce-PLACEHOLDER' — chặn inline alert dù file ngoài khớp hash.

Nâng cao:Giải thích vì sao COEP require-corp có thể gãy iframe thanh toán bên thứ ba, và nêu hai header bài này mà Phần 7 đã dùng cho mối đe dọa khác.

Lời giải

COEP require-corp bắt mọi tài nguyên cross-origin (kể cả frame lồng) gửi CORP hoặc CORS đúng; nhiều widget thanh toán là embed cũ không CORP nên không nạp. Phần 7 đã dùng frame-ancestors / X-Frame-Options chống clickjacking — cùng họ header, mục tiêu khác cô lập COEP. Content-Security-Policy frame-ancestors cũng xuất hiện ở hai phần — framing so với quy tắc script/nạp.


Điểm chính

  • HTTPS/TLS bắt buộc: confidentiality, integrity, authentication; HTTP cleartext cho nghe lén và injection.
  • HSTS đóng cửa sổ downgrade lần đầu; preload mạnh và chậm hoàn tác.
  • Mixed content và asset http:// hardcode phá trang HTTPS — dùng URL tương đối/https://upgrade-insecure-requests.
  • Xếp nosniff, Referrer-Policy, Permissions-Policy, CSP (Phần 3), framing (Phần 7), Cache-Control: no-store trên dữ liệu nhạy cảm.
  • COOP/COEP/CORP bật cô lập cross-origin và crossOriginIsolated — áp có chủ đích; có thể gãy embed bên thứ ba.
  • SRI ghim byte script CDN; kèm CSP và thói quen supply-chain Phần 9.
  • Cấu hình header trên server/CDN (_headers, nginx, v.v.); audit bằng securityheaders.com, Observatory, và báo cáo CSP.

Tiếp theo

Phần 9 — Secrets, Data Leakage & Supply-Chain: biến môi trường trong bundle client, rò localStorage, typosquat npm, lockfile, và biến SRI + CSP thành pipeline release lặp lại.