ActiveRecord an toàn như truy vấn


85

Tôi đang cố gắng viết truy vấn LIKE.

Tôi đọc rằng quy trình chuỗi thuần túy không an toàn, tuy nhiên tôi không thể tìm thấy bất kỳ tài liệu nào giải thích cách viết Truy vấn băm LIKE an toàn.

Có khả thi không? Tôi có nên bảo vệ thủ công trước SQL Injection không?


Câu trả lời:


166

Để đảm bảo rằng chuỗi truy vấn của bạn được làm sạch đúng cách, hãy sử dụng mảng hoặc cú pháp truy vấn băm để mô tả các điều kiện của bạn:

Foo.where("bar LIKE ?", "%#{query}%")

hoặc là:

Foo.where("bar LIKE :query", query: "%#{query}%")

Nếu có khả năng querycó thể bao gồm %ký tự thì trước tiên bạn cần phải làm sạch querybằng sanitize_sql_like:

Foo.where("bar LIKE ?", "%#{sanitize_sql_like(query)}%")
Foo.where("bar LIKE :query", query: "%#{sanitize_sql_like(query)}%")

Điều này không thoát được %trong chuỗi truy vấn. Nó không phải là "SQL injection" tùy ý nhưng vẫn có thể hoạt động bất ngờ.
Beni Cherniavsky-Paskin

@ BeniCherniavsky-Paskin: Đó là toàn bộ điểm, bạn không muốn thoát %%nó là một phần của LIKEcú pháp. Nếu bạn thoát %thì kết quả về cơ bản sẽ là một =truy vấn bình thường .
spickermann

1
Đúng vậy, BẠN muốn sử dụng% ký tự đại diện trong mẫu mẫu của mình nhưng mẫu đó được tham số hóa với querybiến và trong nhiều trường hợp, bạn muốn khớp theo nghĩa đen của chuỗi trong querybiến, không cho phép querysử dụng ký tự siêu nhỏ LIKE. Hãy lấy một ví dụ thực tế hơn rằng% ...%: các chuỗi có cấu trúc giống như đường dẫn và bạn cố gắng khớp /users/#{user.name}/tags/%. Bây giờ nếu tôi sắp xếp tên tôi là fr%d%, tôi sẽ có thể quan sát fredfrida's thẻ ...
Beni Cherniavsky-Paskin

2
Được rồi, điều tôi đang tìm là kết hợp câu hỏi này với stackoverflow.com/questions/5709887/… mà gợi ý sanitize_sql_like().
Beni Cherniavsky-Paskin

2
@ BeniCherniavsky-Paskin Bây giờ tôi hiểu bạn đến từ đâu và bạn đã đúng. Tôi đã cập nhật câu trả lời của mình để giải quyết vấn đề đó.
spickermann

34

Sử dụng Arel, bạn có thể thực hiện truy vấn an toàn và di động này:

title = Model.arel_table[:title]
Model.where(title.matches("%#{query}%"))

1
Đây là giải pháp thích hợp hơn, vì Arel là sql-db-bất khả tri và có một số làm sạch đầu vào bên trong. IMHO cũng dễ đọc và nhất quán hơn nhiều so với kiểu mã.
Andrew Moore

Làm thế nào để bạn phủ nhận điều này? (tức là không thích) Model.where(title.matches("%#{query}%").not)hoạt động, mặc dù SQL được tạo ra là một chút lúng túng:WHERE (NOT (`models`.`title` LIKE '%foo%'))
Noach Magedman

Aah ... tìm thấy nó. Model.where(title.does_not_match("%#{query}%")). Tạo: WHERE (`models`.`title` NOT LIKE '%foo%')
Noach Magedman

Cẩn thận - điều này không thể làm sạch %ở đầu vào không đáng tin cậy: >> ActiveRecord::VERSION::STRING => "5.2.3" >> field = Foo.arel_table[:bar] >> Foo.where(field.matches('%')).to_sql => "SELECT `foos`.* FROM `foos` WHERE `foos`.`bar` LIKE '%'"
vjt

@NoachMagedman hoặc Model.where.not(title.matches("%#{query}%")). does_not_matchđọc tốt hơn mặc dù, IMO.
elquimista

7

Đối với PostgreSQL, nó sẽ là

Foo.where("bar ILIKE ?", "%#{query}%") 

1

Bạn có thể làm

MyModel.where(["title LIKE ?", "%#{params[:query]}%"])

1
@mikkeljuhl Vui lòng xem kỹ câu trả lời của tôi.
Santhosh

0

Trong trường hợp nếu bất kỳ ai thực hiện truy vấn tìm kiếm trên liên kết lồng nhau, hãy thử điều này:

Model.joins(:association).where(
   Association.arel_table[:attr1].matches("%#{query}%")
)

Đối với nhiều thuộc tính, hãy thử điều này:

Model.joins(:association).where(
  AssociatedModelName.arel_table[:attr1].matches("%#{query}%")
    .or(AssociatedModelName.arel_table[:attr2].matches("%#{query}%"))
    .or(AssociatedModelName.arel_table[:attr3].matches("%#{query}%"))
)
 

Đừng quên thay thế AssociatedModelNamebằng tên kiểu máy của bạn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.