SQL Injection trong Rails: parameter binding, query API và những lối tắt nguy hiểm

· ~2 phút đọc

SQL injection xảy ra khi dữ liệu không đáng tin được ghép vào cấu trúc SQL, khiến input của người dùng biến thành một phần câu lệnh.

1
2
# Nguy hiểm
User.where("email = '#{params[:email]}'")

Kẻ tấn công không cần đội hoodie trong phòng tối. Một dấu nháy đúng chỗ thường đã đủ làm ngày của bạn dài hơn.

Dùng parameter binding

1
2
User.where("email = ?", params[:email])
User.where(email: params[:email])

ActiveRecord tách cấu trúc query khỏi value và bind parameter phù hợp.

Những API vẫn cần cảnh giác

Không phải method ActiveRecord nào cũng tự sanitize mọi string:

1
2
3
User.order(params[:sort])
User.select(params[:columns])
User.from(params[:table])

Tên cột, hướng sort và SQL fragment là identifier/cấu trúc, không phải value bind thông thường. Hãy dùng allowlist:

1
2
3
4
5
6
7
SORT_COLUMNS = {
"name" => :name,
"created_at" => :created_at
}.freeze

column = SORT_COLUMNS.fetch(params[:sort], :created_at)
User.order(column => :desc)

Raw SQL

Nếu thật sự cần SQL thủ công, vẫn bind parameter:

1
2
3
4
5
6
7
sql = User.sanitize_sql_array([
"email = ? AND active = ?",
params[:email],
true
])

User.where(sql)

Tốt hơn nữa, dùng adapter/query API hỗ trợ bind thay vì tự escape. Không tự viết hàm thay ' thành ''; bảo mật không nên phụ thuộc vào một bài replace rất tự tin.

Defense in depth

  • Database user chỉ có quyền cần thiết.
  • Không trả database error chi tiết ra client.
  • Log và monitor query bất thường nhưng không log sensitive input.
  • Validate input để bảo vệ domain; parameterize để bảo vệ SQL. Hai việc khác nhau.
  • Test các query dynamic, đặc biệt sort/filter/reporting.

Prepared statement không sửa được logic authorization. Query an toàn vẫn có thể trả dữ liệu của người khác nếu policy sai.

References

Home