DNS RobotDNS Propagation Checker
Trang ChủDNSWHOISIPSSL
DNS RobotDNS Propagation Checker

Công cụ kiểm tra DNS thế hệ mới

Chính Sách Bảo MậtĐiều Khoản Dịch VụVề Chúng TôiBlogLiên hệ

Công Cụ DNS

Tra Cứu DNSKiểm Tra Tốc Độ DNSTên Miền Sang IPTra Cứu NSTra Cứu MXXem tất cả

Công Cụ Email

Kiểm Tra Bản Ghi SPFKiểm Tra DMARCKiểm Tra DKIMKiểm Tra SMTPPhân Tích Header EmailXem tất cả

Công Cụ Website

Tra Cứu WHOISKiểm tra HostingKiểm Tra Tên MiềnTìm Tên Miền PhụPhát Hiện CMSXem tất cả

Công Cụ Mạng

Công Cụ PingTracerouteKiểm Tra CổngKiểm Tra Header HTTPKiểm Tra Chứng Chỉ SSLXem tất cả

Công Cụ IP

Tra Cứu IPIP Của Tôi Là GìKiểm Tra Danh Sách Đen IPIP Sang HostnameTra Cứu ASNXem tất cả

Công Cụ Tiện Ích

Quét Mã QRTạo Mã QRUPI QR Code GeneratorWiFi QR Code GeneratorDịch Mã MorseXem tất cả
© 2026 DNS Robot. Phát triển bởi: ❤ Shaik Brothers
Tất cả hệ thống hoạt động bình thường
Made with
Trang chủ/Blog/Bảo mật website: Hướng dẫn 6 lớp bảo vệ kèm checklist (2026)

Bảo mật website: Hướng dẫn 6 lớp bảo vệ kèm checklist (2026)

Shaik Vahid29 thg 9, 202630 phút đọc
Bảo mật website theo sáu lớp: tên miền và DNS, TLS, HTTP header, ứng dụng, thư viện phụ thuộc, máy chủ
Bảo mật website theo sáu lớp: tên miền và DNS, TLS, HTTP header, ứng dụng, thư viện phụ thuộc, máy chủ

Điểm chính

Bảo mật website hiệu quả được xây theo từng lớp: khóa chặt tên miền và DNS (registrar lock, DNSSEC, CAA), bắt buộc TLS hiện đại kèm HSTS, gửi một bộ HTTP security header nghiêm ngặt, băm mật khẩu bằng Argon2id kèm MFA và rate limit, kiểm tra đầu vào và thực thi kiểm soát truy cập trên máy chủ, ghim và audit thư viện phụ thuộc, đóng mọi cổng bạn không sử dụng, và ghi log đủ để nhận ra khi bị xâm nhập. Mỗi biện pháp dưới đây đều đi kèm cấu hình chính xác và một cách miễn phí để kiểm chứng rằng nó thực sự đang hoạt động, bởi một biện pháp kiểm soát chưa được kiểm thử thì coi như bạn chưa có.

Advertisement

Bảo mật website thực sự nghĩa là gì?

Các biện pháp bảo mật website là tập hợp những lớp kiểm soát giúp một website hay ứng dụng web không bị chiếm quyền, bị thay đổi giao diện (deface), bị lợi dụng để tấn công chính người truy cập, hay bị rút dữ liệu một cách âm thầm. Đó không phải một sản phẩm hay một tùy chọn duy nhất. Đó là một chuỗi quyết định bắt đầu từ nhà đăng ký tên miền (registrar), đi qua DNS, TLS, các HTTP header mà máy chủ gửi đi, phần code xử lý đăng nhập và dữ liệu đầu vào, các package bên thứ ba mà bạn xây dựng dựa trên, bản thân máy chủ, và cuối cùng là log — thứ báo cho bạn biết khi có chuyện không ổn.

Lý do nên tư duy theo từng lớp là vì kẻ tấn công cũng làm vậy. Báo cáo Điều tra Vi phạm Dữ liệu 2026 (DBIR) của Verizon cho thấy 31% vụ xâm phạm hiện bắt đầu từ một lỗ hổng phần mềm bị khai thác, lần đầu tiên vượt qua thông tin đăng nhập bị đánh cắp để trở thành con đường xâm nhập phổ biến nhất, và 48% tổng số vụ xâm phạm có liên quan đến ransomware. Chỉ một bản vá bị bỏ sót, một API key bị lộ hay một trang quản trị không có rate limiting là đủ. Không biện pháp nào trong hướng dẫn này là cao siêu; những website bị xâm nhập thường là nơi bỏ qua điều cơ bản ở một lớp trong khi mải trau chuốt một lớp khác.

Hướng dẫn này được sắp xếp từ ngoài vào trong. Mỗi biện pháp cho bạn biết vì sao nó quan trọng, cần cấu hình chính xác những gì và cách kiểm chứng bằng một lệnh hoặc một công cụ miễn phí, bởi lỗi phổ biến nhất mà chúng tôi thấy qua các lượt kiểm tra bằng Kiểm Tra Header HTTP và Kiểm Tra Chứng Chỉ SSL không phải là một quyết định sai, mà là một thiết lập ai đó tin là đã bật nhưng chưa bao giờ xác nhận.

Ghi chú

Nếu bạn chỉ có một giờ: bật registrar lock và MFA tại nhà đăng ký tên miền, bắt buộc HTTPS kèm HSTS, thêm các security header trong bảng ở Lớp 3, băm mật khẩu bằng Argon2id, cập nhật mọi thư viện phụ thuộc có CVE đã biết, và đóng mọi cổng trừ 80 và 443. Chừng đó đã bao phủ các điểm xâm nhập đứng sau phần lớn các vụ xâm phạm website trong thực tế.

Mô hình bảo mật website: Sáu lớp cần bảo vệ

Mọi cuộc tấn công vào website đều nhắm vào một trong sáu lớp. Bảng dưới đây là bản đồ cho phần còn lại của hướng dẫn: mỗi lớp chứa những gì, thường bị tấn công ra sao, và công cụ kiểm tra miễn phí nào xác nhận lớp phòng thủ của bạn thực sự đang hoạt động.

LớpKẻ tấn công làm gì ở đâyBiện pháp cốt lõiKiểm chứng bằng
1. Tên miền và DNSChiếm tên miền, đổi nameserver, chiếm các subdomain bị bỏ quên, xin chứng chỉ giả mạoRegistrar lock, MFA, DNSSEC, CAA, rà soát bản ghi cũTra Cứu WHOIS, Tra Cứu DNS, Tìm Tên Miền Phụ
2. Truyền tải (TLS)Hạ cấp xuống HTTP, gỡ bỏ lớp mã hóa, khai thác bộ mã hóa cũ, chứng chỉ hết hạnTLS 1.2+, HSTS kèm preload, gia hạn tự độngKiểm Tra Chứng Chỉ SSL
3. HTTP headerChèn script (XSS), nhúng website vào frame để clickjacking, đoán kiểu nội dung, rò rỉ referrerCSP, frame-ancestors, nosniff, Referrer-Policy, Permissions-PolicyKiểm Tra Header HTTP
4. Ứng dụngCredential stuffing, injection, lỗi kiểm soát truy cập, CSRF, upload file không an toànArgon2id + MFA + rate limit, truy vấn tham số hóa, phân quyền phía máy chủ, cookie SameSiteRà soát code, OWASP ZAP, Kiểm Tra Độ Mạnh Mật Khẩu
5. Thư viện phụ thuộcPackage bị cài mã độc, CVE đã biết trong thư viện, pipeline CI bị xâm nhậpLockfile, audit, ghim phiên bản, SBOM, token đặc quyền tối thiểunpm audit, Dependabot, Trivy
6. Máy chủ và vận hànhCổng mở, thông tin đăng nhập mặc định, thiếu bản vá, không có bản sao lưu, không có logTường lửa, SSH chỉ dùng key, vá lỗi, sao lưu 3-2-1, log tập trungKiểm Tra Cổng, Kiểm Tra Danh Sách Đen IP, Kiểm Tra Sức Khỏe Tên Miền

Mẹo

Làm từ trên xuống. Lớp 1 đến 3 là phần cấu hình bạn có thể xong trong một buổi chiều, và chúng bảo vệ mọi trang cùng lúc. Lớp 4 đến 6 là những thói quen lâu dài cần nằm sẵn trong quy trình review code và triển khai của bạn.

Advertisement

Lớp 1: Khóa chặt tên miền và DNS

Mọi thứ khác trong hướng dẫn này đều giả định bạn vẫn kiểm soát tên miền của mình. Nếu kẻ tấn công đăng nhập được vào tài khoản registrar hoặc đổi được nameserver, chúng có thể trỏ lưu lượng của bạn đi bất cứ đâu, xin một chứng chỉ hợp lệ cho tên miền của bạn và đọc mọi email gửi đến bạn, trong khi code ứng dụng của bạn thậm chí không bao giờ được chạy. Vì vậy, bảo mật tên miền và DNS là biện pháp bảo mật website đầu tiên, và gần như hoàn toàn chỉ là việc cấu hình.

