Understanding Rails' sercurity problems
Abstract Bảo mật là một phần không thể thiếu đối với các sản phẩm phầm mềm hiện nay. Rails cũng không phải ngoại lệ, framework này cũng cung cấp các cơ chế để bảo vệ ứng dụng khỏi các lỗ hỏng bảo mật ví dụ : csrf, xss, sql injection... . Trong bài viết lần này mình muốn đi sâu vào tìm hiểu cơ ...
Abstract
Bảo mật là một phần không thể thiếu đối với các sản phẩm phầm mềm hiện nay. Rails cũng không phải ngoại lệ, framework này cũng cung cấp các cơ chế để bảo vệ ứng dụng khỏi các lỗ hỏng bảo mật ví dụ : csrf, xss, sql injection... . Trong bài viết lần này mình muốn đi sâu vào tìm hiểu cơ chế bảo vệ ứng dụng khỏi các mối đe dọa từ các lỗ hổng bảo mật mình nêu trên.
Cross-Site Request Forgery
Cross-Site Request Forgery là một lỗ hổng nghiêm trọng tấn công người dùng từ các đường link mạo danh lợi dụng sự sơ hơ của người dùng trên mạng, thực hiện các hành động vi phạm pháp luật ví dụ: thay đổi tài khoản, chiếm đoạt thông tin hay tồi tệ hơn thực hiện các hành động phá hoại người sử dụng. Một lỗ hổng cũng gây ra rất nhiều thiệt hại năng nề và được vinh dự đứng trong top 10 lỗ hổng bảo mật của OWASP(https://www.owasp.org/index.php/Top_10_2013-Top_10). Cơ chế hoạt động của csrf như sau:
Các bước tấn công như sau:
- Đầu tiên, nạn nhân đăng nhập vào trang web (internet banking...)
- Kẻ tấn công phát tán trang web độc hại và dụ dỗ nạn nhân sử dụng,
- Khi nạn nhân truy cập vào web độc này, một request sẽ được gửi đến trang web banking mà hacker muốn tấn công (thông qua form, img, …).
- Do trong request này có đính kèm cookie của nạn nhân(do chưa logout tài khoản), trang web banking đích sẽ nhầm rằng đây là request chuyển tiền do nạn nhân thực hiện.
- Hacker có thể mạo danh nạn nhân để làm các hành động như đổi mật khẩu, chuyển tiền, ….
Các phòng chống cho website:
Cũng như các framework Rails xây dụng một cơ chế để phòng tránh tấn công csrf bằng "protect_from_forgery" được thêm mặc định vào trong controller application_controller.rb khi khởi tạo mới ứng dụng Rails. Chỉ với phương thức "protect_from_forgery" ứng dụng đã được bảo vệ khỏi các cuộc tấn công csrf của các hackers!
Trong mỗi session(phiên làm việc) của một user Rails sinh ra một chuối tokens(một chuỗi kí tự ngẫu nhiên) và thêm vào trong thẻ hidden với tên authenticity_token trong các form HTML được render . Đảm bảo rảng các tokens sẽ được gửi lên server tương ứng với mỗi reqest khi đó Rails sẽ thực hiện kiểm tra lại tokens đó có trùng với chuỗi tokens của session không. Tuy nhiên có một điều lưu ý rằng tất cả các request đều được kiểm tra chỉ duy nhất phương thức GET sẽ không chuỗi tokens đính kèm và Rails cũng không kiểm tra và bảo vệ
Trong trường hợp remote forms(ajax request), Rails cũng có cơ chế để phỏng tránh csrf như sau: các mã token được lấy từ thẻ meta tag <%= csrf_meta_tag %> được sinh trong layout view của mặc định của rails. Sau đó Rails sử dụng gem jquery jquery_ujs để thêm tự động vào các header của Ajax request được gọi là X-CSRF-Token
Để hiểu rõ hơn về cơ chế xử lý của phương thức protect_from_forgery các bạn có thể vào trong phần source code của rails(https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/request_forgery_protection.rb) để tìm hiểu, ở bài viết này mình xin giải thích sơ qua cơ chế xử lý token được gửi từ các form
Khi chúng ta gọi phương thức protect_from_forgery bản chất ta sẽ thực hiện một callback before_action :verify_authenticity_token . Trong hàm verify_authenticity_token sẽ thực hiện công việc sau
def verify_authenticity_token # :doc:
mark_for_same_origin_verification!
if !verified_request?
if logger && log_warning_on_csrf_failure
if valid_request_origin?
logger.warn "Can't verify CSRF token authenticity."
else
logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
end
end
handle_unverified_request
end
end
Khi mà hàm verify_authenticity_token chạy nó sẽ thực hiển kiểm tra xem request đã được xác thực bởi phương thức verified_request? . Phương thức verified_request? sẽ thực hiện so sánh authenticity_token của session[:csrftoken] với params[:authenticity_token] trong form hoặc X-CSRF-Token HTTP header. Nếu authenticity_token không trùng thì phương thức handle_unverified_request sẽ được gọi tới và thực hiện xóa bỏ dữ liệu session, flash, cookies liên quan...
Trong rails 4 function protect_from_forgery có thể truyền tham số dạng hash để đưa ra exception mỗi khi request gửi lên mà token không hợp lệ
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Tổng kết: CSRF là một lỗ hổng nghiêm trọng, để khắc phục được lỗ hổng cần đến từ hai phía. Một từ phía người sử dụng: có ý thức trong việc bảo mật tài khoản, thoát tài khoản sau của mình sau mối lần sử dụng, không click hoặc đăng nhập các trang web có độ tin cậy thấp. Hai tứ phía server nói chung cần có cơ chế hủy bỏ session trong khoảng thời gian định trước, xác thức các request từ phía client. Về phía Rails sử dụng phương thức protect_from_forgery để có thể khắc phục được lỗ hổng này, tuy nhiên không thể đảm bảo rằng tất cả các trường hợp đã đều được xử lý, vẫn sẽ có những trường hợp phương thức kiểm tra của rails để lọt. Vì vậy có hiểu được cơ chế xử lý của rails thực sự quan trọng để đảm bảo rằng ứng dụng của bạn sẽ không bị đe dọa bởi các lỗ hổng đặc biệt.
Cross-site Scripting (XSS)
Cross-site Scripting là lỗ hổng cho phép hackers chạy các đoạn script code do hacker nhập vào thông qua các entry point: các ô search, đăng biết viết, comment, tóm lại ở bất kì đâu mà người dùng có thể nhập liệu data. Từ đó các đoạn script sẽ được thực hiện và đánh cắp các thông tin quan trọng của hệ thống(trộm cookie, các mã độc...) Ví dụ:
Dưới đây là đoạn code của một form thông tin cá nhân đơn giản như sau:
<%= form_for(@user) do |f| %>
<%= f.label "Profile" %>
<%= f.textarea :profile %>
<% end %>
Giả sử user đó có tình điển vào thông tin là một đoạn text bao gồm cả đoạn code javascript và submit form đó lên server
Hey, nice forum!<script>alert("Guess who just got owned?")</script>
Trong phần view :
<%= raw @user.profile %>
Được render như sau
<p>
Hey, nice forum!<script>alert("Guess who just got owned?")</script>
</p>
Kết quả:
Trong trường hợp 2: HTML Escaped
Trong phần view :
<%= html_escape @user.profile %>
Được render như sau
<p>
Hey, nice forum!<script>alert("Guess who just got owned?")</script>
</p>
Và kêt quả hiển thị như sau:
Như ví dụ trên đã thấy bất cứ ai thấy ai vào đọc đều dính đoạn script đó, trong trường hợp nếu là bài post hoặc comment thì những người khác vào đọc bài để để hiện thị những đoạn script như vậy và nếu không may mắn thay vì là đoạn script greeting có thể mã độc đã được nhúng vào
Cách phòng tránh:
Rất may mắn các framework cả Rails bắt đầu từ phiên bản 3 trở đi tất cả các kí tự khi được nhập vào từ input của người dùng đều là html escaped do đó trong phần view của Rails sẽ không còn phải thêm đoạn code html_escape:
<%= @user.profile %>
Nếu muốn bỏ tính năng này đi thì bạn thêm câu lệnh như sau:
<%= @user.profile.html_safe %>
Có nghĩa răng phương thức raw mà mình sử dụng trong ví dụ vừa rồi xử lý như sau:
def raw stringish
stringish.to_s.html_safe
end
Ngoài ra còn một số cách khác :
- Validation/Sanitize
Một cách chống XSS khác là validation: loại bỏ hoàn toàn các kí tự khả nghi trong input của người dùng, hoặc thông báo lỗi nếu trong input có các kí tự này. Ngoài ra, nếu muốn cho phép người dùng nhập vào HTML, hãy sử dụng các thư viện sanitize. Các thư viện này sẽ lọc các thẻ HTML, CSS, JS nguy hiểm để chống XSS. Người dùng vẫn có thể sử dụng các thẻ <p>, <span>, <ul> để trình bày văn bản - CSP (Content Security Policy)
Ta có thể dùng chuẩn CSP để chống XSS. Với CSP, trình duyệt chỉ chạy JavaScript từ những domain được chỉ định.
SQL Injection
Lỗ hổng SQL injection là gì ?
SQL injection nằm ở vị trí đầu bảng trong top 10 lỗ hỗng bảo mật của OWASP. Những lý do sau đã tạo nên tên tuổi lừng lẫy của SQL Injection
- Cực kỳ nguy hiểm – Có thể gây ra những thiệt hại khổng lồ. Với SQL Injection, hacker có thể truy cập một phần hoặc toàn bộ dữ liệu trong hệ thống.
- Rất phổ biến và dễ thực hiện – Lỗ hổng này rất nổi tiếng, từ developer đến hacker gần như ai cũng biết. Ngoài ra, còn có 1 số tool tấn công SQL Injection cho dân “ngoại đạo”, những người không biết gì về lập trình.
- Rất nhiều ông lớn từng bị dính – Sony, Microsoft UK. Mọi vụ lùm xùm liên quan tới “lộ dữ liệu người dùng” ít nhiều đều dính dáng tới SQL Injection.
Sau đây là ví dụ cho thấy
User.where("email = #{payload}").first
Chỉ với câu lệnh trên trông cũng không có gì đặc biệt nhưng chỉ thế là đủ để có thể khai thác lỗ hổng của hệ thống. Bởi vì param[:email] mà người dùng truyền đến server không có gì đảm bảo rằng chỉ là email bình thường rất có thể là một đoạn mã độc ví dụ:
# http://domain.com/query?email=') or 1=1--
payload = "') or 1=1--"
@user = User.where("email = #{payload}").first
render @user
#=> #<User id: 1, email: "[email protected]", name: "A", admin: false, created_at: "2015-10-02 13:14:38", updated_at: "2015-10-02 13:14:38">
Ở ví dụ trên kẻ tấn công đã gửi đến một đoạn mã ') or 1=1-- . Kết quả rails đã trả về thông tin của user đầu tiên trong cơ sở dữ liệu. Mình sẽ giải thích tại sao lại như vậy
- Phần đầu ') đoạn mã này dùng để kết thúc câu truy vấn tìm email, kết quả trả về sẽ là rỗng.
- Phần thứ hai, OR 1=1 kết quả trả về luôn luôn là TRUE , dẫn tới câu truy vấn user trên cũng trả kết quả là user đầu tiên trong cơ sở dữ liệu.
- Phần cuối, -- là comment trong câu lệnh SQL. Đây là một kĩ thuật dùng để dừng những câu lệnh đằng sau từ đó nâng cao hiệu suất thành công của mã độc được truyền vào.
Như vậy chỉ với một đoạn mã đơn giản kẻ tấn công đã lấy được thông tin của 1 tài khoản user, với chỉ thêm 1 số đoạn lệnh đơn giản nữa, có thể tài khoản của admin cũng có thể bị lộ. Ngoài ra, kẻ tấn công có thể thông qua SQL Injection để dò tìm cấu trúc dữ liệu (Gồm những table nào, có những column gì), sau đó bắt đầu khai thác dữ liệu bằng cách sử dụng các câu lệnh như UNION, SELECT TOP 1…
Cách phòng chống:
Hiện nay, hầu hết các framework đều hỗ trợ việc sinh ra các câu lệnh truy vấn tự động thay vì sử dụng các lệnh SQL thô ví dụ trong Rails:
User.where(email: params[:email]).first
//Hoặc
User.where("email = ?", params[:email]).first
May thay, mặc dù SQL injection rất nguy hại nhưng cũng dễ phòng chống. Gần đây, hầu như chúng ta ít viết SQL thuần mà toàn sử dụng ORM (Object-Relational Mapping) framework. Các framework web này sẽ tự tạo câu lệnh SQL nên hacker cũng khó tấn công hơn. Ngoài ra trong rail còn một tool dùng để thống kê các lỗ hổng trong hệ thống của bạn đó là gem brakenman(https://github.com/presidentbeef/brakeman) . Sau khi chạy lệnh kiểm tra brakeman sẽ trả về kết quả dưới dạng giao diện web thống kê các lỗ hổng trong hệ thống của bạn đặc biệt sql injection.
Brakenman là một tool dùng để thống kê các lỗ hổng có thể bị tấn công vào hệ thống của bạn, nếu có thể hãy thử cài đặt và chạy rất đơn giản thôi bạn có thể thấy được những lỗ hổng trong hệ thống của bạn
Summary
Những lỗ hổng trên mình giới thiệu là những lỗ hổng điển hình, tuy nhiên hầu hết các framework hiện nay đều hỗ trợ, tuy nhiên cũng sẽ có những trường hợp đặc biết do đó bạn cần phải hiểu rõ hệ thống của bạn để xử lý những trường hợp đặc biệt đó. Và cuối vẫn là yếu tố on người là then chốt mình nghĩ rằng hệ thống bảo mật đến đâu mà người dùng phần mềm không tuân thủ các quy định bảo mật thì những kẻ tấn công vẫn có cơ hội và cách thức để phá hoại. Đến đây là kết thúc bài viết của mình cảm ơn các bạn đã theo dõi.
Reference
https://nvisium.com/blog/2014/09/10/understanding-protectfromforgery/
http://www.jasonwieringa.com/Learning-About-XSS-Attacks-in-Rails/
http://gavinmiller.io/2015/fixing-sql-injection-vulnerabilities/
https://toidicodedao.com/