ActiveRecord天然防SQL注入,前提是使用where(['field'=>$val])、find_by()等参数化接口;但字符串插值如"WHERE name='#{input}'"、未校验的order动态字段、find_by_sql等会绕过防护导致注入。

Active Record 默认就防 SQL 注入,但前提是别绕过它——所有拼接字符串进查询的地方,都是突破口。
哪些 ActiveRecord 查询方式天然免疫?
只要用 where、find_by、order(带白名单)、joins(不拼接条件)等标准接口,并传入参数化结构,Active Record 就会走预处理流程,自动转义输入。
-
User.where(name: params[:name])→ 安全,底层生成WHERE name = $1(PostgreSQL)或问号占位符(MySQL) -
User.find_by(email: params[:email])→ 安全,等价于哈希式where -
User.where("status = ? AND created_at > ?", "active", params[:since])→ 安全,问号占位符强制参数绑定 -
User.where("email = :email", email: params[:email])→ 安全,命名占位符同样受保护
哪些写法看似正常,实则危险?
危险点不在函数名,而在“字符串插值”和“未校验的动态字段名”。Active Record 不会帮你检查 "#{params[:sort]}" 里是不是藏了 email; DROP TABLE users; --。
-
User.order("#{params[:sort]} #{params[:direction]}")→ 危险,直接拼进 ORDER BY 子句,无任何过滤 -
User.where("name = '#{params[:name]}'")→ 危险,单引号+插值,等于给攻击者开后门 -
User.find_by_sql("SELECT * FROM users WHERE name = '#{params[:name]}'")→ 危险,绕过 ActiveRecord 参数化,退化为裸 SQL 拼接 -
User.where(["name = '#{params[:name]}'", params[:name]])→ 危险,数组第一项已是污染字符串,第二项根本不会被用上
如何安全地支持动态排序或字段筛选?
核心原则:字段名和操作符必须来自白名单,不能由用户输入直接决定。Active Record 不负责判断“params[:sort] 是否合法”,这事得你来把关。
- 用白名单硬编码允许的列:
%w{email name created_at}.include?(params[:sort]) ? params[:sort] : "name" - 用
User.column_names动态获取表字段(适合内部工具),但需配合in?或include?校验 - 方向只接受
"asc"或"desc":params[:direction].in?(%w{asc desc}) ? params[:direction] : "asc" - 组合后调用:
User.order("#{safe_sort} #{safe_direction}")—— 注意这里仍是字符串拼接,但内容已可控
什么时候必须用原生 SQL?怎么保安全?
遇到复杂窗口函数、CTE、跨 schema JOIN 等 ActiveRecord 不支持的场景时,find_by_sql 或 connection.execute 才是合理选择。但此时你完全脱离了 ActiveRecord 的防护层。
- 绝不用插值:
"SELECT * FROM users WHERE name = '#{params[:name]}'"→ ❌ - 改用数据库适配器的参数化执行:
ActiveRecord::Base.connection.execute("SELECT * FROM users WHERE name = ?", params[:name])→ ✅(PostgreSQL/MySQL 均支持) - 若必须拼接表名或字段名(如分表查询),先用白名单校验再拼:
"logs_#{year}_#{month}"中的year和month必须是整数且范围合理 -
find_by_sql本身不提供参数绑定,所以应避免传入用户输入的字符串;如需动态条件,优先回到where+ 关系链路
真正容易被忽略的不是“怎么写安全代码”,而是“哪一行正在悄悄绕过 ActiveRecord”。每次看到双引号里有 #{}、单引号套变量、或者 find_by_sql 调用,都该停一下,问自己:这个字符串最终会不会变成 SQL 的一部分?如果是,它的每个字符是否都经过白名单或参数化控制?

















