ZUKATEK JOURNAL

Bảo mật tối thiểu cho một website doanh nghiệp: bảy thứ, theo thứ tự

Bảy bước kiểm tra ban đầu cho website doanh nghiệp, từ băm mật khẩu tới nhật ký truy cập bí mật; không thay thế kiểm toán bảo mật đầy đủ.

Bảy việc tối thiểu — theo thứ tự nên làm1Băm mật khẩu bằng argon2idKhông phải MD5 hay SHA-256 trầntrượt mục này thì sửa trước2Giới hạn tần suất mọi cửa công khaiNguy nhất là cửa tiêu tiền3Trả về 429, đừng trả 503Nói đúng chuyện đang xảy ra4Fail-closed, đừng fail-openLớp bảo vệ hỏng thì phải từ chốinguyên tắc quan trọng nhất5Bí mật không nằm trong mã nguồnKiểm cả bản sao lưu database6Mỗi loại dữ liệu một mức quyềnMột điểm nghẽn duy nhất cho đường công khai7Ghi nhật ký cả khi ĐỌC bí mậtAi, lúc nào, khoá nào — không ghi giá trịĐây là bước rà soát ban đầu, không phải một cuộc kiểm toán đầy đủ: còn cậpnhật phần mềm, MFA, sao lưu và thử khôi phục, HTTPS, quản lý phiên.

Bài này không liệt kê 50 việc. Nó liệt kê bảy, theo một thứ tự khởi đầu thực tế. Mỗi việc có bước tự kiểm tra ban đầu; đây không phải bộ kiểm toán bảo mật đầy đủ. Cần thêm cập nhật phần mềm, MFA, sao lưu và thử khôi phục, HTTPS, quản lý phiên và theo dõi sự cố.

Mọi ví dụ đều lấy từ hệ thống của chúng tôi, kể cả những chỗ chúng tôi làm sai trước khi làm đúng.

1. Mật khẩu: argon2id, không phải MD5, không phải SHA-256

Nếu cơ sở dữ liệu rò rỉ, thuật toán băm và cách cấu hình ảnh hưởng lớn đến khả năng kẻ tấn công dò ra mật khẩu và thử lại ở những nơi khác.

  • MD5, SHA-1, SHA-256 trần — hỏng. Chúng được thiết kế để nhanh, mà nhanh chính là thứ kẻ tấn công cần

  • bcrypt — chấp nhận được

  • argon2id — lựa chọn được OWASP khuyến nghị cho hệ thống mới. Tốn bộ nhớ, nên dò bằng dàn GPU rất đắt

Tự kiểm tra: mở bảng người dùng, nhìn một ô mật khẩu. Tiền tố $argon2id$ hay $2a$/$2b$ giúp nhận diện thuật toán, nhưng chưa chứng minh tham số, salt và cách triển khai đủ an toàn. Nếu nó là một chuỗi hex 32 hay 64 ký tự, bạn có việc phải làm ngay hôm nay.

2. Giới hạn tần suất ở mọi cửa công khai

Mọi endpoint không cần đăng nhập đều là một cửa. Form liên hệ, đăng ký nhận tin, khung chat, đăng nhập — mỗi cái đều có thể bị gọi mười nghìn lần.

Chỗ nguy hiểm nhất là cửa nào tiêu tiền. Khung chat của chúng tôi gọi tới nhà cung cấp AI, nghĩa là mỗi tin nhắn là một khoản chi. Một cửa công khai không giới hạn, nối thẳng vào một dịch vụ tính tiền theo lượt, là cách mất tiền nhanh nhất mà không ai nhận ra cho tới khi nhận hoá đơn.

Khi chúng tôi kiểm tra hệ thống, nginx không có một dòng giới hạn nào. Sau khi thêm:

Phép thử

Kết quả

Mở trang bình thường, tải hết tài sản

0 lần bị chặn

150 yêu cầu tuần tự

17 lần chặn

300 yêu cầu song song

225 lần chặn

Phép thử đầu quan trọng ngang hai phép sau. Một giới hạn chặn cả khách thật thì sẽ bị gỡ sau một tuần, và khi đó bạn không có gì cả.

Tự kiểm tra: thử theo ngưỡng của endpoint công khai trên môi trường được phép. Không thấy 429 chưa chứng minh thiếu giới hạn: còn phụ thuộc ngưỡng, cửa sổ thời gian, burst và khoá định danh. Chỉ thử trên staging hoặc hệ thống được phép, theo ngưỡng đã cấu hình; tránh tạo tải lên dịch vụ thật ngoài kế hoạch.

3. Trả về 429, đừng trả 503

Chi tiết nhỏ nhưng đáng nói. 503 nói "máy chủ hỏng". 429 nói đúng sự thật: "bạn gọi quá nhanh".

Khác biệt không chỉ là lịch sự. Công cụ chặn tự động phải được cấu hình đọc đúng mã và mẫu log; không có quy tắc chung rằng chúng luôn hiểu 429 và không hiểu 503.

4. Fail-closed, đừng fail-open

Nguyên tắc quan trọng nhất trong bài này.

Khi một lớp bảo vệ không chạy được, nó phải từ chối, không được cho qua.

Ví dụ thật trong hệ thống của chúng tôi: module kiểm tra reCAPTCHA. Khi không gọi được tới máy chủ của Google, nó không coi đó là hợp lệ:

// Google unreachable is NOT a pass.
return fmt.Errorf("recaptcha unreachable: %w", err)

Hệ quả: khi Google gặp sự cố, form liên hệ của chúng tôi báo lỗi. Khó chịu cho khách — nhưng đúng. Lựa chọn còn lại là mở toang cánh cửa mỗi lần một dịch vụ bên thứ ba trục trặc, và kẻ tấn công thì rất giỏi tạo ra trục trặc.