Hãy bắt đầu bằng một lượt Tra Cứu WHOIS cho chính tên miền của bạn và xác nhận ba điều: mã trạng thái có clientTransferProhibited, ngày hết hạn còn hơn một năm nữa, và registrar là nơi bạn thực sự có tài khoản. Chuyện tên miền của doanh nghiệp nằm trong tài khoản registrar của một nhà thầu cũ phổ biến đến bất ngờ. Hướng dẫn tra cứu WHOIS của chúng tôi giải thích mọi mã trạng thái bạn sẽ gặp.

Registrar lock, MFA và cảnh báo hết hạn

Bật registrar lock (còn gọi là khóa chuyển nhượng — transfer lock) để tên miền không thể bị chuyển sang registrar khác nếu bạn không chủ động mở khóa, và bật xác thực đa yếu tố (MFA) cho chính tài khoản registrar. Tài khoản registrar là mục tiêu phishing ưa thích chính vì một lần đăng nhập kiểm soát mọi thứ phía sau. Hãy dùng khóa bảo mật phần cứng hoặc ứng dụng xác thực thay cho SMS ở bất cứ đâu registrar cho phép.

Đặt tên miền ở chế độ tự động gia hạn với một phương thức thanh toán không hết hạn, và dù vậy vẫn nên thêm một nhắc nhở trên lịch 60 ngày trước ngày hết hạn. Tên miền hết hạn bị các dịch vụ săn tên miền (drop-catcher) chộp lấy chỉ trong vài giờ, và người mua sẽ thừa hưởng email, lưu lượng truy cập của bạn cùng mọi đăng nhập OAuth đang tin tưởng tên miền đó.

  • Registry lock (khóa thủ công ở cấp nhà quản lý tên miền — registry, được xác nhận qua một kênh riêng ngoài hệ thống) đáng với khoản phí thêm cho một tên miền mà doanh nghiệp phụ thuộc vào; nó ngăn cả một tài khoản registrar đã bị xâm nhập thay đổi nameserver.

  • Tách bạch vai trò. Người trả tiền cho tên miền, tài khoản sở hữu tên miền và nhà cung cấp DNS đều cần được ghi lại rõ ràng và không gắn với hộp thư của một nhân viên duy nhất.

  • Bật WHOIS privacy để thông tin liên hệ của bạn không trở thành bản đồ cho kẻ phishing, và giữ trên tài khoản một địa chỉ email theo vai trò có người theo dõi, chẳng hạn domains@.

Ký zone bằng DNSSEC

DNSSEC ký số zone DNS của bạn để resolver có thể phát hiện các câu trả lời giả mạo. Không có nó, kẻ tấn công đầu độc được bộ nhớ đệm của một resolver có thể đưa người truy cập đến máy chủ giả trong khi trình duyệt vẫn hiển thị đúng tên miền của bạn. Hầu hết các nhà cung cấp DNS có quản lý (Cloudflare, Route 53, Google Cloud DNS và nhiều registrar) bật nó chỉ bằng một nút; bước thủ công duy nhất là công bố bản ghi DS tại registrar. Kiểm chứng bằng dig, hoặc bằng mục kiểm tra DNSSEC trong Kiểm Tra Sức Khỏe Tên Miền:

bash
dig +dnssec +short yourdomain.com A
# A signed zone returns the record AND an RRSIG line:
# 203.0.113.10
# A 13 2 3600 20261015000000 20260924000000 34505 yourdomain.com. Kx3f...==

dig +short yourdomain.com DS
# A DS record at the parent zone proves the chain of trust is complete
# 34505 13 2 6A1B...C9

Giới hạn việc cấp chứng chỉ bằng CAA

Bản ghi CAA (RFC 8659) cho các tổ chức cấp chứng chỉ (CA) biết CA nào được phép cấp chứng chỉ cho tên miền của bạn. Mọi CA công khai đều bắt buộc phải kiểm tra bản ghi này trước khi cấp, nên một bản ghi CAA sẽ chặn đường kẻ tấn công đã đánh lừa được quy trình xác thực tên miền của một CA khác. Ba bản ghi bao quát các trường hợp phổ biến: ai được cấp, ai được cấp chứng chỉ wildcard, và báo cáo yêu cầu bị từ chối gửi về đâu:

dns
yourdomain.com.  CAA 0 issue "letsencrypt.org"
yourdomain.com.  CAA 0 issuewild ";"
yourdomain.com.  CAA 0 iodef "mailto:security@yourdomain.com"

Cảnh báo

