Những lỗi bảo mật phổ biến trong Rails

Ruby On Rails là một Framework rất mạnh với số lượng helper và gem lớn, hỗ trợ hữu ích cho Developer tiết kiệm thời gian viết code. Thế nhưng mạnh không đồng nghĩa với độ bảo mật cao. Trong bài viết này tôi sẽ nói về một số lỗ hổng bảo mật cũng nhưng các bước để làm cho ứng dụng của bạn được bảo vệ một cách an toàn hơn.

Các lỗi bảo mật hay gặp gồm có:

Mass assignment XSS attacks. Executing arbitrary code. SQL injections. Form hijacking. Logging private data. Revealing private tokens. Uploading executable files. Using Brakeman to detect possible problems.

Mass assignment

1. Mass Assignment là gì?

Để bắt đầu, trước tiên chúng ta hãy xem mass assignment có nghĩa là gì và tại sao nó lại tồn tại. Thông qua VD sau đây: Chúng ta có 1 class User:

# Assume the following fields: [:id, :first, :last, :email]
class User < ActiveRecord::Base
end

Mass Assignment cho phép chúng ta thiết lập một loạt các thuộc tính cùng một lúc:

attrs = {:first => "John", :last => "Doe", :email => "[email protected]"}
user = User.new(attrs)
user.first #=> "John"
user.last  #=> "Doe"
user.email #=> "[email protected]"

Rõ ràng đoạn code trên đơn giản và rất dễ hiểu phải không? Nhưng đợi đã! Giả sử bây giờ tưởng tượng ứng dụng của chúng ta đã có khả năng cho phép user(admin) có khả năng bắn tên lửa. Vì chúng ta không muốn thế giới biến thành tro, nên chúng ta thêm trường cho phép boolean vào mô hình User để quyết định ai có thể bắn tên lửa.

class AddCanFireMissilesFlagToUsers < ActiveRecord::Migration
  def change
    add_column :users, :can_fire_missiles, :boolean, :default => false
  end
end

Giả sử rằng chúng ta có cách để người dùng chỉnh sửa thông tin liên lạc của họ: đây có thể là một form ở đâu đó mà người dùng có thể truy cập bằng các trường cho first name, last name và email của người dùng. Bạn John Doe của chúng tôi quyết định thay đổi tên và email của mình. Khi submit form trình duyệt gửi 1 request lên UserController:

def update
  user = User.find(params[:id])
  if user.update_attributes(params[:user]) # Mass assignment!
    redirect_to home_path
  else
    render :edit
  end
end

Bây giờ John quyết định không dùng trình duyệt để submit form nữa mà viết 1 đoạn script để gửi params:

PUT http://missileapp.com/users/42?user[can_fire_missiles]=true

Điều gì sẽ xảy ra, Join bây giờ có quyền ấn nút bắn tên lưả mà bình thường chỉ có Ngài Putin mới có. Đây đúng là thảm họa....

2. Cách giải quyết

Từ Rails 4 đã cung cấp strong parameters. Nó chỉ cho phép một user có quyền update thông tin theo đúng thẩm quyền của nó:

# Assume the following fields: [:id, :first, :last, :email]
class User < ActiveRecord::Base
    private
    def user_params
         params.require(:user).permit(:first, :last, :email)
    end
end

Và bây giờ Join không thể "trở thành " ngài Putin được nữa rồi. Bonus Nếu bạn muốn permit tất cả parameter thì chỉ việc sử dụng permit! method:

params.require(:user).permit!

XSS attacks

1. Bản chất của XSS attacks là gì?

