Безпечний ActiveRecord, як запит


85

Я намагаюся написати запит LIKE.

Я прочитав, що чисті рядкові запити не є безпечними, проте я не міг знайти жодної документації, яка б пояснила, як правильно писати безпечний LIKE Hash Query.

Це можливо? Чи слід мені захищатись вручну від SQL Injection?


Відповіді:


166

Щоб переконатися, що рядок запиту належним чином дезінфікований, використовуйте масив або синтаксис хеш-запиту, щоб описати свої умови:

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

або:

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

Якщо це можливо , що queryможе включати в себе %характер , то вам необхідно дезінфікувати queryз sanitize_sql_likeпершим:

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

Це не вдається уникнути %в рядку запиту. Це не довільна "ін'єкція SQL", але все одно може діяти несподівано.
Бені Чернявський-Паскін

@ BeniCherniavsky-Paskin: У цьому вся суть, ви не хочете уникати, %тому що %це частина LIKEсинтаксису. Якщо ви втекли, %тоді результат в основному був би звичайним =запитом.
spickermann

1
Правильно, ВИ хочете використовувати% шаблонів у шаблоні шаблону, але цей шаблон параметризований queryзмінною, і в багатьох випадках ви хочете буквально збігати рядок у queryзмінній, не дозволяючи queryвикористовувати LIKE метасимволи. Давайте візьмемо більш реалістичний приклад, що% ...%: рядки мають структуру, подібну до контуру, і ви намагаєтеся зіставити /users/#{user.name}/tags/%. Тепер, якщо я fr%d%влаштую своє ім'я користувача , я зможу спостерігати fredі fridaтеги ...
Бені Чернявський-Паскін

2
Добре, я шукаю поєднання цього питання зі stackoverflow.com/questions/5709887/…, що пропонує sanitize_sql_like().
Бені Чернявський-Паскін

2
@ BeniCherniavsky-Paskin Тепер я розумію, звідки ти родом, і ти маєш рацію. Я оновив свою відповідь для вирішення цього питання.
spickermann

34

За допомогою Arel ви можете виконати цей безпечний та портативний запит:

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

1
Це найкраще рішення, оскільки Arel є sql-db-агностиком і має деяке внутрішнє очищення вхідних даних. Також набагато читабельніше та послідовніше, що стосується стилю коду, IMHO.
Andrew Moore

Як ви це заперечуєте? (тобто НЕ ПОДОБАЄТЬСЯ) Model.where(title.matches("%#{query}%").not)працює, хоча згенерований SQL трохи незручний:WHERE (NOT (`models`.`title` LIKE '%foo%'))
Noach Magedman

Ааа ... знайшов. Model.where(title.does_not_match("%#{query}%")). Генерує: WHERE (`models`.`title` NOT LIKE '%foo%')
Ноач Магедман

Обережно - це не вдається дезінфікувати %в ненадійному введенні: >> 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 або Model.where.not(title.matches("%#{query}%")). does_not_matchчитає краще, правда, IMO.
elquimista


1

Ви можете зробити

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

1
@mikkeljuhl Будь ласка, уважно подивіться на мою відповідь.
Сантош

0

Якщо хтось виконує пошуковий запит щодо вкладеної асоціації, спробуйте:

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

Для кількох атрибутів спробуйте наступне:

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

Не забудьте замінити AssociatedModelNameна назву вашої моделі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.