Rails 使用参数提交表单时,要注意保护敏感字段

刚接触 Rails 的人都会对 Rails form 实际特别喜爱,因为它让我们省时省力,就算遇到有 100 多个字段的表单,也能够几下就做出来了,因为在服务端不用再去对每个字段分别写文本框与字段的赋值。

但是如果没有注意保护,使用 Model.create(params[:model]) 的方式提交会有很大的安全漏洞。

下面来看一个例子:

有用户表 [users]

  • id
  • login [用户名]
  • passwd [密码]
  • nick_name [昵称]
  • email [Email]
  • state [状态]
  • group_id [组 [1 管理员,2 编辑,3 普通用户]]
  • exp [经验值]
  • money [金币]
  • level_id [等级]
  • created_at
  • updated_at

注册表单 users/regist.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="register">
  <% form_for @user do |f| -%>
    <p class="row">
      <%= f.label :login, "Login" %>
      <%= f.text_field :login %>  
    </p>      
    <p class="row">
      <%= f.label :passwd, "Password" %>
      <%= f.password_field :passwd %>  
    </p>
    <p class="row">
      <%= f.label :nick_name, "Nick Name" %>
      <%= f.password_field :nick_name %>  
    </p>
    <p class="buttons">
      <%= f.submit "Regist" %>
    </p>
  <% end -%>
</div>

  • 控制器 UsersController.rb

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    class UsersController < ApplicationController
      def index
    
      end
    
      def new
        @user = User.new
      end
    
      def create
        @user = User.new(params[:user])
        if @user.save
          flash[:notice] = "注册成功。"
          redirect_to "/"
        end
      end
    end
    

  • 这是 Rails 里面很常见的写法,但是如果没有做相应的保护措施,那么使用 @user = User.new(params[:user]) 然后 @user.save 这样的方式就会有很严重的问题,因为 HTML 表单是可以通过 Firebug 这类前端调试工具修改的。 比如,现在的注册表单上面有 login,passwd,nick_name 三个字段,我可以使用 Firebug 强制修改 HTML,加上:

    1
    2
    3
    
    <input name="user[:group_id]" type="text" value="1" />
    <input name="user[:money]" type="text" value="9999999" />
    <input name="user[:exp]" type="text" value="9999999" />
    

    然后提交保存… 接下来出现的结果大家应该都能猜到,这个用户的金币和经验值都被强制加上了,而且还注册成为了超级管理员!很恐怖把!看我在 is-programmer.com 上面测试的这个例子 我把访问量修改到上亿次!当然 is-programmer.com 做过这方面的保护,这个地方的问题不大不小,我本想强制注册个超级管理员的…但后面发现有做保护的… 呵呵

    如何保护?

    在 Model 里面使用 attr_accessibleattr_protected 详见:ActiveRecord::Base 文档

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    # models/user.rb
    class User < ActiveRecord::base
      # 使用 attr_protected 保护
      attr_protected :group_id, :money, :exp, :level_id, :state
      # 或使用 attr_accessible
      # attr_accessible :login, :passwd, :email
    end
    
    # controllers/users_controller.rb
    class UsersController < ApplicationController
      def index
      end
    
      def new
        @user = User.new
      end
    
      def create
        @user = User.new(params[:user])
        # 如果需要特别修改 attr_protected 保护的字段,请手动赋值,如
        @user.exp = 1000 # 初始经验值 1000
        @user.level_id = 1
        if @user.save
          flash[:notice] = "注册成功。"
          redirect_to "/"
        end
      end
    end
    

    特别需要更改保护字段的时候,需要使用 @model.money = 55 这样的方式赋值,而直接 @model.update_attributes(params[:model]) 这总方式会把保护字段过滤掉。