rails 7 中 active record 默认防 sql 注入,但仅限参数化查询;字符串插值、动态字段名、order/group/having、find_by_sql 和 connection.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{asc desc}.include?(params[:dir]) ? params[:dir] : "desc" - 字段白名单更健壮的做法是用
User.column_names动态生成:User.column_names.include?(params[:sort]) ? params[:sort] : "id" - 别依赖
ActiveRecord::Base.sanitize_sql_for_order,它不校验字段是否存在,只做基础转义
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传数组、connection.execute配合sanitize_sql、或直接用where/update等安全接口
强参数(Strong Parameters)本身不防 SQL 注入
它只过滤控制器参数;若没配好白名单,你会被迫退回到字符串拼接逻辑——这才是真正的突破口。
-
params.require(:user).permit(:email, :name, :status)确保你能放心写User.where(user_params) - 但
params[:sort]这类动态字段名无法被permit放行,必须单独白名单校验 - 一旦因参数未被允许而 fallback 到
"WHERE #{...}"拼接,注入就发生了
真正容易被忽略的是:字段名、排序方向、分组依据这些“SQL 结构”从不参与参数化,它们必须靠代码逻辑拦截,而不是指望框架自动兜底。











