Bezpieczne ActiveRecord, takie jak zapytanie

85

Próbuję napisać zapytanie LIKE.

Czytałem, że wymagania dotyczące czystych ciągów nie są bezpieczne, jednak nie mogłem znaleźć żadnej dokumentacji wyjaśniającej, jak pisać bezpieczne zapytanie LIKE Hash.

Czy to możliwe? Czy powinienem ręcznie bronić się przed iniekcją SQL?

Gal Weiss
źródło
Możliwy duplikat Jak zrobić zapytanie LIKE w Arel i Railsach?
Pedro Rolo,

Odpowiedzi:

166

Aby upewnić się, że ciąg zapytania zostanie prawidłowo oczyszczony, użyj tablicy lub składni zapytania hash do opisania warunków:

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

lub:

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

Jeśli jest to możliwe, że querymoże zawierać %znak, a następnie trzeba zdezynfekować queryz sanitize_sql_likepierwszym:

Foo.where("bar LIKE ?", "%#{sanitize_sql_like(query)}%")
Foo.where("bar LIKE :query", query: "%#{sanitize_sql_like(query)}%")
spickermann
źródło
Nie udaje się to zmienić %w ciągu zapytania. Nie jest to przypadkowe „wstrzyknięcie SQL”, ale nadal może nieoczekiwanie działać.
Beni Cherniavsky-Paskin
@ BeniCherniavsky-Paskin: O to chodzi, nie chcesz uciec od tego, %ponieważ %jest częścią LIKEskładni. Jeśli unikniesz znaku, %wynik będzie w zasadzie zwykłym =zapytaniem.
spickermann
1
Racja, TY chcesz użyć symboli wieloznacznych% w swoim szablonie wzorca, ale ten wzorzec jest sparametryzowany queryzmienną, aw wielu przypadkach chcesz dosłownie dopasować ciąg w queryzmiennej, nie pozwalając queryna użycie metaznaków LIKE. Weźmy bardziej realistyczny przykład, że% ...%: łańcuchy mają strukturę przypominającą ścieżkę, a Ty próbujesz dopasować /users/#{user.name}/tags/%. Teraz, jeśli ustalę moją nazwę użytkownika fr%d%, będę mógł obserwować fredi fridatagi ...
Beni Cherniavsky-Paskin
2
OK, szukam połączenia tego pytania ze stackoverflow.com/questions/5709887/… co sugeruje sanitize_sql_like().
Beni Cherniavsky-Paskin
2
@ BeniCherniavsky-Paskin Teraz rozumiem, skąd pochodzisz i masz rację. Zaktualizowałem odpowiedź, aby rozwiązać ten problem.
spickermann
34

Za pomocą Arel możesz wykonać to bezpieczne i przenośne zapytanie:

title = Model.arel_table[:title]
Model.where(title.matches("%#{query}%"))
Pedro Rolo
źródło
1
Jest to preferowane rozwiązanie, ponieważ Arel jest agnostyczny względem sql-db-agnostic i ma pewne wewnętrzne czyszczenie danych wejściowych. Jest również znacznie bardziej czytelny i spójny, jeśli chodzi o styl kodu, IMHO.
Andrew Moore
Jak to negujesz? (tj. NIE LUBIĘ) Model.where(title.matches("%#{query}%").not)działa, chociaż wygenerowany WHERE (NOT (`models`.`title` LIKE '%foo%'))
kod
Ach ... znalazłem to. Model.where(title.does_not_match("%#{query}%")). Generuje: WHERE (`models`.`title` NOT LIKE '%foo%')
Noach Magedman,
Ostrożnie - nie udaje się to oczyścić %z niezaufanych danych wejściowych: >> 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 lub Model.where.not(title.matches("%#{query}%")). does_not_matchczyta się lepiej, IMO.
elquimista,
7

W przypadku PostgreSQL będzie to

Foo.where("bar ILIKE ?", "%#{query}%") 
Khoga
źródło
1

Możesz to zrobić

MyModel.where(["title LIKE ?", "%#{params[:query]}%"])
Santhosh
źródło
1
@mikkeljuhl Proszę uważnie przyjrzeć się mojej odpowiedzi.
Santhosh
0

W przypadku, gdy ktoś wykonuje zapytanie wyszukiwania na zagnieżdżonym skojarzeniu, spróbuj tego:

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

W przypadku wielu atrybutów spróbuj tego:

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

Nie zapomnij zastąpić AssociatedModelNamenazwą swojego modelu

Aarvy
źródło