Trước khi thêm CAA, hãy liệt kê mọi CA hiện đang cấp chứng chỉ cho bạn, kể cả CA đứng sau CDN hay bảng điều khiển hosting (Cloudflare luân phiên giữa nhiều CA; nhiều nhà cung cấp hosting dùng Sectigo hoặc Let's Encrypt). Một bản ghi CAA bỏ sót CA thực tế của bạn sẽ âm thầm làm hỏng lần gia hạn kế tiếp. Hãy kiểm tra CA hiện tại bằng công cụ Kiểm Tra Chứng Chỉ SSL trước.

Rà soát bản ghi cũ: Subdomain takeover

Subdomain takeover (chiếm quyền tên miền phụ) xảy ra khi một bản ghi DNS vẫn trỏ đến dịch vụ bạn đã ngừng dùng: một CNAME trỏ tới trang GitHub Pages đã xóa, một ứng dụng Azure, một bucket S3 hay hostname của một nhà cung cấp SaaS. Bất kỳ ai cũng có thể đăng ký lại đích đến bị bỏ rơi đó rồi phục vụ nội dung trên old-app.yourdomain.com dưới danh nghĩa của bạn, kèm cả chứng chỉ hợp lệ. Đây là một trong những lỗi được báo cáo nhiều nhất trong các chương trình bug bounty, vì chẳng ai nhớ bản ghi đó vẫn còn tồn tại.

Hãy chạy tên miền của bạn qua công cụ Tìm Tên Miền Phụ, công cụ đọc nhật ký certificate transparency, để thấy mọi hostname từng được cấp chứng chỉ. Sau đó phân giải từng hostname bằng Tra Cứu DNS và xóa mọi bản ghi có đích trả về NXDOMAIN hoặc trang "no such app" của nhà cung cấp. Lặp lại mỗi quý, và biến việc xóa bản ghi DNS thành một bước bắt buộc khi ngừng bất kỳ dịch vụ nào.

Mẹo

Certificate transparency cũng là hệ thống cảnh báo sớm của bạn: Cert Spotter và tính năng giám sát CT của Cloudflare có thể gửi email cho bạn mỗi khi bất kỳ CA nào cấp chứng chỉ cho tên miền của bạn. Một chứng chỉ bất ngờ xuất hiện thường là dấu hiệu đầu tiên có thể nhìn thấy của việc DNS hoặc registrar bị xâm nhập.

Lớp 2: Triển khai HTTPS và TLS đúng cách

HTTPS không còn là một khuyến nghị nữa; nó là mức tối thiểu bắt buộc, và trình duyệt đánh dấu các trang HTTP thuần là "Không an toàn". Những điều vẫn còn phân biệt một website an toàn với một website chỉ đơn thuần được mã hóa là: các phiên bản TLS bạn chấp nhận, HTTP có còn được dùng để truy cập đến bạn hay không, và việc gia hạn có được tự động hóa đủ tốt để vượt qua các đợt rút ngắn thời hạn chứng chỉ bắt đầu từ tháng 3/2026 hay không.

Advertisement

Tối thiểu TLS 1.2, ưu tiên TLS 1.3

TLS 1.0 và 1.1 đã chính thức bị loại bỏ theo RFC 8996 từ năm 2021 và không trình duyệt hiện hành nào còn thương lượng hai phiên bản này. Hãy tắt chúng trên máy chủ, ưu tiên TLS 1.3 (phiên bản loại bỏ mọi bộ mã hóa yếu đã biết và hoàn tất bắt tay chỉ trong một vòng trao đổi), và chỉ giữ TLS 1.2 với các bộ mã hóa AEAD như ECDHE-ECDSA-AES128-GCM-SHA256 hoặc ECDHE-RSA-CHACHA20-POLY1305.

Khi kiểm tra từ bên ngoài, có hai dòng quan trọng: giao thức được thương lượng, và Verify return code: 0, nghĩa là toàn bộ chuỗi chứng chỉ đã được xác thực. Thiếu chứng chỉ trung gian là nguyên nhân phổ biến nhất của các báo lỗi kiểu "chạy được trên Chrome nhưng lỗi trên curl và Android"; hướng dẫn về chuỗi chứng chỉ SSL của chúng tôi chỉ cách khắc phục, và công cụ Kiểm Tra Chứng Chỉ SSL báo trực tiếp khi chuỗi không đầy đủ. Đây là kết quả kiểm tra chạy trên dnsrobot.net:

bash
echo | openssl s_client -connect dnsrobot.net:443 -servername dnsrobot.net 2>/dev/null \
  | grep -E 'Protocol|Cipher|Verify return'

# Protocol  : TLSv1.3
# Cipher    : TLS_AES_256_GCM_SHA384
# Verify return code: 0 (ok)

# Confirm the old versions are refused:
openssl s_client -connect dnsrobot.net:443 -tls1_1 2>&1 | grep -iE 'alert|no protocols'

Ghi chú

Danh sách cipher là chỗ các cấu hình sao chép trên mạng nhanh lỗi thời nhất. Thay vì tự viết tay, hãy tạo khối cấu hình máy chủ được khuyến nghị hiện tại cho nginx, Apache, Caddy hoặc HAProxy bằng Mozilla SSL Configuration Generator và chọn cấu hình "Intermediate", trừ khi bạn buộc phải hỗ trợ các client rất cũ.

HSTS: Không bao giờ để trình duyệt dùng lại HTTP

Chỉ chuyển hướng HTTP sang HTTPS là chưa đủ. Yêu cầu đầu tiên mà người truy cập gửi đến http://yourdomain.com vẫn đi dưới dạng văn bản thuần, và kẻ tấn công trong cùng mạng có thể trả lời nó trước khi lệnh chuyển hướng của bạn kịp đến. HTTP Strict Transport Security (RFC 6797) khắc phục điều này: khi trình duyệt đã thấy header này, nó sẽ tự viết lại mọi URL http:// sau này của tên miền bạn thành https:// trước khi gửi bất cứ thứ gì.

Dùng max-age ít nhất một năm (31536000 giây), thêm includeSubDomains khi mọi subdomain đều đã phục vụ HTTPS, rồi đăng ký tên miền tại hstspreload.org để nó được nhúng cứng sẵn trong Chrome, Firefox, Safari và Edge. Preload đóng hoàn toàn khoảng hở ở lần truy cập đầu tiên. Kiểm chứng header bằng công cụ Kiểm Tra Header HTTP, công cụ chấm HSTS như một phần của thang điểm bảo mật từ A đến F.

http
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Cảnh báo

HSTS là cánh cửa một chiều. Khi includeSubDomains đã được preload, bất kỳ subdomain nào không phục vụ được HTTPS hợp lệ, kể cả công cụ nội bộ và một host dev. bị bỏ quên, sẽ không thể truy cập được trên trình duyệt. Hãy triển khai theo từng giai đoạn: max-age=300 trong một tuần, rồi một tháng, rồi một năm, và chỉ khi đó mới thêm preload.

Chứng chỉ 47 ngày: Tự động hóa gia hạn ngay từ bây giờ

Nghị quyết SC-081 của CA/Browser Forum đang rút ngắn thời hạn tối đa của mọi chứng chỉ TLS công khai theo từng giai đoạn. Đợt cắt giảm đầu tiên có hiệu lực từ ngày 15/3/2026, nên bất kỳ chứng chỉ nào bạn mua hay gia hạn hôm nay đều đã bị giới hạn ở 200 ngày, và đến năm 2029, chứng chỉ sẽ phải thay khoảng sáu tuần một lần:

Ngày hiệu lựcThời hạn chứng chỉ tối đaThời gian tái sử dụng xác thực tên miền
Trước 15/3/2026398 ngày398 ngày
15/3/2026200 ngày200 ngày
15/3/2027100 ngày100 ngày
15/3/202947 ngày10 ngày

Gia hạn thủ công không thể theo kịp lịch trình này. Cách làm đúng là cấp chứng chỉ hoàn toàn tự động qua ACME: Let's Encrypt hoặc Google Trust Services thông qua certbot, acme.sh, Caddy, hoặc chứng chỉ tự động có sẵn trong Cloudflare, Vercel, Netlify và hầu hết các bảng điều khiển hosting. Hãy thử quy trình gia hạn trước khi thực sự cần đến nó (certbot renew --dry-run với certbot); lỗi cần để ý là một bản ghi CAA hoặc quy tắc tường lửa được thêm sau lần gia hạn trước và giờ đang chặn bước xác thực. Dù dùng giải pháp nào, hãy theo dõi cả ngày hết hạn từ bên ngoài: công cụ Kiểm Tra Chứng Chỉ SSL hiển thị số ngày còn lại, và một chứng chỉ hết hạn sẽ biến mọi lượt truy cập thành một trang cảnh báo toàn màn hình của trình duyệt.

Lớp 3: HTTP security header

Security header là những chỉ dẫn máy chủ gửi cho trình duyệt về những gì nó được và không được làm với các trang của bạn: script nào được chạy, trang có được nhúng vào frame hay không, trình duyệt có được tự đoán kiểu nội dung hay không, và nó nên cho các website khác biết gì về nơi người truy cập đến từ đâu. Chúng không tốn chi phí, áp dụng cho mọi trang cùng lúc và chặn được cả một nhóm tấn công. Đây cũng là lớp mà hầu hết các website cấu hình sai một phần, đó là lý do công cụ Kiểm Tra Header HTTP chấm điểm chúng. Đây là những gì dnsrobot.net gửi đi, đã lược bớt chỉ còn các security header:

bash
curl -sI https://dnsrobot.net/ | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy'

strict-transport-security: max-age=31536000; includeSubDomains; preload
content-security-policy: default-src 'self'; script-src 'self' ... https://*.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(self), microphone=(), geolocation=(), payment=(), usb=()

Bảy header mọi website nên gửi

Đây là các giá trị nên hướng tới với một website thông thường. Năm header đầu là những header ảnh hưởng đến điểm số của bạn; hai header cuối là phần bổ sung dễ dàng khi các header kia đã có.

HeaderGiá trị khuyến nghịNgăn chặn điều gì
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadHạ cấp giao thức, đánh cắp cookie qua HTTP
Content-Security-Policyscript-src dựa trên nonce kèm 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'Cross-site scripting (XSS), chèn dữ liệu, clickjacking
X-Content-Type-OptionsnosniffMIME sniffing biến một file upload thành script thực thi được
Referrer-Policystrict-origin-when-cross-originRò rỉ URL đầy đủ (token, từ khóa tìm kiếm) cho bên thứ ba
Permissions-Policycamera=(), microphone=(), geolocation=(), payment=()Script bên thứ ba âm thầm sử dụng các API trình duyệt nhạy cảm
X-Frame-OptionsDENY (phương án dự phòng kiểu cũ cho frame-ancestors)Clickjacking trên các trình duyệt không hỗ trợ CSP Level 2
Cross-Origin-Opener-Policysame-originTấn công xuyên cửa sổ thông qua window.opener

Content Security Policy mà không làm hỏng website

Content Security Policy là biện pháp phòng thủ hiệu quả nhất trước XSS, vì nó ngăn các script bị chèn vào được chạy ngay cả khi tồn tại lỗi injection. Điểm khó là một chính sách nghiêm ngặt sẽ làm hỏng mọi script inline hay thẻ bên thứ ba mà bạn đã quên mất. Cách tiếp cận hiện đại, được Google khuyến nghị và có tài liệu trên MDN, là chính sách dựa trên nonce: máy chủ tạo một nonce ngẫu nhiên cho mỗi response, gắn nó vào mọi thẻ script mà nó chủ động render, và trình duyệt từ chối mọi thứ còn lại.

'strict-dynamic' là thứ làm cho chính sách này khả thi trong thực tế: một script có nonce được phép tải thêm các script khác (analytics, tag manager, widget) mà không cần đưa từng cái vào allowlist, còn các token https: và 'unsafe-inline' bị trình duyệt hiện đại bỏ qua và chỉ đóng vai trò dự phòng cho trình duyệt cũ. Hãy triển khai với Content-Security-Policy-Report-Only trước, theo dõi các báo cáo vi phạm trong một tuần, rồi mới chuyển sang chế độ thực thi. Các framework như Next.js, Rails và Django có hỗ trợ nonce chính thức; website tĩnh có thể dùng script-src dựa trên hash thay thế.

http
Content-Security-Policy:
  script-src 'nonce-r4nd0m1z3dV4lu3' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
  form-action 'self';
  report-to csp-endpoint

<!-- Only scripts carrying the nonce run; strict-dynamic lets them load their own dependencies -->
<script nonce="r4nd0m1z3dV4lu3" src="/app.js"></script>

Ghi chú

Một chính sách allowlist như script-src 'self' https://cdn.example.com vẫn tốt hơn không có gì, nhưng bất kỳ endpoint JSONP hay thư viện cũ nào trên một host nằm trong allowlist đều có thể bị lợi dụng để vượt qua nó. Nếu chưa thể dùng nonce, ít nhất hãy đặt object-src 'none' và base-uri 'none', hai chỉ thị giúp đóng ngay hai cách vượt qua phổ biến.

Clickjacking: frame-ancestors và X-Frame-Options

Clickjacking tải website của bạn một cách vô hình bên trong trang của kẻ tấn công và lừa người truy cập bấm vào một nút họ không nhìn thấy. frame-ancestors 'none' trong CSP (hoặc 'self' nếu bạn nhúng các trang của chính mình) ngăn chặn điều này trên mọi trình duyệt hiện hành, còn X-Frame-Options: DENY bao phủ các trình duyệt cũ. Nếu bạn cố ý cung cấp một widget có thể nhúng, chỉ miễn trừ đúng đường dẫn đó và chỉ cho các origin cần đến nó; hướng dẫn X-Frame-Options của chúng tôi trình bày chi tiết các quy tắc máy chủ chính xác và những lỗi "refused to connect" bạn sẽ gặp khi thử nghiệm.

nosniff, Referrer-Policy, Permissions-Policy và COOP

X-Content-Type-Options: nosniff ngăn trình duyệt tự đoán lại Content-Type, chính điều có thể biến một "ảnh" do người dùng tải lên nhưng chứa HTML thành một trang thực thi được. Referrer-Policy: strict-origin-when-cross-origin (mặc định trên các trình duyệt hiện nay, nhưng vẫn nên đặt rõ ràng) chỉ gửi origin của bạn cho các website khác, nhờ đó token đặt lại mật khẩu và từ khóa tìm kiếm trong URL vẫn được giữ kín. Permissions-Policy tắt các tính năng trình duyệt bạn không dùng, để một script bên thứ ba bị xâm nhập không thể mở camera hay đọc vị trí. Cross-Origin-Opener-Policy: same-origin cắt liên kết window.opener tới các trang bạn mở, qua đó chặn một nhóm tấn công xuyên cửa sổ.

Các header cần gỡ bỏ cũng quan trọng không kém: Server và X-Powered-By quảng cáo chính xác phiên bản phần mềm cho các công cụ quét lỗ hổng, còn X-XSS-Protection đã lỗi thời và có thể gây lỗi trên trình duyệt cũ, vì vậy hãy đặt nó thành 0 hoặc bỏ hẳn. Công cụ Phát Hiện CMS cho thấy một công cụ quét biết được gì về hệ thống của bạn chỉ từ header.

Mẹo

Đặt header một lần tại lớp biên (CDN, reverse proxy hoặc middleware của framework) thay vì cho từng trang. Trong Next.js, đó là file proxy hoặc middleware; trong nginx là khối add_header trong context server; trong Apache là Header always set. Sau đó chạy lại công cụ Kiểm Tra Header HTTP sau mỗi lần deploy, vì một phiên bản framework mới hay một thiết lập CDN có thể âm thầm làm mất một header.

Lớp 4: Xác thực chống được credential stuffing

Kẻ tấn công hiếm khi đoán mật khẩu từng cái một. Chúng dùng lại hàng tỷ cặp email và mật khẩu bị rò rỉ từ các website khác (credential stuffing) và dùng phishing cho phần còn lại. Vì thế, xác thực đúng cách gồm ba phần: lưu mật khẩu sao cho rò rỉ cơ sở dữ liệu không đồng nghĩa với rò rỉ mật khẩu, khiến một mật khẩu bị đánh cắp trở nên vô dụng nếu đứng một mình, và làm cho brute force chậm đến mức vô nghĩa. OWASP xếp Authentication Failures (lỗi xác thực) ở vị trí A07 trong OWASP Top 10:2025.

Advertisement

Băm mật khẩu bằng Argon2id hoặc bcrypt

Không bao giờ lưu mật khẩu, và cũng không bao giờ lưu giá trị băm nhanh của mật khẩu. MD5, SHA-1 và cả SHA-256 được thiết kế để chạy nhanh, nên một bảng giá trị băm bị rò rỉ có thể bị thử với tốc độ hàng tỷ lần đoán mỗi giây chỉ trên một GPU. Hãy dùng hàm băm mật khẩu chậm và tốn bộ nhớ (memory-hard) với salt riêng cho mỗi mật khẩu. OWASP Password Storage Cheat Sheet khuyến nghị theo thứ tự:

  • Argon2id với tối thiểu 19 MiB bộ nhớ, 2 vòng lặp và mức song song 1 (hoặc 46 MiB với 1 vòng lặp).

  • scrypt với N = 2^17, r = 8, p = 1 khi không có Argon2.

  • bcrypt với hệ số công việc (work factor) từ 10 trở lên (lưu ý giới hạn đầu vào 72 byte; việc băm trước các mật khẩu dài hơn cần làm cẩn thận).

  • PBKDF2-HMAC-SHA256 với 600.000 vòng lặp, chỉ dùng khi bắt buộc phải tuân thủ FIPS.

javascript
// Node.js with the argon2 package
import argon2 from "argon2";

const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 19456, // KiB = 19 MiB
  timeCost: 2,
  parallelism: 1,
});

// Later, on login:
const ok = await argon2.verify(hash, submittedPassword);

Ghi chú

Điều chỉnh chi phí sao cho một lần băm mất khoảng 100 đến 250 ms trên máy chủ đăng nhập. Người dùng thật không nhận ra độ trễ đó, nhưng kẻ tấn công bị giới hạn ở vài nghìn lần đoán mỗi giây trên mỗi nhân CPU thay vì hàng tỷ. Băm lại mật khẩu sau mỗi lần đăng nhập thành công bất cứ khi nào bạn tăng tham số, để các giá trị băm cũ tự nâng cấp.

Quy tắc mật khẩu thực sự hữu ích (NIST SP 800-63B)

Các quy tắc về thành phần mà hầu hết website vẫn áp dụng (một chữ hoa, một ký hiệu, đổi mỗi 90 ngày) tạo ra những mật khẩu dễ đoán như Summer2026! và đẩy người dùng tới việc dùng lại mật khẩu. Hướng dẫn hiện hành NIST SP 800-63B thay chúng bằng các quy tắc đo lường đúng điều thực sự quan trọng:

  • Tối thiểu 8 ký tự khi đồng thời bắt buộc MFA, và ít nhất 15 ký tự với mật khẩu được dùng một mình; cho phép độ dài tối thiểu 64 ký tự.

  • Không có quy tắc về thành phần và không bắt buộc đổi định kỳ; chỉ yêu cầu đổi khi có bằng chứng bị xâm phạm.

  • Đối chiếu mọi mật khẩu mới với danh sách mật khẩu bị rò rỉ (range API của Have I Been Pwned cho phép làm điều này mà không phải gửi mật khẩu đi) và từ chối những mật khẩu đã bị lộ.

  • Cho phép dán và dùng trình quản lý mật khẩu, chấp nhận dấu cách và Unicode, và hiển thị thước đo độ mạnh dựa trên entropy thay vì quy tắc.

Công cụ Kiểm Tra Độ Mạnh Mật Khẩu của chúng tôi cho thấy các thước đo này khác quy tắc cũ ra sao: nó ước tính thời gian bẻ khóa từ entropy và khả năng phát hiện mẫu, đồng thời đối chiếu mật khẩu với dữ liệu rò rỉ, một phản hồi hữu ích hơn nhiều so với thông báo màu đỏ "cần có ký hiệu".

MFA và passkey

Xác thực đa yếu tố biến một mật khẩu bị đánh cắp thành ngõ cụt. Hãy cung cấp nó cho mọi người và bắt buộc với các vai trò quản trị, tài chính và hỗ trợ. Xếp hạng các lựa chọn theo khả năng chống phishing: passkey và khóa bảo mật FIDO2 không thể bị phishing vì thông tin xác thực gắn với origin thật của bạn; ứng dụng xác thực (TOTP) là tốt; mã SMS tốt hơn không có gì nhưng có thể bị chặn qua hình thức tráo SIM (SIM swapping). Passkey được hỗ trợ trên mọi trình duyệt và hệ điều hành hiện hành và loại bỏ hoàn toàn mật khẩu, nhờ đó cũng xóa sổ credential stuffing như một loại hình tấn công.

Bảo vệ quy trình khôi phục tài khoản cẩn thận như chính việc đăng nhập: mã khôi phục được lưu dưới dạng băm, đặt lại qua email bằng token dùng một lần hết hạn trong vòng 15 phút, và không dùng câu hỏi bảo mật.

Rate limiting, khóa tài khoản và chống bot

Áp dụng rate limiting cho các endpoint đăng nhập, đăng ký, đặt lại mật khẩu và MFA theo từng IP và theo từng tài khoản, và trả về 429 Too Many Requests kèm header Retry-After khi vượt giới hạn (hướng dẫn về lỗi HTTP 429 của chúng tôi trình bày cách client nên phản ứng). Kết hợp với độ trễ tăng dần hoặc khóa tạm thời sau nhiều lần thất bại trên cùng một tài khoản, và CAPTCHA hoặc thử thách proof-of-work cho các endpoint hứng chịu tấn công phân tán. Ghi log mọi lần đăng nhập thất bại kèm IP nguồn để dấu hiệu của một đợt credential stuffing lộ ra trong vài phút thay vì vài tháng.

nginx
# nginx: 5 login attempts per minute per client IP
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

