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

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.
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ớp | Kẻ tấn công làm gì ở đây | Biện pháp cốt lõi | Kiểm chứng bằng |
|---|---|---|---|
| 1. Tên miền và DNS | Chiếm tên miền, đổi nameserver, chiếm các subdomain bị bỏ quên, xin chứng chỉ giả mạo | Registrar 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ạn | TLS 1.2+, HSTS kèm preload, gia hạn tự động | Kiểm Tra Chứng Chỉ SSL |
| 3. HTTP header | Chèn script (XSS), nhúng website vào frame để clickjacking, đoán kiểu nội dung, rò rỉ referrer | CSP, frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Kiểm Tra Header HTTP |
| 4. Ứng dụng | Credential stuffing, injection, lỗi kiểm soát truy cập, CSRF, upload file không an toàn | Argon2id + MFA + rate limit, truy vấn tham số hóa, phân quyền phía máy chủ, cookie SameSite | Rà soát code, OWASP ZAP, Kiểm Tra Độ Mạnh Mật Khẩu |
| 5. Thư viện phụ thuộc | Package bị cài mã độc, CVE đã biết trong thư viện, pipeline CI bị xâm nhập | Lockfile, audit, ghim phiên bản, SBOM, token đặc quyền tối thiểu | npm audit, Dependabot, Trivy |
| 6. Máy chủ và vận hành | Cổ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ó log | Tường lửa, SSH chỉ dùng key, vá lỗi, sao lưu 3-2-1, log tập trung | Kiểm Tra Cổng, Kiểm Tra Danh Sách Đen IP, Kiểm Tra Sức Khỏe Tên Miề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:
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...C9Giớ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:
yourdomain.com. CAA 0 issue "letsencrypt.org"
yourdomain.com. CAA 0 issuewild ";"
yourdomain.com. CAA 0 iodef "mailto:security@yourdomain.com"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.
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:
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'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.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadChứ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ực | Thời hạn chứng chỉ tối đa | Thời gian tái sử dụng xác thực tên miền |
|---|---|---|
| Trước 15/3/2026 | 398 ngày | 398 ngày |
| 15/3/2026 | 200 ngày | 200 ngày |
| 15/3/2027 | 100 ngày | 100 ngày |
| 15/3/2029 | 47 ngày | 10 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:
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ó.
| Header | Giá trị khuyến nghị | Ngăn chặn điều gì |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | Hạ cấp giao thức, đánh cắp cookie qua HTTP |
Content-Security-Policy | script-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-Options | nosniff | MIME sniffing biến một file upload thành script thực thi được |
Referrer-Policy | strict-origin-when-cross-origin | Rò rỉ URL đầy đủ (token, từ khóa tìm kiếm) cho bên thứ ba |
Permissions-Policy | camera=(), 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-Options | DENY (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-Policy | same-origin | Tấ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ế.
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>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.
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.
// 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);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: 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;
}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:
Securenghĩ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.HttpOnlykhiế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ặcStrictcho 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ínhDomain, nên một subdomain không thể ghi đè lên nó.
Set-Cookie: __Host-session=9f1c7e2a...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800CSRF: 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).
// 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 ở đâu | Mã hóa dưới dạng | Ví dụ |
|---|---|---|
| Nội dung HTML | HTML entity | < thành <, " thành " |
| Thuộc tính HTML | Entity trong thuộc tính có dấu ngoặc kép | Luôn đặt giá trị thuộc tính trong dấu ngoặc kép; encode " và ' |
| JavaScript | Khô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ố URL | Percent-encoding | encodeURIComponent(value) |
| Giá trị CSS | Tránh hoàn toàn; chọn tên class từ một allowlist | Khô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 đó.
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ỳOriginnà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.
// 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);
});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ằngnpm citrong 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 (
minimumReleaseAgecủ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.
# 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>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.
# 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 sshVá 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.
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:
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"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.
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 tra | Công cụ | Tần suất |
|---|---|---|
| Cấu hình TLS, chuỗi chứng chỉ, hạn sử dụng | Kiểm Tra Chứng Chỉ SSL, SSL Labs, testssl.sh | Sau mỗi thay đổi chứng chỉ hoặc máy chủ; kiểm tra hạn hằng ngày |
| Security header và CSP | Kiểm Tra Header HTTP, MDN HTTP Observatory | Sau mỗi lần deploy (đưa vào CI) |
| Cổng đang mở | Kiểm Tra Cổng, nmap | Sau mỗi thay đổi tường lửa hoặc cloud |
| DNS, DNSSEC, xác thực email | Kiểm Tra Sức Khỏe Tên Miền, Kiểm Tra DMARC | Hằng tháng, và sau mỗi lần chỉnh sửa DNS |
| Subdomain bị bỏ quên | Tì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ết | npm audit, Dependabot, Trivy | Mỗi lần build |
| Lỗi ở mức code (SAST) | Semgrep, CodeQL | Mỗi pull request |
| Lỗ hổng web khi đang chạy (DAST) | OWASP ZAP, Nuclei | Hằ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 bounty | Hằ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ớp | Biện pháp | Hoàn thành khi |
|---|---|---|
| Tên miền | Registrar lock và MFA trên tài khoản registrar; bật tự động gia hạn | WHOIS 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ạn | dig +dnssec trả về RRSIG; có bản ghi DS ở zone cha |
| DNS | Khô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 |
| TLS | Chỉ 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 |
| TLS | HSTS với max-age một năm, includeSubDomains, đã preload | Có tên trong danh sách hstspreload.org |
| TLS | Gia hạn tự động qua ACME; hạn chứng chỉ được giám sát | certbot renew --dry-run chạy thành công; cảnh báo đặt ở mốc 14 ngày |
| Header | CSP (dựa trên nonce), frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Kiểm Tra Header HTTP chấm điểm A |
| Xác thực | Bă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ực | MFA cho mọi người dùng, bắt buộc với admin; hỗ trợ passkey | Không thể đăng nhập admin nếu thiếu yếu tố thứ hai |
| Xác thực | Endpoint đă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ản | Lần thử thứ sáu trong một phút trả về 429 |
| Session | Cookie __Host- với Secure, HttpOnly, SameSite; ID được cấp lại khi đăng nhập | Thuộc tính cookie hiển thị trong DevTools; session cũ mất hiệu lực sau khi đăng nhập |
| Đầu vào | Truy 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 dung | Tì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ập | Routing mặc định từ chối; kiểm tra quyền sở hữu từng đối tượng; CORS nghiêm ngặt | Phá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ừ CDN | Bả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 vault | Kiểm Tra Cổng cho thấy 22, 3306 và 6379 đóng khi nhìn từ internet |
| Sao lưu | 3-2-1 với một bản sao bất biến; đã thử khôi phục | Lần thử khôi phục thành công gần nhất nằm trong vòng 90 ngày |
SPF -all, DKIM, DMARC p=reject, MTA-STS | Kiểm Tra DMARC đạt; báo cáo tổng hợp đang được gửi về | |
| Giám sát | Sự 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 tabletop | Kế 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:2025 | Biện pháp trong hướng dẫn này |
|---|---|
| A01 Broken Access Control | Mặ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 Misconfiguration | Security 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 Failures | TLS 1.2+ và 1.3, HSTS, Argon2id, quản lý secret, mã hóa bản sao lưu |
| A05 Injection | Truy vấn tham số hóa, mã hóa đầu ra, CSP, kiểm tra file upload |
| A06 Insecure Design | Mô 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 Failures | Quy 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 Failures | SRI, thư viện phụ thuộc được ký và ghim, deserialization an toàn |
| A09 Security Logging and Alerting Failures | Log 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 |
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 HTTPAdvertisement
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.