Đừng Để Chức Cơ bản Trở Thành Ác Mộng: Xây Dựng Hệ Thống Bảo Mật từ Góc Nhìn System Architect

Meta bị phạt 102 triệu đô la vì vi phạm GDPR khi lưu trữ mật khẩu người dùng dưới dạng văn bản (plain text). Vụ việc này nhấn mạnh tầm quan trọng của bảo mật mật khẩu và tuân thủ các tiêu chuẩn bảo mật cao nhất trong ngành.

Đừng Để Chức Cơ bản Trở Thành Ác Mộng: Xây Dựng Hệ Thống Bảo Mật từ Góc Nhìn System Architect
Photo by Kenny Eliason / Unsplash

Đọc tin

Meta bị phạt 102 triệu đô la vì lưu trữ mật khẩu dưới dạng văn bản thuần túy

Ủy ban Bảo vệ Dữ liệu Ireland (DPC) đã áp đặt mức phạt 101,5 triệu đô la (91 triệu euro) đối với Meta sau khi kết thúc cuộc điều tra về vụ vi phạm bảo mật năm 2019. Trong vụ việc này, Meta đã vô tình lưu trữ mật khẩu của người dùng dưới dạng văn bản thuần túy, khiến chúng dễ dàng đọc được trên máy chủ của công ty.

Ban đầu, thông báo của Meta đề cập đến việc phát hiện một số mật khẩu người dùng được lưu trữ dưới dạng văn bản thuần túy vào tháng 1 năm 2019. Tuy nhiên, một tháng sau đó, công ty tiết lộ rằng hàng triệu mật khẩu Instagram cũng bị ảnh hưởng. Mặc dù Meta không công bố con số chính xác về số tài khoản bị xâm phạm, một nhân viên cấp cao đã tiết lộ với Krebs on Security rằng có tới 600 triệu mật khẩu đã được lưu trữ dưới dạng văn bản thuần túy. Một số mật khẩu này đã được lưu trữ từ năm 2012, và hơn 20.000 nhân viên Facebook có thể tìm kiếm chúng, mặc dù DPC đã làm rõ rằng các bên bên ngoài không được cấp quyền truy cập.

Tóm lại

Bạn lưu mật khẩu ra thành text để nhân viên/develop team có thể đọc được, bạn ăn gậy từ uỷ ban dữ liệu.


Học hỏi

Nhân dịp này, tại sao chúng ta không cùng nhau ôn lại một chút kiến thức về Software engineering principles nhỉ ?

Password storage best practices

Để bảo vệ thông tin người dùng và duy trì lòng tin của họ, việc lưu trữ mật khẩu một cách an toàn là vô cùng quan trọng. Dưới đây là những thực hành tốt nhất trong việc lưu trữ mật khẩu mà mọi tổ chức nên áp dụng:

Password handling best practice

1. Băm tất cả các mật khẩu

Không bao giờ lưu trữ mật khẩu dưới dạng văn bản thuần túy. Thay vào đó, hãy tạo một giá trị băm từ mật khẩu và lưu trữ giá trị này. Băm giúp bảo vệ mật khẩu bởi vì nó không thể được giải mã ngược lại.

Ví dụ:

import hashlib

password = "mysecretpassword"
hashed_password = hashlib.sha256(password.encode()).hexdigest()
print(hashed_password)

2. Sử dụng hàm băm mạnh

Không phải tất cả các hàm băm đều an toàn. Sử dụng các hàm băm như Argon2, bcrypt hoặc scrypt được khuyến nghị bởi OWASP vì chúng được thiết kế để chống lại các cuộc tấn công bằng phương pháp brute-force.

Ví dụ:

import bcrypt

password = "mysecretpassword"
hashed_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
print(hashed_password)

3. Thêm chút cay (pepper)

Pepper là một chuỗi ngẫu nhiên được sử dụng cho tất cả các mật khẩu nhưng không được lưu trữ cùng với cơ sở dữ liệu mật khẩu. Pepper thường được lưu trữ ở một vị trí an toàn khác, ở đây tôi đề xuất lưu pepper ở Secret manager services.

Example:

import bcrypt
from google.cloud import secretmanager

client = secretmanager.SecretManagerServiceClient()

project_id = "your-gcp-project-id"
secret_id = "your-secret-id"
version_id = "latest"

name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"
response = client.access_secret_version(name=name)

pepper = response.payload.data.decode("UTF-8")

password = "mysecretpassword"
password_pepper = password + pepper

salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password_pepper.encode(), salt)

print(hashed_password)

3. Thêm muối (salt) vào mật khẩu

Salt là một chuỗi ngẫu nhiên được thêm vào mật khẩu trước khi băm. Điều này đảm bảo rằng các mật khẩu giống nhau sẽ có giá trị băm khác nhau, ngăn chặn việc sử dụng bảng tra cứu (rainbow tables) để giải mã mật khẩu.

Ví dụ

import bcrypt

# Define your password
password = "mysecretpassword"

# Generate salt
salt = bcrypt.gensalt()

# Hash the password using the generated salt
hashed_password = bcrypt.hashpw(password.encode(), salt)

# Print the salt and the hashed password
print("Salt:", salt)
print("Salted and hashed password:", hashed_password)

5. Cập nhật các yếu tố làm việc (work factors)

Các hàm băm thường có tham số điều chỉnh độ khó của quá trình băm. Cần định kỳ tăng các yếu tố này để đảm bảo rằng việc băm mật khẩu vẫn khó khăn theo thời gian khi công nghệ tiến bộ.

Example:

import bcrypt

password = "mysecretpassword"
work_factor = 12
hashed_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt(work_factor))
print(hashed_password)

Nhưng liệu chỉ có vậy ?

Tôi không nghĩ rằng đây là vấn đề về kỹ thuật lưu trữ mật khẩu. Ngay cả lập trình viên kém kinh nghiệm nhất cũng biết lưu mật khẩu dạng văn bản thuần là ý tưởng tồi. Và Mark Zuckerberg đọc tin nhắn của chúng ta để nuôi mấy gã từ Harvard ra chứ không phải những tay mơ.

Tôi nghi ngờ rằng đây không phải là điều có chủ ý trong thiết kế. Nhưng có khả năng cao là mật khẩu vô tình hoặc cố ý bị lưu vào các file log hoặc những nơi tương tự. Tôi đã tìm hiểu thêm về ý tưởng này.


Điều tôi tìm được là: Một nhân viên cấp cao của Facebook đã tiết lộ thêm chi tiết cho KrebsOnSecurity, và theo họ:

"Facebook is probing a series of security failures in which employees built applications that logged unencrypted password data for Facebook users and stored it in plain text on internal company servers."

Điều này cho thấy vấn đề có thể nghiêm trọng hơn chúng ta tưởng. Thay vì chỉ là sơ suất đơn giản, có vẻ như đã có những ứng dụng được tạo ra một cách có chủ ý để thu thập và lưu trữ mật khẩu không an toàn. Đây là một vi phạm nghiêm trọng về quy trình bảo mật và quyền riêng tư của người dùng.

Giải pháp ta nên bàn tới là gì?

Review code không bao giờ đủ

Không tư vấn cho Facebook, chúng ta hãy nghĩ làm sao để những hệ thống mà ta đang và sẽ làm trong tương lai không gặp các vấn đè tương tự:

Liệu một câu "Review code kỹ lưỡng", điều mà những PM vẫn luôn nghĩ rằng đó là cây đũa thần có thể giải quyết mọi vấn đề của hệ thống.

Vấn đề là việc review code một cách kỹ lưỡng rất tốn thời gian và công sức. "Trông có vẻ ổn" thường là kết quả của việc review mà Engineer lead có thể cung cấp khi nhìn vào tên của một hàm và không thực sự hiểu những gì bên trong. Liệu một dòng write_error_log ở trong một thư viện nào đó để log toàn bộ fields khi có lỗi sảy ra có được review tới ? Và khi thư viện có update thì sao ?

Giải pháp về thiết kế hệ thống

Phân tầng hệ thống

Services design propose

Một phương pháp hiệu quả để nâng cao bảo mật và đảm bảo tính nhất quán trong việc xử lý mật khẩu là triển khai tất cả các quy trình liên quan đến mật khẩu như một middleware tại API gateway. Cách tiếp cận này mang lại nhiều lợi ích quan trọng:

  1. Tập trung hóa: Mật khẩu của người dùng được xử lý trực tiếp tại lớp gần nhất với người dùng. Điều này giúp giảm thiểu số lượng code có quyền truy cập trực tiếp vào mật khẩu người dùng. Từ đó, giảm nguy cơ rò rỉ thông tin qua tệp logs. Việc tập trung này còn giới hạn quy mô develop team, giúp tránh các trường hợp rò rỉ bảo mật do yếu tố con người (càng đông người tham gia, càng dễ xảy ra lỗ hổng bảo mật).
  2. Tính nhất quán: Đảm bảo mọi yêu cầu đều được xử lý theo cùng một tiêu chuẩn bảo mật.
  3. Đơn giản hóa các dịch vụ phía sau: Các microservices khác không cần quan tâm đến việc xử lý mật khẩu. Thay vào đó, User ID sẽ được gắn trực tiếp vào request context, giúp các dịch vụ đích xác định được người dùng nào đang gửi yêu cầu.

Lời kết

Lặp lại, không ai biết thực sự những gã thông minh nhất ở đó đã gặp vấn đề gì, nhưng có vẻ họ đã làm điều gì đó sai. Thông qua bài viết này, Tôi mong rằng đã giúp được những người bạn đang bắt đầu xây dựng những hệ thống mới nắm được các tiêu chuẩn, từ đó tránh phải trả giá trên những gì tưởng chừng như đã hiển nhiên (chức năng đăng nhập).

Một lần nữa, Software Engineering Principles vẫn là một điều gì đó quan trọng mà đôi khi chúng ta lại quên đi.

Một quyển sách mà các bạn đã thấy trong thư viện trường nhưng bỏ qua ?


Tangled Web A Guide to Securing Modern Web Applications

Tài liệu tham khảo