location = /api/login {
    limit_req zone=login burst=4 nodelay;
    limit_req_status 429;
    proxy_pass http://app;
}

Cảnh báo

Giữ thông báo lỗi giống hệt nhau cho "người dùng không tồn tại" và "sai mật khẩu", và làm cho thời gian phản hồi của cả hai như nhau (băm một mật khẩu giả khi người dùng không tồn tại). Nếu không, form đăng nhập sẽ vô tình trở thành một API dò tìm người dùng.

Session, cookie và CSRF

Sau khi đăng nhập, cookie phiên chính là người dùng, nên nó xứng đáng được bảo vệ như mật khẩu. Ba thuộc tính cookie và một tiền tố tên đảm nhận phần lớn công việc:

  • Secure nghĩa là cookie không bao giờ được gửi qua HTTP thuần, nên không thể bị nghe lén trên Wi-Fi công cộng.

  • HttpOnly khiến JavaScript không thể nhìn thấy cookie, nên một lỗi XSS không đọc được nó.

  • SameSite=Lax (hoặc Strict cho trang quản trị) giữ cookie khỏi các request POST liên trang, qua đó vô hiệu hóa phần lớn các cuộc tấn công CSRF.

  • Tiền tố __Host- khiến trình duyệt từ chối cookie trừ khi nó có Secure, có Path=/ và không có thuộc tính Domain, nên một subdomain không thể ghi đè lên nó.

http
Set-Cookie: __Host-session=9f1c7e2a...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800

Mẹo

Các hành động làm thay đổi trạng thái tuyệt đối không được thực hiện qua GET. Một liên kết như /account/delete?id=42 có thể bị kích hoạt bởi một thẻ <img> trên bất kỳ trang nào người dùng truy cập, bất kể các cờ cookie của bạn tốt đến đâu.

CSRF: Lớp phòng thủ thứ hai phía sau SameSite

Cross-site request forgery (CSRF) lừa một trình duyệt đang đăng nhập gửi request làm thay đổi trạng thái tới website của bạn từ một trang khác. Cookie SameSite chặn được trường hợp phổ biến, nhưng vẫn nên giữ một lớp phòng thủ thứ hai trên mọi request không phải GET: một token chống CSRF theo từng phiên trong trường ẩn hoặc header tùy chỉnh, hoặc kiểm tra header Origin (hay Sec-Fetch-Site) khớp với origin của chính bạn. Hầu hết framework (Django, Rails, Laravel, server action của Next.js) mặc định đã làm điều này; sai lầm thường gặp là tắt nó cho một endpoint API rồi lại gọi endpoint đó từ trình duyệt.

Session ID, xoay vòng và JWT

Tạo session ID với ít nhất 128 bit ngẫu nhiên, cấp lại ID khi đăng nhập và khi có bất kỳ thay đổi quyền hạn nào để chống session fixation, cho session hết hạn sau một thời gian không hoạt động, và cung cấp cho người dùng nút "đăng xuất trên mọi thiết bị" có tác dụng vô hiệu hóa session ở phía máy chủ. Với JWT, hãy để thời hạn ngắn (vài phút, kèm refresh token có thể thu hồi), không bao giờ lưu trong localStorage nơi mọi script đều đọc được, và coi việc lộ khóa ký là một vụ xâm phạm toàn diện, vì mọi token từng được ký bằng khóa đó giờ đều có thể bị giả mạo.

Injection và XSS: Kiểm tra đầu vào, mã hóa đầu ra

Injection (A05:2025) và cross-site scripting là cùng một sai lầm ở những chỗ khác nhau: dữ liệu từ người dùng được đưa cho một trình thông dịch (SQL, shell, truy vấn LDAP, trang HTML) như thể đó là code. Cách khắc phục ở mọi nơi cũng giống nhau: tách biệt dữ liệu và code, và không bao giờ xây dựng lệnh bằng cách nối chuỗi.

Advertisement

Truy vấn tham số hóa thay vì nối chuỗi

Với SQL, hãy dùng prepared statement hoặc một ORM tạo ra chúng; khi đó cơ sở dữ liệu coi đầu vào là một giá trị không bao giờ có thể thay đổi cấu trúc câu truy vấn. Quy tắc tương tự áp dụng cho lệnh shell (truyền một mảng đối số, không bao giờ truyền một chuỗi), cho bộ lọc NoSQL (từ chối các object toán tử như {"$gt": ""} trong dữ liệu người dùng), và cho đường dẫn file (chuẩn hóa đường dẫn và xác nhận nó vẫn nằm trong thư mục dự định).

javascript
// Vulnerable: the input becomes part of the query text
const rows = await db.query(`SELECT * FROM users WHERE email = '${email}'`);

// Safe: the driver sends the value separately from the query
const rows = await db.query("SELECT * FROM users WHERE email = $1", [email]);

// Python / psycopg: same idea
cur.execute("SELECT * FROM users WHERE email = %s", (email,))

Mã hóa đầu ra để chặn XSS

Cross-site scripting xảy ra khi văn bản do người dùng kiểm soát được ghi vào trang mà không được mã hóa (encode) phù hợp với ngữ cảnh nơi nó xuất hiện. Các template engine hiện đại (React và JSX, Vue, Jinja2, Blade, ERB) mặc định đều encode; XSS ngày nay thường đến từ các "lối thoát hiểm": dangerouslySetInnerHTML, v-html, |safe, innerHTML, và việc tạo URL hoặc event handler inline từ dữ liệu người dùng. Hãy encode đúng theo ngữ cảnh, và làm sạch HTML định dạng (rich HTML) bằng một thư viện chuyên dụng như DOMPurify thay vì dùng regex.

Dữ liệu được đặt ở đâuMã hóa dưới dạngVí dụ
Nội dung HTMLHTML entity< thành &lt;, " thành &quot;
Thuộc tính HTMLEntity trong thuộc tính có dấu ngoặc képLuôn đặt giá trị thuộc tính trong dấu ngoặc kép; encode " và '
JavaScriptKhông chèn vào nội dung script; truyền dữ liệu qua thuộc tính data- hoặc một khối JSON <script type="application/json"></script> bên trong một chuỗi trở thành \u003c/script\u003e
Tham số URLPercent-encodingencodeURIComponent(value)
Giá trị CSSTránh hoàn toàn; chọn tên class từ một allowlistKhông bao giờ chèn dữ liệu người dùng vào style

SSRF, upload file và deserialization

Server-side request forgery (SSRF): nếu máy chủ của bạn tải một URL do người dùng cung cấp (webhook, proxy ảnh, xem trước liên kết), kẻ tấn công có thể trỏ nó tới các dịch vụ nội bộ hoặc endpoint metadata của cloud 169.254.169.254 và đọc được thông tin xác thực. Hãy phân giải hostname trước, từ chối các dải địa chỉ private và link-local, tắt chuyển hướng, và đặt các tiến trình tải URL vào một phân đoạn mạng không thể tiếp cận bất cứ thứ gì nội bộ.

Upload file: xác thực loại file bằng cách kiểm tra nội dung, không dựa vào phần mở rộng hay Content-Type do client gửi; áp đặt giới hạn dung lượng; lưu file bên ngoài thư mục gốc của web (web root) hoặc trong object storage dưới một tên ngẫu nhiên do bạn tạo; và phục vụ chúng từ một origin riêng với nosniff và Content-Disposition: attachment khi có thể. Không bao giờ để file upload nằm ở bất kỳ đâu mà máy chủ web sẽ thực thi nó.

Deserialization và mass assignment: không bao giờ deserialize dữ liệu không đáng tin cậy bằng một định dạng có thể khởi tạo lớp tùy ý (Java native serialization, pickle của Python, unserialize của PHP, YAML với tag tùy chỉnh); hãy dùng JSON kèm schema. Ràng buộc body của request vào một allowlist trường rõ ràng để người dùng không thể POST "role": "admin" vào một model tình cờ có cột đó.

Cảnh báo

Validation dùng để kiểm tra hình dạng dữ liệu (đây có phải email, số nguyên dương, hay một trong năm giá trị này không?), không phải để bảo mật. Các blocklist loại bỏ <script> hay ký tự dấu nháy luôn có thể bị vượt qua. Chỉ chấp nhận đúng những gì bạn mong đợi, rồi encode khi xuất ra và tham số hóa khi lưu trữ.

Broken Access Control: Rủi ro số một

