ActiveRecord ปลอดภัยเช่นแบบสอบถาม


89

ฉันกำลังพยายามเขียนข้อความค้นหา LIKE

ฉันอ่านแล้วว่าคำถามสตริงที่แท้จริงไม่ปลอดภัย แต่ฉันไม่พบเอกสารใด ๆ ที่อธิบายวิธีการเขียน LIKE Hash Query ที่ปลอดภัย

เป็นไปได้ไหม? ฉันควรป้องกัน SQL Injection ด้วยตนเองหรือไม่


เป็นไปได้ที่จะซ้ำกันของวิธีการทำแบบสอบถาม LIKE ใน Arel และ Rails?
Pedro Rolo

คำตอบ:


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)}%")

สิ่งนี้ล้มเหลวในการหลบหนี%ในสตริงแบบสอบถาม ไม่ใช่ "การแทรก SQL" ตามอำเภอใจ แต่ก็ยังอาจทำงานได้โดยไม่คาดคิด
Beni Cherniavsky-Paskin

@ BeniCherniavsky-Paskin: นั่นคือประเด็นทั้งหมดคุณไม่ต้องการหนี%เพราะ%เป็นส่วนหนึ่งของLIKEไวยากรณ์ ถ้าคุณหนี%ผลลัพธ์ก็จะเป็น=แบบสอบถามปกติ
spickermann

2
ใช่แล้วคุณต้องการใช้% wildcards ในเทมเพลตรูปแบบของคุณ แต่รูปแบบนั้นถูกกำหนดให้เป็นqueryตัวแปรและในหลาย ๆ กรณีคุณต้องการจับคู่สตริงในqueryตัวแปรอย่างแท้จริงไม่อนุญาตให้queryใช้ตัวอักษรLIKE ลองมาเป็นตัวอย่างที่สมจริงมากขึ้นว่า% ... %: /users/#{user.name}/tags/%สตริงมีเส้นทางเช่นโครงสร้างและคุณพยายามที่จะแข่งขัน ตอนนี้ถ้าผมจัดชื่อผู้ใช้ของฉันจะfr%d%ฉันจะสามารถที่จะสังเกตfredและfrida's แท็ก ...
Beni Cherniavsky-ร์กพาสกิน

2
ตกลงสิ่งที่ฉันหลังจากที่มีการรวมคำถามนี้กับstackoverflow.com/questions/5709887/...sanitize_sql_like()ซึ่งแสดงให้เห็น
Beni Cherniavsky-Paskin

2
@ BeniCherniavsky-Paskin ตอนนี้ฉันเข้าใจแล้วว่าคุณมาจากไหนและคุณพูดถูก ฉันอัปเดตคำตอบเพื่อแก้ไขปัญหานั้น
spickermann

36

การใช้ Arel คุณสามารถดำเนินการสืบค้นที่ปลอดภัยและพกพาได้:

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

1
นี่เป็นวิธีที่ดีกว่าเนื่องจาก Arel เป็น sql-db-agnostic และมีการล้างอินพุตภายใน นอกจากนี้ยังมีความชัดเจนและสอดคล้องกันมากขึ้นอีกด้วย 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%')
Noach Magedman

ระวัง - สิ่งนี้ล้มเหลวในการฆ่าเชื้อ%ในอินพุตที่ไม่น่าเชื่อถือ: >> 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 โปรดดูคำตอบของฉันอย่างระมัดระวัง
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ด้วยชื่อรุ่นของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.