+6

5 lỗi bảo mật rất dễ gặp trong Rails

Như tiêu đề, trong bài viết này, mình không nói đến những cái cao siêu để bạn đọc xong có thể hóa thân thành hacker oanh tạc hệ thống, các lỗi mình đề cập đến trong bài nó rất bình thường mà bất kì người dùng nào đều có thể gặp phải mà bản thân người lập trình đôi lúc lại không để tâm đến.

1. Bỏ qua thời gian hết hạn của session

Mô tả lỗi:

Session ở đây được hiểu là một phiên kết nối giữa hai máy tính trên hệ thống mạng thường được duy trì bởi các giá trị như thời gian tồn tại của session, thông tin cookie của trình duyệt hay các thẻ bài thích hợp

Theo tài liệu Securing Rails Applications:

Session không bao giờ hết hạn sẽ mở rộng khung thời gian cho các đợt tấn công hay còn gọi là CSRF, session hijackingsession fixation (bởi vì để giải thích được mấy cái khái niệm này rất dài dòng nên mình có đính thêm link, mọi người có thể vào đọc để hiểu hơn)

Mặc dù việc session vô thời hạn là một cách tiếp cận khá thân thiện với người dùng (bởi vì người dùng chỉ cần đăng nhập lần đầu tiên vào ứng dụng và sẽ không bao giờ phải đăng nhập lại lần nữa), nhưng nó lại là một ý tưởng tệ. Để tránh việc người khác có thể lợi dụng việc lưu session để truy cập vào tài khoản của bạn nếu bạn có lỡ quên không đăng xuất khỏi một thiết bị công cộng, session nên hết hạn nhanh nhất có thể.

Hướng giải quyết

Cách đơn giản nhất là set thời gian hết hạn cho cookie trong config/initializers/session_store.rb như sau:

Rails.application.config.session_store :cookie_store, expire_after: 12.hours

Câu lệnh trên set cookie tự động hết hạn sau 12h tính từ thời điểm khởi tạo. Giải pháp này rất dễ thực hiện, nhưng nó có 1 nhược điểm lớn. Nó set thời gian hết hạn trong trình duyệt của người dùng. Bất kì ai có quyền truy cập cookie đều có thể mở rộng thời gian hết hạn của cookie.

Để giải quyết vấn đề này, thời gian hết hạn nên được lưu phía server. Nó cũng được đề xuất ở trong Securing Rails Application:

Một cách là set thời gian hết hạn của cookie bằng một session ID. Tuy khách hàng có thể sửa cookie lưu ở trình duyệt, nhưng thời hạn ở trên server thì vẫn được giữ nguyên.

Nếu bạn sử dụng gem Devise để xác thực người dùng trong ứng dụng Rails, nó có 1 module Timeoutable được tích hợp sẵn để kiểm tra phiên làm việc của người dùng đã hết hạn hay chưa. Để sử dụng bạn cần enable nó trong model đại diện cho người dùng trong ứng dụng:

class User < ActiveRecord::Base
  devise :timeoutable
end

Sau đó bạn có thể set giá trị timeout_in trong file khởi tạo devise thành giá trị bạn muốn (mặc định là 30 phút)

# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. 
# After this time the user will be asked for credentials again. 
# Default is 30 minutes.
config.timeout_in = 30.minutes

Nếu bạn không sử dụng gem Devise, bạn có thể tạo model Session để lưu user session với created_at, updated_at và xử lý các bản ghi đã hết hạn. Bạn có thể tìm thấy nó trong tài liệu của Rails.

2. Thiếu cơ chế khóa

Mô tả lỗi:

Người dùng có thể cố gắng đăng nhập vào hệ thống của bạn bao nhiêu lần trước khi bị khóa? Nếu câu trả lời là vô tận, thì bạn đang có một lỗ hổng bảo mật. Nếu người dùng có thể thử nhiều email và passwork mà không có hậu quả gì, thì hacker cũng có thể làm vậy. Với lỗi này, chúng ta sẽ lại biết thêm 2 khái niệm là dictionary attack và brute-force attack

Nói đơn giản thì brute-force attack sẽ kết hợp mọi cách có thể, còn dictionary attack là thay vì mò vô tội vạ, ta sẽ có một danh sách các tổ hợp ký tự có khả năng là passwork nhất và đem dò trong đấy. Cụ thể hơn thì có thể đọc trong link này.

Để khắc phục điều này, người dùng cần bị khóa sau khi đăng nhập N lần.

Hướng giải quyết

Nếu bạn sử dụng gem Devise vấn đề này cũng dễ giải quyết như vấn đề trước. Module Lockable cho phép chặn quyền truy cập của người dùng sau một số lần thử nhất định. Số lần tùy thuộc vào bạn, nhưng thường thì sẽ là 5. Bạn hoàn toàn có thể chỉnh lại nếu như nhận được những phản ánh từ phía người dùng.

Module này cung cấp 2 cách để mở khóa:

  1. :time tự động mở khóa sau một thời gian
  2. :email gửi email tới người dùng khi bị khóa, trong email chứa link để mở khóa.

Dù là dùng cách nào thì cũng đều tốt hơn là không dùng, còn dùng cái nào thì tùy bạn, cũng có thể dùng đồng thời cả 2 cái. Bạn có thể đọc thêm trong này.

Nếu bạn không dùng gem, bạn có thể tự code một cái tương tự hoặc là tìm xem thư viện bạn đang dùng có hỗ trợ không.

