Безопасный запрос типа ActiveRecord

89

Я пытаюсь написать запрос LIKE.

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

Является ли это возможным? Должен ли я вручную защищаться от внедрения SQL-кода?

Гал Вайс
источник
Возможный дубликат Как сделать запрос LIKE в Arel и Rails?
Педро Роло

Ответы:

168

Чтобы гарантировать, что ваша строка запроса будет правильно обработана, используйте массив или синтаксис хеш-запроса для описания ваших условий:

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)}%")
Spickermann
источник
Этого не удается избежать %в строке запроса. Это не произвольная «SQL-инъекция», но она может работать неожиданно.
Бени Чернявский-Паскин
@ BeniCherniavsky-Paskin: В том-то и дело, вы не хотите убегать, %потому что %это часть LIKEсинтаксиса. Если вы экранировали, %то результатом будет обычный =запрос.
spickermann
2
Правильно, ВЫ хотите использовать подстановочные знаки% в своем шаблоне шаблона, но этот шаблон параметризован queryпеременной, и во многих случаях вы хотите буквально сопоставить строку в queryпеременной, не позволяя queryиспользовать метасимволы LIKE. Давайте возьмем более реалистичный пример:% ...%: строки имеют структуру, подобную пути, и вы пытаетесь сопоставить /users/#{user.name}/tags/%. Теперь, если я сделаю свое имя пользователя fr%d%, я смогу наблюдать fredи fridaтеги ...
Бени Чернявский-Паскин
2
Хорошо, то, что мне нужно, объединяет этот вопрос с stackoverflow.com/questions/5709887/…, который предлагает sanitize_sql_like().
Бени Чернявский-Паскин
2
@ BeniCherniavsky-Paskin Теперь я понимаю, откуда вы, и вы правы. Я обновил свой ответ, чтобы решить эту проблему.
spickermann
36

Используя Arel, вы можете выполнить этот безопасный и переносимый запрос:

title = Model.arel_table[:title]
Model.where(title.matches("%#{query}%"))
Педро Роло
источник
1
Это предпочтительное решение, поскольку Arel не зависит от sql-db и имеет некоторую внутреннюю очистку ввода. Кроме того, он намного более четкий и последовательный в плане стиля кода, ИМХО.
Эндрю Мур
Как вы это отрицаете? (т.е. НЕ НРАВИТСЯ) Model.where(title.matches("%#{query}%").not)работает, хотя сгенерированный SQL- WHERE (NOT (`models`.`title` LIKE '%foo%'))
запрос
Аа ... нашел. 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читается лучше, ИМО.
elquimista
7

Для PostgreSQL это будет

Foo.where("bar ILIKE ?", "%#{query}%") 
Хога
источник
1

Ты можешь сделать

MyModel.where(["title LIKE ?", "%#{params[:query]}%"])
Сантош
источник
1
@mikkeljuhl Пожалуйста, внимательно посмотрите на мой ответ.
Santhosh
1

Если кто-то выполняет поисковый запрос по вложенной ассоциации, попробуйте следующее:

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на название вашей модели

Aarvy
источник