Ruby on Rails 安全全景
1. Rails 安全模型概览
Rails 以"约定优于配置"著称,内置安全机制丰富:
- Strong Parameters(强参数)防止 Mass Assignment;
- CSRF Protection 默认启用;
- BCrypt / Argon2 密码哈希;
- Active Record 自动参数化;
- Action View 默认 HTML 转义;
- HTTP Security Headers(默认启用 hsts)。
漏洞根源:开发者主动"简化"或"绕过"这些安全机制。
2. 经典漏洞详解
2.1 Mass Assignment(Rails 4 之前)
Rails 3 时代的 attr_accessible / attr_protected 声明不够灵活,开发者常在开发阶段省略,导致攻击者通过额外的表单参数直接赋值数据库列。
漏洞代码(Rails 3):
class User < ActiveRecord::Base
# 开发时常忘加 attr_accessible
# attr_accessible :name, :email
end
# controller
def create
User.create(params[:user]) # 直接把 params 全丢进去
end
PoC:
POST /users HTTP/1.1
Host: victim.example.com
Content-Type: application/x-www-form-urlencoded
user[name]=alice&user[email]=alice@example.com&user[role]=admin
# role 字段被直接赋值!alice 变成管理员!
修复:Rails 4+ 强制 Strong Parameters:
private
def user_params
params.require(:user).permit(:name, :email, :password)
# ❌ 忘了 role
# ✅ role 如果是用户自己设置的,放在单独的方法里手动校验
end
2.2 Asset Pipeline 反序列化
Rails 3 / 4 的 Asset Pipeline 会把编译后的文件缓存在 tmp/cache/assets。攻击者如果能控制文件内容(如通过 S3 上传 + 文件包含),就能触发 YAML / Marshal 反序列化 RCE。
CVE-2014-0130 就是这条链。
修复:升级到 Rails 5+,或用 Sprockets 4 + YAML.safe_load。
2.3 Active Record SQL 注入(Rails 5.1 之前)
Rails 的 where 方法支持 hash 形式和数组形式,但开发者常误用字符串形式:
# ❌ 危险
User.where("name = '#{params[:name]}'").first
User.find("id = #{params[:id]}")
User.order(params[:sort]) # 类似 Django order_by
# ✅ 安全
User.where(name: params[:name]).first
User.where("name = ?", params[:name]).first
# 白名单排序
ALLOWED_SORT = { 'id' => 'id ASC', 'name' => 'name DESC' }
User.order(ALLOWED_SORT.fetch(params[:sort], 'id ASC'))
2023 年仍在被利用的 Rails SQL 注入(GitHub Advisory GHSA-34f8-26hw-6wrg):where(foo: bar) 形式中 bar 为数组时存在绕过。
2.4 CVE-2020-8164 Action View 双重序列化
Rails 5.2.4.3 / 6.0.3 以下版本中,当控制器返回 JSON 时,若参数包含 action_dispatch.content_typing,会触发 Marshal.load 反序列化任意对象。
PoC:
POST /users HTTP/1.1
Host: victim.example.com
Content-Type: application/json
Content-Length: 132
{"authenticity_token":"","action_dispatch.content_typing":"BAhvLUkAAnhIVTEEMQV5N2VybnNlYw5TbGVjdGlvblRlbXBsYXRlAApvYg10AAAAAAMKdmFsdWUAAmliAQFzNQx1dXRwYWdlBAAAAA=="}
其中 base64 解码后是:
Marshal.dump(ActionController::Parameters.new("value" => 1, "oops" => OpenStruct.new(eval: "system('id')")))
2.5 CVE-2022-32224 Web Console 路径穿越
Rails Web Console(开发环境)允许通过 /__better_errors/eval 执行任意 Ruby 代码,这在生产环境里被误部署后等同于 RCE。
修复:确保 Web Console 只在 development 环境启用。
2.6 CVE-2023-22667 Content Security Policy 绕过
Rails 7 的 image_tag 生成的 srcset 可被特殊 URL 绕过 CSP。
3. Rails 安全 Checklist
3.1 Gemfile 安全依赖
# 必须锁定版本,避免意外升级
gem 'rails', '~> 7.1', '>= 7.1.3'
gem 'devise'
gem 'bcrypt', '~> 3.1.7'
gem 'rack-attack' # 速率限制
gem 'brakeman', require: false, group: :development
gem 'bundler-audit', require: false, group: :development
gem 'secure_headers'
gem 'audit-log'
gem 'rack-cors'
group :development, :test do
gem 'web-console', '~> 4.2'
end
3.2 ApplicationController 保护
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :set_secure_headers
private
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end
def set_secure_headers
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
end
end
3.3 rack-attack 速率限制
# config/initializers/rack_attack.rb
class Rack::Attack
throttle('req/ip', limit: 300, period: 5.minutes) do |req|
req.ip unless req.path.start_with?('/assets')
end
throttle('login/ip', limit: 5, period: 20.seconds) do |req|
if req.path == '/users/sign_in' && req.post?
req.ip
end
end
self.throttled_response = lambda do |env|
[429, { 'Content-Type' => 'application/json' }, [{ error: 'Too many requests' }]]
end
end
3.4 secure_headers gem
# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
config.hsts = "max-age=31536000; includeSubDomains; preload"
config.xframe_options = "DENY"
config.csp = {
default_src: ["'self'"],
script_src: ["'self'"],
style_src: ["'self'", "'unsafe-inline'"],
img_src: ["'self'", "data:", "https:"],
frame_ancestors: ["'none'"],
}
end
3.5 强制 HTTPS
# config/application.rb
config.force_ssl = true
# 同时设置 secure cookies
config.action_dispatch.cookies_same_site_protection = :strict
4. Brakeman 扫描
# Brakeman 是 Rails 最著名的静态分析工具
gem install brakeman
brakeman . -o report.html
brakeman --confidence-level=2 --no-pager
典型警告项:
- Weak Authentication:密码哈希强度;
- Unsafe Reflection:eval / send / instance_variable_set;
- SQL Injection:动态拼接字符串;
- Mass Assignment:缺少 Strong Parameters;
- Information Disclosure:debug 模式泄露。
5. bundler-audit 依赖检查
gem install bundler-audit
bundler-audit check --update
6. 错误处理
# config/environments/production.rb
config.consider_all_requests_local = false
config.action_dispatch.show_exceptions = true
config.action_controller.raise_on_open_redirects = true
config.log_level = :warn
# 禁止泄露堆栈
config.active_record.verbose_query_logs = false
7. 数据库层加固
# 禁止 DDL 运行时修改
ActiveRecord::Base.class_eval do
unless Rails.env.development?
self.class_eval do
def alter(); raise "Not allowed in production"; end
end
end
end
# SQL 白名单验证
ALLOWED = ['id', 'name', 'email']
scope :sorted, ->(field) {
where.not(nil: true).order(ALLOWED.include?(field) ? field : 'id')
}
8. 升级路线图
| 版本 | 状态 | 安全建议 |
| Rails 5.2 | EOL 2021 | 立刻升级 |
| Rails 6.0 | EOL 2022 | 立刻升级 |
| Rails 6.1 | EOL 2023 | 升级到 7.0+ |
| Rails 7.0 | 安全支持中 | 升级到 7.1 |
| Rails 7.1 | 当前推荐 | 生产首选 |
9. 小结
Rails 安全 = Strong Parameters + secure_headers + rack-attack + brakeman + bundler-audit + force_ssl + 定期升级。
Rails 框架团队的安全意识在所有主流框架中排第一,几乎每个安全功能(Strong Parameters、CSRF、HSTS)都在框架层面强制开启。真正的漏洞大多来自开发者绕过这些默认保护——比如开发时关了 CSRF、写了 raw SQL、或忘了 permit 参数。