Broken Access Control (lỗi kiểm soát truy cập) giữ vị trí đầu tiên trong OWASP Top 10 từ năm 2021 và tiếp tục đứng ở A01:2025. Đó không phải một lỗi đơn lẻ mà là một thói quen: kiểm tra một người là ai (xác thực — authentication) nhưng quên kiểm tra họ được phép chạm vào những gì (phân quyền — authorization) trên mỗi request. Dạng kinh điển là insecure direct object reference (IDOR): GET /api/invoices/1042 hoạt động với chủ sở hữu hóa đơn, và cũng hoạt động với bất kỳ ai đổi con số đó.

  • Mặc định từ chối. Mọi route đều cần một quy tắc cấp quyền rõ ràng; thiếu quy tắc nghĩa là 403, không phải 200.

  • Kiểm tra trên máy chủ, cho từng đối tượng. Ẩn một nút trên giao diện không phải là kiểm soát truy cập. Mọi thao tác đọc và ghi phải xác nhận người dùng hiện tại sở hữu hoặc được phép truy cập đúng bản ghi đó, kể cả trong các endpoint xử lý hàng loạt, chức năng xuất dữ liệu và tác vụ chạy nền.

  • Dùng ID khó đoán khi có ích (UUID), nhưng không bao giờ phụ thuộc vào chúng: che giấu không phải là phân quyền.

  • Siết chặt CORS. Access-Control-Allow-Origin: * đi kèm credentials, hoặc phản chiếu bất kỳ Origin nào mà request gửi lên, chẳng khác nào trao API của bạn cho mọi website người dùng truy cập.

  • Tắt liệt kê thư mục, chặn .git, .env, file sao lưu và file cấu hình ở cấp máy chủ web, và giữ trang quản trị khỏi internet công cộng hoặc đặt nó sau MFA và allowlist IP.

  • Rate limit và ghi log các lần phân quyền thất bại. Một loạt 403 dồn dập từ một phiên là dấu hiệu một cuộc tấn công dò tìm đang diễn ra.

javascript
// Express: ownership check on every object access
app.get("/api/invoices/:id", requireAuth, async (req, res) => {
  const invoice = await Invoice.findById(req.params.id);
  if (!invoice || invoice.ownerId !== req.user.id) {
    return res.status(404).end(); // 404, not 403: don't confirm the record exists
  }
  res.json(invoice);
});

Ghi chú

Hãy kiểm thử kiểm soát truy cập theo cách kẻ tấn công làm: đăng nhập bằng một người dùng có quyền thấp, ghi lại mọi request, rồi phát lại từng request với ID của người dùng khác và khi đã bỏ header Authorization. Công cụ quét tự động tìm ra injection; chúng gần như không bao giờ tìm ra lỗi phân quyền, đó là lý do những lỗi này tồn tại hàng năm trời.

Lớp 5: Thư viện phụ thuộc và chuỗi cung ứng phần mềm

Một ứng dụng web điển hình có lẽ chỉ gồm 5% code do bạn viết và 95% code bạn tải về. Đó là lý do Software Supply Chain Failures (lỗi chuỗi cung ứng phần mềm) lần đầu xuất hiện ở vị trí A03 trong OWASP Top 10 bản 2025, và vì sao việc khai thác lỗ hổng đã vượt qua thông tin đăng nhập bị đánh cắp trong DBIR 2026. Vào tháng 9/2025, chỉ một maintainer bị phishing đã khiến mã độc đánh cắp tiền mã hóa bị chèn vào chalk, debug và 16 package npm khác có tổng cộng hơn hai tỷ lượt tải mỗi tuần; mọi dự án cài một phiên bản không được ghim trong khoảng thời gian đó đều tự động kéo mã độc về.

  • Commit lockfile (package-lock.json, yarn.lock, pnpm-lock.yaml, poetry.lock, go.sum) và cài đặt bằng npm ci trong CI để bản build có thể tái lập và một bản phát hành mới từ upstream không thể lọt vào khi chưa được review.

  • Quét liên tục: npm audit, pip-audit, bundler-audit, GitHub Dependabot hoặc Renovate để cập nhật, và một công cụ quét container như Trivy hoặc Grype cho lớp hệ điều hành.

  • Trì hoãn các bản nâng cấp không liên quan đến bảo mật vài ngày (minimumReleaseAge của Renovate) để một bản phát hành bị nhiễm độc thường đã bị gỡ trước khi bạn dùng đến, nhưng áp dụng bản vá bảo mật ngay lập tức.

  • Ghim các action và script bên thứ ba vào một commit SHA trong CI, và dùng Subresource Integrity (integrity="sha384-...") cho mọi script bạn tải từ CDN công cộng.

  • Cấp cho CI đặc quyền tối thiểu: token chỉ đọc khi có thể, thông tin xác thực OIDC ngắn hạn thay cho secret dài hạn, và quyền publish chỉ giới hạn cho job phát hành.

  • Tạo SBOM (CycloneDX hoặc SPDX) khi build để bạn có thể trả lời câu hỏi "chúng ta có bị ảnh hưởng không?" trong vài phút khi CVE tầm cỡ Log4Shell tiếp theo xuất hiện.

bash
# Reproducible install + audit in CI
npm ci --ignore-scripts
npm audit --audit-level=high

# Pin a GitHub Action to a commit, not a floating tag
# uses: actions/checkout@v4                 <- can change underneath you
# uses: actions/checkout@<full-commit-sha>  # v4.2.2

# Subresource Integrity for a CDN script (hash from: openssl dgst -sha384 -binary lib.min.js | openssl base64 -A)
<script src="https://cdn.example.com/lib.min.js"
        integrity="sha384-<base64-hash>"
        crossorigin="anonymous"></script>

Mẹo

npm ci --ignore-scripts chặn các script chạy lúc cài đặt mà phần lớn mã độc npm dùng để thực thi; sau đó chỉ bật lại script cho số ít package thực sự cần bước build.

Nếu bạn dùng WordPress hay một CMS khác, các quy tắc tương tự áp dụng cho plugin và theme, nơi phần lớn các vụ xâm nhập CMS bắt đầu. Hãy cập nhật core và mọi plugin, xóa những thứ bạn không dùng, và kiểm tra xem một công cụ quét có thể biết gì về các phiên bản bạn đang chạy bằng công cụ Phát Hiện CMS.

Lớp 6: Gia cố máy chủ (cổng, bản vá, secret, sao lưu)

Lớp ứng dụng chẳng còn ý nghĩa nếu máy chủ bên dưới chấp nhận đăng nhập SSH bằng mật khẩu, chạy cơ sở dữ liệu trên một cổng công khai, hoặc chưa từng được vá kể từ khi dựng lên. Các biện pháp bảo mật máy chủ thì nhàm chán, nhưng đó chính là nơi ransomware lọt vào.

Đóng mọi cổng bạn không sử dụng

Một máy chủ web công khai cần mở cổng 80 và 443, và SSH từ dải IP của riêng bạn. Cơ sở dữ liệu (3306, 5432, 27017), Redis (6379), Elasticsearch (9200), trang quản trị và endpoint metrics chỉ nên bind vào localhost hoặc mạng riêng. Hãy kiểm tra từ bên ngoài bằng công cụ Kiểm Tra Cổng sau mỗi thay đổi, vì đó chính là góc nhìn của kẻ tấn công, và security group trên cloud rất dễ bị đọc nhầm.

bash
# Ubuntu: default-deny firewall, allow only web + SSH from your office range
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp
sudo ufw enable

# SSH: keys only, no root password login
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
sudo systemctl reload ssh

Vá lỗi theo lịch, giữ secret ngoài code

Bật cập nhật bảo mật tự động (unattended) cho hệ điều hành, đăng ký các danh sách thông báo bảo mật của runtime và framework bạn dùng, và coi một CVE nghiêm trọng trong bất cứ thứ gì tiếp xúc với internet là việc phải xử lý ngay trong ngày. Chạy các dịch vụ dưới người dùng không có đặc quyền, trong container hoặc với cơ chế sandbox của systemd, để một tiến trình bị xâm nhập không thể đọc toàn bộ máy.

Secret (mật khẩu cơ sở dữ liệu, API key, khóa ký) không bao giờ được nằm trong repository, trong một layer của Docker image hay trong JavaScript phía client. Hãy nạp chúng từ biến môi trường được đưa vào lúc deploy hoặc từ một trình quản lý secret, xoay vòng chúng khi có người từng có quyền truy cập rời đi, và quét lịch sử repository bằng một công cụ như gitleaks, vì một key đã commit một lần là bị lộ vĩnh viễn, kể cả sau khi commit đó bị xóa.

Cảnh báo

Mọi thứ có tiền tố NEXT_PUBLIC_, VITE_ hoặc REACT_APP_ đều được đóng gói vào mã chạy trên trình duyệt và theo định nghĩa là công khai. Thay vào đó, hãy đặt API key của bên thứ ba phía sau một route máy chủ của riêng bạn. Hướng dẫn kiểm tra một API endpoint công khai của chúng tôi chỉ cách xác nhận frontend của bạn thực sự để lộ những gì.

Sao lưu sống sót trước ransomware, và CDN đứng phía trước

