12/08/2018, 14:28

Rails SQL Injection

Ruby On Rails cung cấp module ActiveRecord, trong đó xây dựng hàng loạt phương thức giúp thao tác với cơ sở dữ liệu một cách dễ dàng. Nhưng cũng cần phải lưu ý khi xử dụng một số phương thức nhận tham số là param từ client gửi lên, bởi vì nó có thể là lỗ hổng để khai thác lỗi SQL Injection. Ta ...

Ruby On Rails cung cấp module ActiveRecord, trong đó xây dựng hàng loạt phương thức giúp thao tác với cơ sở dữ liệu một cách dễ dàng. Nhưng cũng cần phải lưu ý khi xử dụng một số phương thức nhận tham số là param từ client gửi lên, bởi vì nó có thể là lỗ hổng để khai thác lỗi SQL Injection.

Ta có thể sử dụng gem Brakeman để giúp phát hiện những truy vấn có thể gây ra lỗi. Nhưng để có thể chủ động viết ra những code an toàn hơn, chúng ta hãy cùng nhau tìm hiểu một số phương thức có thể gây ra lỗi SQL Injection:

Các phương thức tính toán

  • average
  • calculate
  • count
  • maximum
  • minimum
  • sum

Ví dụ

params[:column] = "point) where users.id = 1"
User.calculate(:sum, params[:column])
SELECT SUM(age) FROM users WHERE id = 1';) FROM "orders"

Ở câu lệnh trên, thay vì tính tổng số tuổi của tất cả user, câu lệnh chỉ tính tổng tuổi của một user xác định, có id=1

Tương tự đối với các phương thức còn lại

Phương thức delete_all

Bất kỳ phương thức xóa bản ghi nào đều cần được sử dụng cẩn thận! Phương thức delete_all có tham số điều kiện tìm kiếm giống với phương thức find. Tham số có thể là một string, một array, hoặc một hash các điêù kiện. Sử dụng array hoặc hash để an toàn hơn và nhớ rằng không bao giờ truyền trực tiếp tham số được nhập bởi user.

Ví dụ

Câu lệnh sau sẽ xóa toàn bộ user

params[:id] = "1) OR 1=1--"
User.delete_all("id=#{params[:id]}")
DELETE FROM "users" WHERE ((id = 1) OR 1=1--)

Phương thức destroy_all

Phương thức destroy_all sẽ an toàn hơn phương thức delete_all một chút do nó sẽ gọi các call_back tới các model có quan hệ với model đó.

Tương tự phương thức delete_all, tham số điều kiện có thể là một string, một array hoặc một hash các điều kiện. Sử dụng array hoặc hash sẽ an toàn hơn.

Ví dụ

Câu lệnh sau sẽ xóa tất cả các user

params[:admin] = "') OR 1=1--'"
User.destroy_all(["id = ? AND admin = '#{params[:admin]}", params[:id]])
SELECT "users".* FROM "users" WHERE (id = NULL AND admin = ') OR 1=1--')

Phương thức find_by

Thêm vào trong rails 4, find_by/find_by! thực thi tương tự phương thức where(*args).take. Do đó điều kiện cho where được áp dụng. An toàn nhất và cũng hầu hết được sử dụng là truyền một hash các điều kiện.

Ví dụ

Ví dụ sau sẽ tìm users là admin

params[:id] = "admin = 't'"
User.find_by params[:id]
SELECT "users".* FROM "users" WHERE (admin = 't') LIMIT 1

Phương thức from

Phương thức from chấp nhận tham số là một truy vấn bất kỳ.

Ví dụ

Câu lệnh sau sẽ trả về tất cả các user là admin

params[:from] = "users WHERE admin = 't';"
User.from(params[:from])
SELECT "users".* FROM users WHERE admin = 't';

Phương thức group

Phương thức group chấp nhận tham số là một truy vấn bất kỳ.

Vi dụ

Mục đích của truy vấn này là group nhưng users không phải là admin. Thay vào đó lại trả lại tất cả users.

params[:group] = "name UNION SELECT * FROM users"
User.where(:admin => false).group(params[:group])
SELECT "users".* FROM "users" WHERE "users"."admin" = ? GROUP BY name UNION SELECT * FROM users

Phương thức having

Phương thức having không xử lý tham số đầu vào cho nên dễ dàng để khai thác SQL injection bởi vì nó có xu hướng được gọi ở phần cuối câu truy vấn.

Ví dụ

Câu lệnh sau thay vì trả lại danh sách orders của một user thì trả lại tất cả các orders.

params[:total] = "1 UNION SELECT * FROM orders"
Order.where(:user_id => 1).group(:user_id).having("total > #{params[:total]}")
SELECT "orders".* FROM "orders" WHERE "orders"."user_id" = ? GROUP BY "orders"."user_id" HAVING total > 1 UNION SELECT * FROM orders

Phương thức joins

Phương thức joins có thể nhận tham số là một mảng các quan hệ hoặc một string.

Ví dụ

Bỏ qua điều kiện where trả lại tất cả orders thay vì trả lại danh sách order của một user xác đinh.

params[:table] = "--"
Order.joins(params[:table])
SELECT "orders".* FROM "orders" --

Phương thức select

Phương thức select cho phép truyền vào một string, do vậy tham số đầu vào cần được ascape.

Ví dụ

Câu lệnh sau trả về tất cả user, thay vì trả lại chỉ user thường hoặc admin

params[:column] = "WHERE admin = 't' OR 1=1"
User.select(params[:column])
SELECT * FROM users WHERE admin = 't' OR 1=1"

Trên đây mình đã đưa ra một số phương thức có thể khai thác SQL ịnjection khi dữ liệu đầu vào chưa được kiểm tra. Để hiểu rõ hơn các bạn tìm đọc thêm tại đây:

  • Official Rails Security Guide
  • Gem kiểm tra security
  • Rails SQL Injection
0