使用参数化查询防止sql注入,动态字段名需白名单校验,sanitize_sql仅处理值不处理结构。

where子句里用"#{params[:col]}"会直接执行恶意SQL
只要在where、order、group等方法中对用户输入做字符串插值,就等于把数据库控制权交给了攻击者。比如User.where("name = '#{params[:name]}'")遇到params[:name] = "admin' OR '1'='1",实际执行的就是WHERE name = 'admin' OR '1'='1'——全表匹配,绕过所有条件。
用where参数化查询替代字符串拼接
Rails的where方法原生支持问号占位符和命名参数,这是最直接、最可靠的防护方式。
-
User.where("email = ? AND active = ?", params[:email], true)—— 两个?按顺序绑定值,Active Record自动转义 -
User.where("email = :email AND active = :active", email: params[:email], active: true)—— 命名参数更易读,也支持嵌套哈希 - 不要写
User.where("email = '#{params[:email]}'"),哪怕你加了presence或strip也拦不住注入
动态字段名(如排序、搜索列)必须白名单校验
参数化查询只防值,不防字段名、表名、函数名这类SQL结构。所以order("#{params[:sort]} #{params[:dir]}")依然危险。
- 用
in?或include?硬性限制可选字段:params[:sort].in?(%w[email name created_at]) ? params[:sort] : "name" - 查数据库元信息更灵活:
User.column_names.include?(params[:sort]) ? params[:sort] : "id" - 排序方向同理:
params[:dir].in?(%w[asc desc]) ? params[:dir] : "asc" - 别依赖
ActiveRecord::Base.sanitize_sql_for_order,它不校验字段是否存在,只做基础转义
绕过Active Record时务必用ActiveRecord::Base.sanitize_sql
极少数场景需要手写SQL(比如复杂窗口函数、JSONB查询),又不能用find_by_sql的参数化接口,就必须手动消毒。
sql = ActiveRecord::Base.sanitize_sql(["SELECT * FROM users WHERE status = ?", params[:status]])- 这个
sanitize_sql会调用底层connection.quote处理值,但依然不处理字段名——所以字段名仍需白名单 - 绝对避免
connection.execute("UPDATE users SET ... WHERE id = #{params[:id]}")这种裸拼接 - 如果用
find_by_sql,确保整个SQL字符串是静态的,或所有变量都经sanitize_sql处理过
真正容易被忽略的是:字段名、排序方向、分组依据这些“SQL结构”从不参与参数化,它们必须靠代码逻辑拦截,而不是指望框架自动兜底。