Tuân theo quy tắc 3-2-1: ba bản sao, trên hai loại phương tiện lưu trữ khác nhau, một bản ở ngoài hệ thống (off-site), và để ít nhất một bản sao là bất biến (immutable) hoặc ngoại tuyến để ransomware xâm nhập được máy chủ cũng không thể mã hóa luôn bản sao lưu. Mã hóa bản sao lưu, bao gồm cả cơ sở dữ liệu lẫn thư mục upload, và điều cực kỳ quan trọng là thử khôi phục theo lịch định kỳ. Một bản sao lưu chưa ai từng khôi phục chỉ là hy vọng, không phải kế hoạch. Thời gian khôi phục là yếu tố quyết định một sự cố chỉ là gián đoạn dịch vụ hay là sự kiện khiến công ty sụp đổ.

Đặt một CDN hoặc WAF (Cloudflare, Fastly, AWS CloudFront kèm WAF, hoặc giải pháp tương đương của nhà cung cấp hosting) phía trước máy chủ gốc (origin). Nó hấp thụ các đợt DDoS dựa trên lưu lượng lớn, áp dụng bộ quy tắc được quản lý cho các payload injection phổ biến và bot xấu, ẩn IP gốc của bạn, và cho bạn một nơi áp đặt rate limit và quy tắc theo vùng địa lý mà không cần động vào ứng dụng. Sau đó hãy siết tường lửa của origin để chỉ các dải IP của CDN mới có thể truy cập trực tiếp cổng 443; nếu không, kẻ tấn công chỉ cần đi vòng qua CDN. Công cụ Kiểm tra Hosting cho biết origin thực của một website có bị lộ phía sau CDN hay không.

Xác thực email: SPF, DKIM và DMARC

Bảo mật web bao gồm cả email mang theo liên kết đặt lại mật khẩu, hóa đơn và phản hồi hỗ trợ của bạn. Không có SPF, DKIM và DMARC, bất kỳ ai cũng có thể gửi thư dưới danh nghĩa billing@yourdomain.com, và từ tháng 2/2024 Google và Yahoo đã bắt buộc cả ba với các bên gửi thư số lượng lớn. Toàn bộ cấu hình gồm ba bản ghi DNS TXT:

dns
yourdomain.com.                    TXT  "v=spf1 include:_spf.google.com -all"
google._domainkey.yourdomain.com.  TXT  "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
_dmarc.yourdomain.com.             TXT  "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s"

Ghi chú

DMARC p=reject cũng là một biện pháp bảo vệ thương hiệu: đó là thứ duy nhất ngăn một chiến dịch phishing đặt chính xác tên miền của bạn vào dòng From. Các báo cáo tổng hợp (rua=) cho bạn thấy ai đang cố làm điều đó.

Bắt đầu DMARC ở p=none để thu thập báo cáo, chuyển sang p=quarantine rồi p=reject khi mọi nguồn gửi hợp lệ (nền tảng marketing, helpdesk, CRM) đều vượt qua kiểm tra alignment. Thêm bản ghi MTA-STS và TLS-RPT để buộc thư gửi tới máy chủ mail của bạn phải được mã hóa, và công bố một null MX (MX 0 .) trên các tên miền không bao giờ gửi thư để chúng hoàn toàn không thể bị giả mạo. Kiểm chứng từng bản ghi bằng Kiểm Tra Bản Ghi SPF, Kiểm Tra DKIM và Kiểm Tra DMARC, và kiểm tra các IP gửi thư của bạn trong các danh sách chặn bằng Kiểm Tra Danh Sách Đen IP nếu tỷ lệ thư vào hộp thư đến giảm.

Ghi log, giám sát và lên kế hoạch cho sự cố

Hai trong số mười hạng mục của OWASP 2025 nói về những gì xảy ra sau một sai sót: Security Logging and Alerting Failures (A09) và hạng mục mới Mishandling of Exceptional Conditions (A10). Một vụ xâm phạm điển hình vẫn không bị phát hiện trong nhiều tháng vì bằng chứng chưa bao giờ được ghi lại, hoặc đã được ghi lại nhưng không ai xem.

  • Ghi log sự kiện bảo mật kèm ngữ cảnh: mọi lần đăng nhập thành công và thất bại, thay đổi MFA, đặt lại mật khẩu, thay đổi quyền, các lần bị từ chối truy cập, lỗi kiểm tra đầu vào và thao tác quản trị, mỗi sự kiện kèm dấu thời gian, user ID, IP nguồn và user agent.

  • Không bao giờ ghi log secret: mật khẩu, token phiên, số thẻ đầy đủ, API key. Hãy che chúng ở tầng logging, không phải ở từng chỗ gọi.

  • Đẩy log ra khỏi máy chủ tới một kho lưu trữ tập trung (CloudWatch, Loki, Elastic, một SIEM) với thời gian lưu giữ ít nhất 90 ngày, để kẻ tấn công có quyền root cũng không thể xóa dấu vết.

  • Cảnh báo theo mẫu hình, không theo sự kiện đơn lẻ: 50 lần đăng nhập thất bại trong một phút, số 403 tăng vọt từ một phiên, một tài khoản admin mới được tạo ngoài giờ làm việc, một chứng chỉ được cấp cho tên miền của bạn mà bạn không hề yêu cầu.

  • Fail closed (lỗi thì từ chối): khi một bước kiểm tra phía sau (dịch vụ xác thực, bộ giới hạn tốc độ, WAF) gặp lỗi, hãy từ chối request. A10 tồn tại vì quá nhiều hệ thống lại cấp quyền truy cập khi chính bước kiểm tra lẽ ra phải chặn request lại ném ra một exception.

  • Giám sát từ bên ngoài: thời gian hoạt động (uptime), hạn chứng chỉ, thay đổi DNS, việc bị đưa vào danh sách chặn và các header bị thụt lùi. Công cụ Kiểm Tra Sức Khỏe Tên Miền gộp các bước kiểm tra DNS, SSL, xác thực email và header thành một điểm số bạn có thể chạy lại sau mỗi lần deploy.

Viết kế hoạch ứng phó sự cố trước khi cần đến

Hãy quyết định trước ai trực sự cố, cách xoay vòng mọi thông tin xác thực, cách chuyển website sang chế độ chỉ đọc, các bản sao lưu sạch nằm ở đâu, và phải thông báo cho cơ quan quản lý hay khách hàng nào trong vòng bao nhiêu giờ (72 giờ theo GDPR). Tổ chức diễn tập trên giấy (tabletop exercise) mỗi năm một lần. Các đội có kế hoạch đã diễn tập khôi phục trong vài ngày; các đội không có kế hoạch sẽ phải xoay xở trong nhiều tuần.

Mẹo

Thêm file security.txt tại /.well-known/security.txt (RFC 9116) với địa chỉ liên hệ và khóa PGP. Các nhà nghiên cứu tìm thấy lỗi trên website của bạn sẽ dùng nó; nếu không có, họ có thể công bố lỗi đó công khai.

Kiểm thử: Công cụ quét, pentest và kiểm tra liên tục

Mọi biện pháp ở trên đều có thể kiểm chứng, và việc kiểm chứng nên được tự động hóa bất cứ khi nào có thể để nó không bị mai một theo thời gian. Một lộ trình hợp lý, từ miễn phí và tức thì đến định kỳ và trả phí:

Hạng mục kiểm traCông cụTần suất
Cấu hình TLS, chuỗi chứng chỉ, hạn sử dụngKiểm Tra Chứng Chỉ SSL, SSL Labs, testssl.shSau mỗi thay đổi chứng chỉ hoặc máy chủ; kiểm tra hạn hằng ngày
Security header và CSPKiểm Tra Header HTTP, MDN HTTP ObservatorySau mỗi lần deploy (đưa vào CI)
Cổng đang mởKiểm Tra Cổng, nmapSau mỗi thay đổi tường lửa hoặc cloud
DNS, DNSSEC, xác thực emailKiểm Tra Sức Khỏe Tên Miền, Kiểm Tra DMARCHằng tháng, và sau mỗi lần chỉnh sửa DNS
Subdomain bị bỏ quênTìm Tên Miền PhụHằng quý và mỗi lần ngừng một dịch vụ
Thư viện phụ thuộc có lỗ hổng đã biếtnpm audit, Dependabot, TrivyMỗi lần build
Lỗi ở mức code (SAST)Semgrep, CodeQLMỗi pull request
Lỗ hổng web khi đang chạy (DAST)OWASP ZAP, NucleiHằng tuần trên môi trường staging
Phân quyền và logic nghiệp vụKiểm thử xâm nhập thủ công hoặc chương trình bug bountyHằng năm, và sau các tính năng lớn

Công cụ quét giỏi phát hiện injection, header và thành phần lỗi thời nhưng kém ở phân quyền và logic nghiệp vụ, vì vậy hãy dành ngân sách cho ít nhất một đợt kiểm thử bởi con người mỗi năm với bất cứ thứ gì xử lý tiền hoặc dữ liệu cá nhân. Ưu tiên khắc phục theo mức độ phơi nhiễm: bất cứ lỗ hổng nào mà người dùng chưa xác thực có thể khai thác từ internet phải được xử lý đầu tiên.

Checklist bảo mật website

