Rails CSRF Protection
Nếu như bạn đang sử dụng Rails, rất có thể bạn cũng đang sử dụng cơ chế bảo vệ CSRF. Đây là một tính năng đã xuất hiện ngay từ những phiên bản đầu tiên của Rails. Nói qua một chút về Cross-Site Request Forgery (CSRF), nó là một phương thức tấn công được thực hiện bằng việc giả mạo các request đến máy chủ của bạn như một người dùng thực sự trong hệ thống. Rails chống lại loại tấn công này bằng cách tạo ra các token để xác thực cho mỗi request.
Tổng quan
CSRF bao gồm hai thành phần:
- Một token được nhúng vào trang HTML, đồng thời nó cũng được lưu lại trong session
- Khi người dùng gửi request, Rails sẽ so sánh chuỗi token nhận được với những gì đang được lưu trong session và đảm bảo chúng trùng khớp
Sử dụng trong Rails
Rất đơn giản, chắc hẳn khi vừa khởi tạo project chúng ta đã thấy ngay dòng đầu tiên trong application_controller.rb
một lời gọi kích hoạt cơ chế bảo vệ CSRF:
protect_from_forgery with: :exception
Tiếp sau đó là một dòng code trong application.html.erb
<%= csrf_meta_tags %>
Đó cũng chính là hai thành phần mà chúng ta vừa phân tích ở trên. Nhưng chúng thực sự sẽ hoạt động như thế nào?
Tạo và mã hóa token
Theo đúng vòng hoạt động, chúng ta sẽ bắt đầu với csrf_meta_tags
. Nó đơn thuần chỉ là một view helper giống như content_tag
, link_to
... Chức năng của nó là render ra các thẻ meta có nội dung là các token đã được mã hóa.
def csrf_meta_tags
if protect_against_forgery?
[
tag("meta", name: "csrf-param", content: request_forgery_protection_token),
tag("meta", name: "csrf-token", content: form_authenticity_token)
].join("\n").html_safe
end
end
Chúng ta sẽ tập chung vào csrf-token
vì đó là nơi mọi thứ được bắt đầu. form_authenticity_token
là một method dùng để lấy nội dung token. Tại thời điểm này, chúng ta sẽ cùng nhau tìm hiểu về module RequestForgeryProtection
.
RequestForgeryProtection
là một module chứa đựng logic xử lý mọi thứ với CSRF. Nó đảm bảo việc xác thực CSRF sẽ được thực hiện trên mỗi request cũng như việc phản hồi nếu như có một request nào đó không được xác thực. Nó cũng là nơi tạo, mã hóa và giải mã CSRF tokens.
Hãy cùng tiếp tục đi sâu vào cách mã hóa CSRF trước khi mọi thứ được nhúng vào trang HTML của bạn. form_authenticity_token
đơn giản chỉ là một method nhận vào các option parameters và thực hiện một lời giọi tới method masked_authenticity_token
def form_authenticity_token(form_options: {})
masked_authenticity_token(session, form_options: form_options)
end
Trong method masked_authenticity_token
mọi thứ mới thực sự được diễn ra:
def masked_authenticity_token(session, form_options: {})
raw_token = if per_form_csrf_tokens && action && method
# ...
else
real_csrf_token(session)
end
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
masked_token = one_time_pad + encrypted_csrf_token
Base64.strict_encode64(masked_token)
end
Nó nhận vào session là để truyền vào cho real_csrf_token
. Tại đây, một token được tạo ra và được lưu lại trong session[:_csrf_token]
trước khi nó được mã hóa:
def real_csrf_token(session) # :doc:
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
Base64.strict_decode64(session[:_csrf_token])
end
Kết quả cuối cùng mà masked_authenticity_token
trả về là một token được mã hóa từ chuỗi kết hợp từ token lưu trong session[:_csrf_token]
và một khóa gọi là one_time_pad
. Khóa này sẽ được sử dụng để giải mã token trong quá trình xác thực sau này.
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
masked_token = one_time_pad + encrypted_csrf_token
Base64.strict_encode64(masked_token)
Toàn bộ quá trình sẽ được mình họa bằng hình ảnh bên dưới:
Khi tất cả hoàn tất, <%= csrf_meta_tags %>
sẽ render ra ngoài view hai thẻ meta và nội dung của chúng sẽ trông thế này:
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="vtaJFQ38doX0b7wQpp0G3H7aUk9HZQni3jHET4yS8nSJRt85Tr6oH7nroQc01dM+C/dlDwt5xPff5LwyZcggeg==" />
Giải mã và xác thực
Vừa rồi chúng ta đã đi tìm hiểu làm thế nào một CSRF token được tạo và nhúng vào HTML. Tiếp theo hãy cùng xem Rails đã làm gì để xác thực một request. Đó cũng là bước cuối cùng trong toàn bộ quá trình bảo vệ CSRF.
Khi người dùng submit form, CSRF token sẽ được gửi theo tương tự như như các form data khác. Nó cũng có thể được gửi thông qua X-CSRF-Token
của HTTP header.
Trong ApplicationController
:
protect_from_forgery with: :exception
Một trong những điều mà protect_from_forgery
thực hiện là thêm một before_action
vào lifecycle
của tất cả các action
trong controller
:
before_action :verify_authenticity_token, options
Callback verify_authenticity_token
bắt đầu bằng việc so sánh CSRF token nhận được từ request params hoặc từ header với token đã được lưu trong session:
def verify_authenticity_token
if !verified_request?
# handle errors ...
end
end
def verified_request?
!protect_against_forgery? || request.get? || request.head? ||
(valid_request_origin? && any_authenticity_token_valid?)
end
Xác thực any_authenticity_token_valid?
được thực hiện cuối cùng sau khi các thủ tục thông thường khác đã được thông qua.
def any_authenticity_token_valid? # :doc:
request_authenticity_tokens.any? do |token|
valid_authenticity_token?(session, token)
end
end
Vì token có thể được gửi dưới dạng form data hoặc thông quan header, do đó Rails chỉ yêu cầu một trong số chúng được xác thực.
Method valid_authenticity_token?
có chức năng là đảo ngược lại quá trình mà masked_authenticity_token
trước đó đã thực hiện
Conclusion
Quá trình implementation CSRF protection là một ví dụ tuyệt vời về việc phân tách chức năng trong codebase. Bằng việc tạo ra một module tương ứng với một chức năng duy nhất Rails team đã phát triển thêm nhiều tính năng mới qua các phiên bản mà không làm ảnh hưởng đến phần còn lại của codebase. Hi vọng qua bài viết chúng ta sẽ hiểu hơn về cách hoạt động của cơ chế bảo vệ CSRF trong Rails
Tham khảo: https://medium.com/rubyinside/a-deep-dive-into-csrf-protection-in-rails-19fa0a42c0ef
All rights reserved