4 cách xác thực hệ thống bảo mật cho app của bạn

Mở đầu

Ngày nay, nói về các framework xác thực (authenticate frameworks) của rails vẫn là một chủ đề đang gây tranh cãi rất nhiều.
Ví dụ như devise, một gem được dùng rất phổ biến trong việc xác thực mà các developer vẫn thường xuyên sử dụng. Các developer không thích dùng devise thì quan niệm rằng bản thân devise không phải một gem tốt bởi vì nó không có một tài liệu rõ ràng nào cả, rất khó để có thể hiểu được, càng khó để có thể chỉnh sửa cho phù hợp với từng project, thậm chí họ còn đưa ra quan điểm rằng liệu có nên sử dụng devise không trong khi chúng ta có thể custom hệ thống xác thực của riêng mỗi project. Những người ủng hộ devise lại đưa ra một quan điểm hoàn toàn ngược lại rằng devise là một gem được hoàn thiện qua nhiều năm và cũng bắt đầu từ chính lối suy nghĩ tự custom hệ thống xác thực cho hệ thống, đây sẽ là một sai lầm khi bạn tự mình bỏ qua một gem cung cấp độ bảo mật cao và được kiểm chứng qua rất nhiều các project khác nhau trên thế giới. Và nếu là lần đầu nghe đến cuộc tranh luận này hoặc chưa sử dụng devise các bạn chắc chắn cũng sẽ suy nghĩ rằng tại sao mình cần phải sử dụng một gem khác trong khi rails tutorials hoặc railscast đều chỉ dẫn việc xây dựng một hệ thống xác thực từ đầu bằng việc sử dụng has_secure_password và việc sử dụng reset password token (bằng cách sinh ra từ secure random) trong một khoảng thời gian rất ngắn.
Mặc dù railscast và rails tutorial giúp chúng ta những kiến thức rất hữu ích, tuy nhiên, các bạn có nghĩ rằng hệ thống xác thực của rails không thực sự an toàn? Nếu không dùng các hệ thống đó, bạn có nghĩ bạn có thể áp dụng gem devise cho hệ thống của mình? Kể cả bạn có áp dụng được gem devise đi nữa, bạn có nghĩ rằng từ đây hệ thống của bạn đã bảo mật tuyệt đối?
Giống như hầu hết các lĩnh vực khác, mình nghĩ câu trả lời của mọi người ở thời điểm hiện tại đó là "Tùy thuộc vào từng dự án". Sự xác thực không phải là sự cô lập hệ thống hay cơ sở hạ tầng trong dự án của bạn. Cũng tương tự như việc hệ thống của bạn có một chế độ xác thực rất mạnh, được bố trí cực kỳ hợp lý tuy nhiên ở hệ thống chính lại có rất nhiều các lỗ hổng khác, điều này lại có thể dẫn đến việc người dùng của bạn sẽ bị xâm nhập. Ngược lại, nếu bạn quá tập trung vào vấn đề của hệ thống thì việc xác thực của bạn sẽ trở nên lỏng lẻo hơn
Cách duy nhất cho bạn_một developer, có thể trả lời những câu hỏi này một cách thỏa đáng đó chính là mở rộng hơn nữa tầm hiểu biết của bản thân về vấn đề bảo mật và xác thực nói chung. Từ đó bạn có thể tự cân bằng giữa 2 vấn đề nói trên trong mọi tình huống lúc đang xây dựng hoặc maintain dự án của mình.
Dù bạn có sử dụng gem devise (hay bất cứ một gem thứ 3 nào khác tương tự devise) hoặc không sử dụng gem devise thì dưới đây là 4 cách mà mình có đi tìm hiểu một chút về vấn đề bảo mật. Mặc dù suy nghĩ của mỗi người đều khác nhau nhưng mong rằng nó có thể giúp được một chút cho việc xác thực hệ thống của bạn.

1. Throttle Requests

Điều dễ dàng nhất mà kẻ tấn công có thể làm để làm hại người dùng của bạn là đoán thông tin đăng nhập của họ bằng một tập lệnh. Người dùng không phải lúc nào cũng chọn mật khẩu tốt, do đó, nếu có đủ thời gian, kịch bản, kẻ tấn công có thể sẽ làm tổn hại đến số lượng người dùng (có thể là khổng lồ) của bạn chỉ bằng cách tạo ra một số lượng lớn các dự đoán dựa trên danh sách mật khẩu.
Bạn có thể gây khó khăn cho kẻ tấn công kiểu này bằng cách hạn chế loại và số lượng yêu cầu có thể được thực hiện cho ứng dụng của bạn trong khoảng thời gian đã xác định trước. Gem rack-attack có thể cung cấp cho bạn điều này. Nó là một middleware có thể cung cấp cho bạn một DSL nơi mà bạn có thể block hoặc bóp nhỏ lại lượng request. (Đại khái DSL là một đường thông tin số mà các request của người dùng phải đi qua).
Nếu bạn chỉ làm theo hướng dẫn cho hệ thống xác thực của mình và giờ bạn muốn sử dụng throttle request, bạn có thể làm như sau: Đầu tiên là cài đặt gem rack-attack các bạn có thể xem ở đây Sau đó các bạn cần thực hiện thêm một số việc giống như dưới đây sau khi cài đặt gem

throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
  req.ip
end

Đoạn code trên là để cho bất kỳ ip nào cũng có thể request nhưng số lượng tối đa là 300 request trong khoảng thời gian là 5 phút. Mọi người có thể thấy rằng với đoạn code trên, chúng ta có thể dễ dàng config rất nhiều thông số cho hệ thống của mình.
Tuy nhiên cách bảo mật trên vẫn còn 1 khuyết điểm, đó là hạn chế được 1 ip request nhiều lần nhưng các kẻ tấn công có thể tấn công bằng nhiều ip, như vậy thì sao? Để làm chậm loại tấn công này xuống, bạn có thể cân nhắc việc giảm bớt yêu cầu cho mỗi tài khoản, ví dụ:

throttle("logins/email", :limit => 5, :period => 20.seconds) do |req|
  if req.path == '/login' && req.post?
    req.params['email'].presence
  end
end

Nếu bạn đang sử dụng Devise, bạn cũng có tùy chọn để "lock" tài khoản nếu có quá nhiều nỗ lực không thành công để đăng nhập. Bạn cũng có thể thực hiện một tính năng "lock" bằng tay. Tuy nhiên, có một mặt trái để khóa hoặc điều chỉnh trên mỗi tài khoản - kẻ tấn công có thể hạn chế quyền truy cập vào các tài khoản người dùng tùy ý bằng cách buộc họ bị khóa tài khoản, đó là một dạng của cuộc tấn công "Từ chối dịch vụ". Trong trường hợp này, không có câu trả lời dễ dàng. Khi nói đến việc chọn cơ chế bảo mật cho ứng dụng của mình, bạn sẽ phải quyết định mức độ và loại rủi ro bạn muốn thực hiện.

Ngoài ra rack_attack còn có một tính năng là "track" điều này giúp bạn hiểu về lưu lượng truy cập trên máy chủ web của bạn. Điều này sẽ giúp bạn đưa ra quyết định với các thông tin rõ ràng hơn về các thông số điều chỉnh.

2. Thiết lập chính xác các security headers của bạn

Nhìn tiêu đề, ngay lúc đầu mình đã có suy nghĩ là việc này đã có HTTPS làm đầy đủ các sercurity header rồi và việc sử dụng nó cho các request của hệ thống sẽ là một điều bảo mật tuyệt đối. Tuy nhiên, việc này cũng có lúc không chính xác cho lắm. Cụ thể là vẫn có một vài trang web trước khi chuyển sang https:// (trong một thời gian ngắn) trình duyệt của bạn vẫn ở dạng http:// request. Điều này có nghĩa là nếu có 1 hacker ở đúng thời điểm này thì họ có thể dễ dàng chèn vào 1 session giả mạo trang web bạn muốn vào để lấy cắp thông tin cá nhân về id và password của bạn.
Strict-Transport-Security header được sinh ra để ngăn chặn kiểu tấn công này. Bằng cách include Strict-Transport-Security header (Hay còn gọi là tiêu đề HTTP Strict Transport Security hoặc HSTS header) trong phần response của hệ thống, một máy chủ có thể cho trình duyệt biết chỉ liên lạc với nó thông qua https. Tiêu đề HSTS thường chỉ định tham số max-age và trình duyệt tương đương giá trị của tham số này với thời gian cần sử dụng https để giao tiếp với máy chủ. Ví dụ, nếu max-age được đặt thành 31536000, nghĩa là một năm, trình duyệt sẽ chỉ liên lạc với máy chủ qua https trong một năm. HSTS header cũng cho phép máy chủ của bạn chỉ định tên miền phụ nếu nó muốn trình duyệt nói chuyện qua https trên các tên miền phụ.
Để làm điều này thì các bạn cần thêm config.force_ssl = true config này sẽ đảm bảo HSTS được set trong khoảng 180 ngày. Để áp dụng https cho tất cả subdomain của hệ thống, bạn có thể làm như sau:

config.ssl_options = {hsts: {subdomains: true}}


Lỗ hổng trong trường hợp này là yêu cầu đầu tiên tới máy chủ vẫn có thể được thực hiện qua http. Tiêu đề HSTS bảo vệ tất cả các yêu cầu ngoại trừ yêu cầu đầu tiên. Có một cách để trình duyệt luôn sử dụng https cho trang web của bạn, ngay cả trước khi người dùng thực sự truy cập, và đó là bằng cách gửi tên miền của bạn để được đưa vào danh sách tải trước Chromium. "Bất lợi" với cách tiếp cận tải trước là bạn sẽ không bao giờ có thể cung cấp thông tin qua http trên tên miền của bạn.
Việc bật HSTS không có nghĩa là người dùng của bạn sẽ hoàn toàn an toàn, nhưng mình nghĩ họ sẽ an toàn hơn đáng kể so với không có.