Hãy sao chép bảng này vào README hoặc hệ thống quản lý ticket của dự án. Mỗi dòng là một biện pháp ở trên, được diễn đạt để có thể đánh dấu đã xong hay chưa; cột cuối cùng là bằng chứng.

LớpBiện phápHoàn thành khi
Tên miềnRegistrar lock và MFA trên tài khoản registrar; bật tự động gia hạnWHOIS hiển thị clientTransferProhibited; còn hơn 12 tháng mới hết hạn
DNSĐã ký DNSSEC; bản ghi CAA chỉ liệt kê các CA của bạndig +dnssec trả về RRSIG; có bản ghi DS ở zone cha
DNSKhông có bản ghi CNAME hoặc A trỏ vào dịch vụ đã bỏ (dangling)Mọi hostname tìm được bằng công cụ Tìm Tên Miền Phụ đều phân giải về thứ bạn kiểm soát
TLSChỉ TLS 1.2+, ưu tiên TLS 1.3, phục vụ đầy đủ chuỗi chứng chỉKiểm Tra Chứng Chỉ SSL: chuỗi đầy đủ, TLS 1.0/1.1 bị từ chối
TLSHSTS với max-age một năm, includeSubDomains, đã preloadCó tên trong danh sách hstspreload.org
TLSGia hạn tự động qua ACME; hạn chứng chỉ được giám sátcertbot renew --dry-run chạy thành công; cảnh báo đặt ở mốc 14 ngày
HeaderCSP (dựa trên nonce), frame-ancestors, nosniff, Referrer-Policy, Permissions-PolicyKiểm Tra Header HTTP chấm điểm A
Xác thựcBăm bằng Argon2id hoặc bcrypt; quy tắc mật khẩu theo NIST; đối chiếu danh sách rò rỉMột lần băm mất 100 đến 250 ms; không có quy tắc về thành phần
Xác thựcMFA cho mọi người dùng, bắt buộc với admin; hỗ trợ passkeyKhông thể đăng nhập admin nếu thiếu yếu tố thứ hai
Xác thựcEndpoint đăng nhập, đặt lại mật khẩu và MFA bị giới hạn tốc độ theo IP và theo tài khoảnLần thử thứ sáu trong một phút trả về 429
SessionCookie __Host- với Secure, HttpOnly, SameSite; ID được cấp lại khi đăng nhậpThuộc tính cookie hiển thị trong DevTools; session cũ mất hiệu lực sau khi đăng nhập
Đầu vàoTruy vấn tham số hóa ở mọi nơi; đầu ra được mã hóa theo ngữ cảnh; file upload được kiểm tra theo nội dungTìm trong code không thấy truy vấn nối chuỗi nào; payload XSS hiển thị dưới dạng văn bản
Kiểm soát truy cậpRouting mặc định từ chối; kiểm tra quyền sở hữu từng đối tượng; CORS nghiêm ngặtPhát lại request với ID của người dùng khác trả về 404 hoặc 403
Thư viện phụ thuộcĐã commit lockfile; npm ci; audit trong CI; action được ghim theo SHA; SRI cho script từ CDNBản build thất bại khi có CVE mức nghiêm trọng cao
Máy chủChỉ 80 và 443 công khai; SSH chỉ dùng key; cập nhật bảo mật tự động; secret nằm trong biến môi trường hoặc vaultKiểm Tra Cổng cho thấy 22, 3306 và 6379 đóng khi nhìn từ internet
Sao lưu3-2-1 với một bản sao bất biến; đã thử khôi phụcLần thử khôi phục thành công gần nhất nằm trong vòng 90 ngày
EmailSPF -all, DKIM, DMARC p=reject, MTA-STSKiểm Tra DMARC đạt; báo cáo tổng hợp đang được gửi về
Giám sátSự kiện xác thực và phân quyền được ghi log tập trung; cảnh báo theo mẫu hình; giám sát uptime, chứng chỉ và DNS từ bên ngoàiĐã kích hoạt thử một cảnh báo và nhận được
Ứng phóCó kế hoạch ứng phó sự cố bằng văn bản; đã công bố security.txt; đã diễn tập tabletopKế hoạch được rà soát trong vòng 12 tháng gần nhất

Đối chiếu với OWASP Top 10:2025

OWASP Top 10 là danh sách rủi ro ứng dụng web được trích dẫn nhiều nhất, và phiên bản 2025 đã sắp xếp lại thứ tự cũng như bổ sung hai hạng mục mới. Nếu khách hàng, kiểm toán viên hay một khung tuân thủ hỏi bạn xử lý các rủi ro này thế nào, đây là bảng đối chiếu với các biện pháp trong hướng dẫn này:

OWASP Top 10:2025Biện pháp trong hướng dẫn này
A01 Broken Access ControlMặc định từ chối, kiểm tra từng đối tượng, CORS nghiêm ngặt, khóa chặt trang quản trị
A02 Security MisconfigurationSecurity header, HSTS, đóng cổng, gỡ header lộ phiên bản, tắt liệt kê thư mục
A03 Software Supply Chain Failures (mới)Lockfile, audit, ghim action, SRI, SBOM, CI đặc quyền tối thiểu
A04 Cryptographic FailuresTLS 1.2+ và 1.3, HSTS, Argon2id, quản lý secret, mã hóa bản sao lưu
A05 InjectionTruy vấn tham số hóa, mã hóa đầu ra, CSP, kiểm tra file upload
A06 Insecure DesignMô hình hóa mối đe dọa cho từng lớp, mặc định fail closed, rate limiting ngay từ khâu thiết kế
A07 Authentication FailuresQuy tắc mật khẩu NIST, kiểm tra rò rỉ, MFA và passkey, cấp lại session
A08 Software or Data Integrity FailuresSRI, thư viện phụ thuộc được ký và ghim, deserialization an toàn
A09 Security Logging and Alerting FailuresLog tập trung, cảnh báo theo mẫu hình, giám sát từ bên ngoài
A10 Mishandling of Exceptional Conditions (mới)Fail closed, phản hồi lỗi thống nhất, không hiển thị stack trace cho người dùng

Ghi chú

ASVS (Application Security Verification Standard) của OWASP biến cùng nội dung này thành một checklist được đánh số dùng cho kiểm toán. Level 1 là mục tiêu thực tế cho mọi website công khai; Level 2 dành cho bất cứ thứ gì xử lý dữ liệu cá nhân hoặc tài chính.

Kiểm tra security header của website chỉ trong vài giây

Công cụ Kiểm Tra Header HTTP miễn phí của DNS Robot tải bất kỳ URL nào và chấm điểm security header từ A đến F: HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy và Permissions-Policy, kèm giá trị chính xác tìm thấy và những gì còn thiếu. Không cần đăng ký.

Thử công cụ Kiểm Tra Header HTTP

Advertisement

Câu hỏi thường gặp về bảo mật website

Bắt buộc HTTPS với TLS hiện đại và HSTS, gửi các security header nghiêm ngặt (đặc biệt là Content Security Policy), băm mật khẩu bằng Argon2id kèm MFA và rate limiting, dùng truy vấn tham số hóa và mã hóa đầu ra, kiểm soát truy cập trên máy chủ cho từng đối tượng, giữ thư viện phụ thuộc luôn được vá và ghim phiên bản, đóng các cổng không dùng, và ghi log sự kiện bảo mật tập trung. Hãy khóa chặt tên miền và DNS trước tiên, vì mọi thứ khác đều phụ thuộc vào việc bạn vẫn kiểm soát được tên miền của mình.

Công cụ liên quan

HTTP Headers CheckSSL Certificate CheckPort CheckerDomain Health CheckerDMARC CheckerSubdomain FinderPassword Strength Tester

Bài viết liên quan

Chuỗi chứng chỉ SSL là gì? Cách hoạt độngX-Frame-Options Explained: Fix “Refused to Connect” in an iframeLỗi HTTP 429 Too Many Requests: Nguyên nhân và Cách khắc phụcWHOIS tên miền: Cách tra cứu tên miền và tìm chủ sở hữu

Mục lục

  • Bảo mật website thực sự nghĩa là gì?
  • Mô hình bảo mật website: Sáu lớp cần bảo vệ
  • Lớp 1: Khóa chặt tên miền và DNS
  • Lớp 2: Triển khai HTTPS và TLS đúng cách
  • Lớp 3: HTTP security header
  • Lớp 4: Xác thực chống được credential stuffing
  • Session, cookie và CSRF
  • Injection và XSS: Kiểm tra đầu vào, mã hóa đầu ra
  • Broken Access Control: Rủi ro số một
  • Lớp 5: Thư viện phụ thuộc và chuỗi cung ứng phần mềm
  • Lớp 6: Gia cố máy chủ (cổng, bản vá, secret, sao lưu)
  • Xác thực email: SPF, DKIM và DMARC
  • Ghi log, giám sát và lên kế hoạch cho sự cố
  • Kiểm thử: Công cụ quét, pentest và kiểm tra liên tục
  • Checklist bảo mật website
  • Đối chiếu với OWASP Top 10:2025
  • Câu hỏi thường gặp