Trong ngôn ngữ lập trình/kịch bản, có một số ký tự đặc biệt giúp thông dịch viên/trình phân tích cú pháp phân biệt dữ liệu từ mã thực. Trong HTML, các thẻ bắt đầu và kết thúc bằng các ký tự <>. Trong các thẻ, chúng tôi phân biệt các thuộc tính từ dữ liệu bằng cách đặt thứ hai giữa dấu nháy đơn hoặc dấu ngoặc đơn. Những ký tự đặc biệt này có thể là một phần của dữ liệu cần thiết để được hiển thị trong một trang HTML. Trong trường hợp này, các ký tự đó phải được thay thế bằng những cái vô hại để đảm bảo chúng không được coi là các thẻ HTML thông thường.

Note: Rails sử dụng ERB :: Util # html_escape chức năng để thoát khỏi thực thể HTML. Chức năng này đặc biệt nào thay thế dựa trên băm sau đây (thêm về ERB :: Util).

{ '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }

Đây là 1 ví dụ:

<b>Hello <%= params[:name] %></b>

Trong trường hợp cơ bản thì không sao, nhưng nếu name là:

<script>alert(1)</script>

Như bạn thấy, nếu ai khác được giao nhiệm vụ viết bài báo cho blog, có một kẻ xâm nhập có thể cố gắng chèn một vài dòng Javascript vào nội dung của bài báo, chúng cũng tương tự như các đoạn script khác. Một hacker có thể hiển thị một vài thông báo trên trang, hay chuyển hướng user đến các trang khác bằng cách thêm window.location.href attribute. Thậm chí, hacker có thể nhúng một key logger, ghi lại các phím bạn ấn trên trong web của bạn để trôm mật khẩu hay các thông tin khác, điều đó thật tệ. Tránh sử dụng html_safe trực tiếp.

  • Truyền dữ liệu trực tiếp vào thẻ html:
<b>Hello <%=params[:name]%></b>

Khi Rails nhận một đầu vào dữ liệu là mã độc như đoạn alert trên chẳng hạn, nó sẽ thoát ra khỏi nó và báo về cho user. Trong ví dụ này, dữ liệu đã được đặt trong 1 cặp thẻ <b>, điều đó tránh được các lỗi bảo mật.

  • Truyền dữ liệu vào attributes hoặc tag name của các thẻ html:
<b name=”<%=params[:name]%>”>Hey All!</b>

Có một số attributes đặc biệt trong html có khả năng ảnh hưởng đến sự thực thi của trang, VD như name, href, src. Tương tự như trên, trong trường hợp này cũng k nên truyền trực tiếp giá trị cho dữ liệu.

  • Truyền số vào javascript: 1 VD đơn giản là truyền số vào javascript như sau:
<script>current_user_id = <%=param[:id]%></script>

Nếu như đoạn code trên bị tấn công với một tham số như: ?id=alert(document.cookie) thì output sẽ là:

<script>current_user_id = alert(document.cookie)</script>

SQL injections

1. Tấn công SQL injections là gì?

SQL injection là một kỹ thuật cho phép hacker tấn công lợi dụng lỗ hổng của việc kiểm tra dữ liệu đầu vào trong các ứng dụng web và các thông báo lỗi của hệ quản trị cơ sở dữ liệu trả về để thêm vào các lệnh SQL và thực thi chúng bất hợp pháp để lấy dữ liệu. SQL injections thường được dùng để truy cập vào các dữ liệu nhạy cảm mà user không có quyền thay đổi. Trong hầu hết các trường hợp, Rails dev luôn được bảo vệ khỏi SQL injections bởi strong params, ví dụ với xử lý:

user = User.find_by(id: params[:id])

Điều này hoàn toàn an toàn. Thế nhưng với xử lý này:

user = User.where("id = '#{params[:id]}'")

là không được kiểm tra trước, do đó đừng bao giờ làm theo. Một hacker có thể sửa lại đường link http://yoursite.com/user?id=' OR 1 và toàn bộ user trên trang web sẽ hiển thị ra. Thay vào đó bạn nên sử dụng:

user = User.where("id = ?", params[:id])

Tài liệu tham khảo: https://www.netsparker.com/blog/web-security/preventing-xss-ruby-on-rails-web-applications/