Thiếu khoá là lỗi cấu hình, không phải lý do âm thầm bỏ kiểm tra. Giao diện quản trị nên từ chối bật chế độ bắt buộc khi chưa đủ khoá và báo rõ trạng thái. Nếu runtime đang yêu cầu CAPTCHA mà không xác minh được, hãy từ chối thao tác hoặc chuyển sang kênh hỗ trợ đã thiết kế trước, không giả vờ đã xác thực. Chính sách dự phòng cần được chọn theo rủi ro của từng luồng.

5. Bí mật không bao giờ nằm trong mã nguồn

Nghe hiển nhiên, nhưng chỗ rò rỉ thường nằm ở nơi không ai nhìn: bản sao lưu cơ sở dữ liệu.

Khi chúng tôi tạo bản dump để đưa lên máy chủ, lần quét đầu tiên "sạch". Lần quét thứ hai, kỹ hơn, tìm thấy khoá bí mật reCAPTCHA — nó nằm trong một cột tên value_encrypted mà lần quét đầu không nhìn tới.

Rà lại toàn bộ, có bốn cột chứa bí mật trong lược đồ:

ai_providers.api_key              khoá API nhà cung cấp AI
push_keys.private_key             khoá riêng thông báo đẩy
system_settings.value_encrypted   secret reCAPTCHA
two_factor_secrets.secret         mã 2FA từng người

Tự kiểm tra: mở bản sao lưu mới nhất bằng trình soạn thảo văn bản, tìm sk-, api_key, secret, BEGIN PRIVATE KEY. Đây chỉ là bước sàng lọc: bí mật có thể ở cột text, JSON, nhị phân hoặc được mã hoá. Rà lược đồ và cấu hình, giới hạn quyền đọc backup, mã hoá và đặt thời hạn lưu; nếu từng lộ thì phải thu hồi hoặc xoay khoá.

6. Mỗi loại dữ liệu một mức quyền

Dễ nhất là cho tất cả mọi người thấy tất cả mọi thứ. Nó cũng là thứ khiến một tài khoản bị chiếm thành một sự cố toàn hệ thống.

Một ví dụ cụ thể: trang web của chúng tôi hiển thị đội ngũ. Trước đây nó trả về tên thật và email của từng người. Mỗi email đó cũng là một tài khoản đăng nhập — nghĩa là trang giới thiệu đang làm lộ định danh có thể dùng để dò tài khoản và phishing cho bất kỳ ai xem mã nguồn trang.

Cách sửa không phải là xoá trường ở từng chỗ hiển thị. Là làm một điểm nghẽn duy nhất: mọi đường đọc công khai đều đi qua một hàm bóc bỏ tên thật và email. Cần kiểm tra mọi endpoint mới cũng đi qua lớp này; mô tả kiến trúc chưa tự chứng minh không tồn tại đường vòng.

Tự kiểm tra: mở trang công khai, xem mã nguồn hoặc gọi API, tìm email và tên thật. Nếu thấy, hỏi: thông tin này có cần cho khách không?

7. Đọc một bí mật cũng là một hành động cần ghi lại

Mục này ít hệ thống làm, và nó rẻ.

Tạo hay sửa một cấu hình thì bản thân cấu hình đã là dấu vết. Nhưng đọc một khoá API thì không để lại gì cả. Nếu sau này khoá bị lạm dụng, câu hỏi "ai đã xem nó" không có câu trả lời.

Chúng tôi làm chức năng xem lại khoá API đã lưu với ba lớp, và cần cả ba:

  1. Quyền — phải là quản trị viên

  2. Nhập lại mật khẩu — giúp giảm nguy cơ từ một phiên bỏ quên; không chứng minh tuyệt đối danh tính. Với thao tác nhạy cảm, cân nhắc MFA hoặc xác thực lại phù hợp. Cookie phiên chỉ chứng minh họ đã ở đó lúc đăng nhập

  3. Giới hạn tần suất — để cửa này không thành chỗ dò mật khẩu

Và mỗi lần xem đều ghi một dòng nhật ký: ai, lúc nào, định danh khoá nào — không ghi giá trị bí mật. Ưu tiên che khoá và cho xoay khoá thay vì cho đọc lại.

Bảng tự chấm

#

Việc

Kiểm tra trong 5 phút

1

Băm mật khẩu

Nhìn một ô password_hash, có $argon2id$ không

2

Giới hạn tần suất

Thử theo ngưỡng cấu hình trên môi trường được phép

3

Mã lỗi đúng

Khi bị chặn trả 429 hay 503

4

Fail-closed

Tắt mạng tới dịch vụ xác thực — form cho qua hay chặn

5

Bí mật trong sao lưu

Quét bản dump mới nhất, liệt kê cột nhị phân

6

Dữ liệu công khai

Xem mã nguồn trang, tìm email nội bộ

7

Nhật ký đọc bí mật

Có dòng nào ghi lại việc xem khoá không

Nếu bạn trượt mục 1, hãy sửa nó trước mọi thứ khác. Nếu trượt mục 4, hãy đọc lại toàn bộ chỗ xử lý lỗi — một hệ thống fail-open thường fail-open ở nhiều nơi cùng lúc, vì đó là thói quen chứ không phải sơ suất.


Các tình huống và số lượt chặn được giữ từ bản thảo Zukatek tháng 10/2026; chưa có log gốc để kiểm chứng độc lập.

Nguồn đối chiếu: OWASP — Password Storage.

BUILD SIMPLE. GROW BEYOND.

Cảm ơn bạn đã đọc và đồng hành cùng Zukatek.

← Trở về Blog