rails 7 中 active record 默认防 sql 注入,但仅对参数化查询有效;字符串插值、动态 order/group/having、find_by_sql 和 raw execute 必须严格避免拼接用户输入,须用问号占位符、哈希或命名绑定,并对动态字段做白名单校验。

Active Record 在 Rails 7 中默认防 SQL 注入,但仅对参数化查询生效;你不需要“配置”它,真正起作用的是你写查询时有没有绕过绑定机制。
where 和 find_by 必须用参数化写法
这两者语义不同,但防注入逻辑一致:必须走问号占位符、哈希或命名绑定。字符串插值(如 "name = '#{params[:name]}'")会完全跳过 ActiveRecord 的防护层。
-
User.where("email = ? AND active = ?", params[:email], true)—— 顺序绑定,最通用 -
User.where(email: params[:email], active: true)—— 哈希写法,自动处理字段名和值,推荐用于简单等值条件 -
User.find_by("email = :email", email: params[:email])—— 命名绑定可行,但不如哈希直观 -
User.find_by("email = '#{params[:email]}'")—— ❌ 禁止。加了strip或正则也拦不住注入
order/group/having 动态字段必须白名单校验
order 不接受 ? 占位符,所以 User.order("#{params[:sort]} #{params[:dir]}") 是高危操作。Active Record 不会校验字段名是否合法,这事必须你来守门。
- 字段名白名单:
%w{email name created_at}.include?(params[:sort]) ? params[:sort] : "created_at" - 方向白名单:
%w{asc desc}.include?(params[:dir]) ? params[:dir] : "desc" - 更健壮的做法是用
User.column_names动态生成白名单,避免硬编码遗漏新字段
find_by_sql 和 connection.execute 绝对不能拼接用户输入
这些方法绕过 ActiveRecord 全部防护,传入的字符串被原样交给数据库驱动执行。Ruby 插值发生在参数化之前,根本没有转义机会。
-
User.find_by_sql("SELECT * FROM users WHERE name = '#{params[:name]}'")—— ❌ 直接裸奔 -
ActiveRecord::Base.connection.execute("UPDATE users SET status = '#{params[:status]}'")—— ❌ 同样危险 - 正确做法只保留三种参数化形式:
find_by_sql(["SELECT * FROM users WHERE name = ?", params[:name]])
最容易被忽略的点是:强参数(permit)本身不防 SQL 注入,它只过滤控制器参数;但若没配好白名单,你会被迫退回到字符串拼接逻辑——这才是真正的突破口。