3. Đọc tất cả các thư viện xác thực (Devise, Authlogic, Clearance, Rodauth và bất cứ thứ gì bạn có thể tìm được)

Cách này khá thủ công và mình thấy khá hữu ích nếu bạn tự mình đọc tất cả code của các gem nhưng nếu không đọc được tất cả bạn cũng sẽ học được khá nhiều điều từ việc rút ra các điều mà nhiều gem khác nhau cùng làm 1 việc đó là "xác thực". Ngoài ra, bạn có thể không phải tự mình đọc tất cả các source code để hiểu chi tiết về gem mà đôi khi ở chính các trang blog hoặc diễn đàn của gem, người ta sẽ đi sâu vào chi tiết các phần mà mình nghĩ nếu tự học sẽ tốn khá khá thời gian.
Dưới đây là 3 điều mà mình thu thập trên mạng về Rodauth và Devise:

  • Việc truy cập vào password hash bị hạn chế (Rodauth)

    • Không giống như Devise, Rodauth sử dụng một bảng hoàn toàn riêng biệt để lưu trữ mật khẩu, và bảng này không thể truy cập vào phần còn lại của ứng dụng. Rodauth thực hiện điều này bằng cách thiết lập hai tài khoản cơ sở dữ liệu, appph. Các hash mật khẩu được lưu trữ trong một bảng chỉ có ph truy cập và app được cấp quyền truy cập vào một chức năng của cơ sở dữ liệu sử dụng ph để kiểm tra xem một hash mật khẩu có phù hợp với một tài khoản nhất định hay không. Bằng cách này, ngay cả khi có một lỗ hổng SQL injection trong ứng dụng của bạn, kẻ tấn công sẽ không thể truy cập trực tiếp vào mật khẩu của người dùng.
  • Mỗi User có một tokens cụ thể (Rodauth)

    • Rodauth không chỉ lưu trữ password_reset và các thẻ nhạy cảm khác trong các bảng riêng biệt, nó cũng prepends tất cả các mã thông báo với một ID tài khoản. Hãy tưởng tượng liên kết quên mật khẩu của bạn trông giống như www.example.com/reset_password?reset_password_token=abcd1234 và kẻ tấn công đã cố gắng đoán một mã thông báo hợp lệ. Việc đoán của kẻ tấn công có thể là một mã thông báo hợp lệ cho bất kỳ người dùng nào. Nếu chúng ta thêm tiền tố vào mã thông báo bằng ID của tài khoản (có thể là dấu hiệu giống như reset_password_token = <account_id> -abcd1234), thì kẻ tấn công chỉ có thể cố gắng truy cập vào một tài khoản người dùng một lần.
  • Digesting Tokens (Devise)

    • Cái này chắc mọi người quen thuộc, đó là việc digests password reset, confirmation and unlock tokens trước khi lưu chúng vào database. Nó được thực hiên bằng cách tạo ra một token đầu tiên với SecureRandom và digesting nó với phương pháp OpenSSL::HMAC.hexdigest. Cách này ngoài việc tránh được các hacker thâm nhập được vào db sẽ không lấy được password nó cũng chống lại các cuộc tấn công bằng thời gian bởi vì nó gần như không thể cho kẻ tấn công kiểm soát chuỗi được so sánh đủ để thực hiện các thay đổi.

4. Bảo vệ phần còn lại của ứng dụng của bạn

Xác thực không đứng riêng biệt. Các lỗ hổng trong phần còn lại của ứng dụng có tiềm năng vượt qua bất kỳ biện pháp bảo mật nào mà bạn có thể đã tích hợp vào hệ thống xác thực của mình.
Lấy ví dụ hệ thống của bạn có một đoạn html_safe và không may hơn nữa đoạn code đấy lại nằm ngay ở chỗ input của người dùng, điều này dẫn đến các attacker có thể thêm được 1 đoạn javascript hoặc ajax vào html của chúng ta nhằm thay đổi mật khẩu. Kể cả có yêu cầu xác nhận mật khẩu cũ khi thay đổi mật khẩu, họ vẫn có thể thay đổi email hoặc các thông tin khác để forgot password và chiếm quyền điều khiển.
Nói tóm lại là một lỗ hổng không trong hệ thống xác thực cũng có nguy cơ dẫn đến việc không an toàn trong hệ thống của chúng ta.

Kết luận

Bài viết được tìm hiểu và biên dịch từ nguồn ở trên mạng, có thể nó không hoàn toàn chính xác nhưng mình mong có thể giúp cho chính mình và một số bạn đang tìm hiểu về vấn đề xác thự hiểu thêm một phần nào về an ninh, bảo mật mạng. Từ đó giúp cho hệ thống của bạn an toàn hơn.
Mong mọi người góp ý về bài viết để nội dung của bài viết thêm hoàn chỉnh hơn.