Ngoài ra, việc sử dụng mã capcha cũng là một cách để tránh brute-force attack và dictionary attack.

Tham khảo ở đây.

3. Địa chỉ email dễ tìm

Mô tả lỗi:

Không quá rõ ràng, nhưng nó cũng là vấn đề nghiêm trọng. Giờ hãy vào ứng dụng của bạn, vào trang ít khi được ghé thăm là Reset password. Điều gì sẽ xảy ra nếu bạn điền một email không liên kết với bất cứ user vào trong hệ thống?

Hy vọng nó sẽ không hiện lỗi email không tồn tại. Vì sao? Đối với hacker, nó là một cách dễ dàng để thu thập email trong hệ thống. Rất dễ để chuẩn bị hoặc tìm một đoạn script tạo ra hàng triệu request với một list địa chỉ email sẵn có và dựa trên các response trả về để xác định email nào tồn tại. Sau khi có được danh sách đó, hacker có thể sử dụng lỗ hổng bảo mật ở lỗi phía trên để truy cập vào tài khoản người dùng.

Hướng giải quyết

Ứng dụng nên trả về response kiểu như chuỗi JSON từ API hoặc là chuyển hướng đến trang có thông báo xác nhận khi người dùng cung cấp đúng email có trong hệ thống. Khi đó hacker không thể thu thập địa chỉ email người dùng của bạn.

Nếu bạn sử dụng gem Devise, nó có một option trong file khởi tạo gọi là paranoid

# Nó có thể xác nhận thay đổi, khôi phục password, các luồng xử lý khác bất kể email được cung cấp là đúng hay sai
config.paranoid = true

Nếu bạn không sử dụng devise, bạn nên sửa tương tự như vậy, ứng dụng sẽ hoạt động dù cho người dùng cung cấp địa chỉ email tồn tại hay không.

4. Truy cập trái phép

Mô tả lỗi:

Giả sử bạn tạo một API để lấy thông tin project của một user dựa vào project_id

GET https://my-rails-app.com/api/projects/:project_id

Bạn test nó bằng cách gửi một vài request sử dụng ID của project của 1 user và nó trả về đúng JSON chi tiết dự án. Ok rồi, vậy deploy lên production. Nhưng khoan! Bạn đã kiểm tra nếu truyền vào project_id của một người khác thì sao?

Bùm! Bạn đã quên giới hạn quyền truy cập trong phạm vi project của user đó. Bản thân mình cũng vừa gặp phải lỗi này mà không hề phát hiện ra, khi viết test mình chỉ để ý đến việc test dữ liệu trả về, tạo dự liệu test thỏa mãn quyền truy cập, và đến khi deploy lên staging mới phát hiện ra, cũng may nó mới chỉ là staging, bạn chắc chắn sẽ không muốn gặp phải lỗi giống mình phải không? 😦 Nhớ khi test phải bổ sung thêm case không có quyền truy cập nhé!

Hướng giải quyết

Luôn nhớ phải hạn chế quyền truy cấp ở mức hẹp nhất có thể. Nếu bạn có quyền gọi đến phương thức current_user trong controller thì cách sửa nhanh nhất là:

Project.find(params[:id])

chuyển thành

current_user.projects.find(params[:id])

Nếu bạn muốn quản lý tài nguyên theo hướng đối tượng, bạn có thể sử dụng gem pundit hoặc cancan.

5. Cho phép user sử dụng mật khẩu "yếu"

Mô tả lỗi:

Đa số người dùng ứng dụng không sử dụng các tool như 1password hay KeePass để sinh ra mật khẩu an toàn, lưu trữ chúng và tự điền vào mỗi lần đăng nhập.

Người dùng thường dùng mật khẩu dễ nhớ và dùng nó cho mọi ứng dụng.

Theo ý kiến cá nhân, mình nghĩ nên cảnh báo người dùng và quan tâm đến vấn đề bảo mật của họ ngay cả khi nó có hơi khó chịu hay bất tiện với họ.

Đừng bao giờ cho user tạo tài khoản mà sử dụng những password kiểu như 123456 hay qwerty. Nó sẽ khiến brute-force attack và dictionary attack trở nên dễ dàng hơn nhiều.

Hướng giải quyết

Giới thiệu và áp dụng chính sách bảo mật.

Để áp dụng chính sách bảo mật cơ bản, chỉ cần thêm phương thức validation trong model User:

validate :password_complexity
def password_complexity
  return if password.blank? || password =~ /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,70}$/
  errors.add :password, "Complexity requirement not met. Length   
    should be 8-70 characters and include: 1 uppercase, 1 lowercase, 
    1 digit and 1 special character"
end

Phương thức trên được lấy từ wiki của Devise nên được thêm vào ứng dụng của bạn ngay sau khi bạn đọc bài viết này. (nếu chưa có =)) )

Nếu bạn muốn một cái gì đó "mạnh" hơn, bạn có thể sử dụng gem strong_password, nhưng nó có thể hơi quá mức cần thiết.

Tổng kết Hy vọng trong ứng dụng của bạn không có lỗi nào trong các lỗi kể trên. Còn nếu bạn tìm được, thì hy vọng các cách giải quyết của mình có thể giúp bạn "vá" lại các lỗ hổng đó. Chúc may mắn!

Nguồn: https://frontdeveloper.pl/2018/10/5-security-issues-in-ruby-on-rails/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí