Link Search Menu Expand Document

5. 大量賦值(Mass Assignment)

5-1 ActiveRecord 賦值功能

大量賦值(Mass Assignment)是 ActiveRecord 的一個功能,例如以下是新建 Event 的代碼:

event = Event.new
event.name = "Foo"
event.description = "Bar"
event.save

這個 new 可以接受 Hash 來做賦值,上述的代碼等同於:

Event.new( { :name => "Foo", :description => "Bar" } )

另外,放在方法參數最後面的 Hash,它的 { } 是可以省略的,因此這又等同於:

Event.new( :name => "Foo", :description => "Bar" )

這種用法就叫做大量賦值(Mass Assignment)。除了 new 之外,update 也是一樣的,這個我們在做 CRUD 時就有看過,例如:

event = Event.first
event.name = "Foo"
event.description = "Bar"
event.save

等同於

event = Event.first
event.update( :name => "Foo", :description => "Bar" )

5-2 大量賦值與 Rails 表單

這個功能的主用用途是可以跟 Rails form_for 方法產生的表單做配套,在以下的 Event 表單中,Rails 故意將 HTML 輸入框 input 的 name 命名成 event[name]event[description]

image

這樣送出後,Rails 會解析成 params[:event] 剛好是一個 Hash,可以跟大量賦值用法配套在一起:

image

在早先的 Rails 版本中,可以直接把 params[:event] 丟進 newupdate 中,例如:

Event.new(params[:event])

@event.update( params[:event] )

不過這樣卻存在一個大漏洞,用戶可以自行修改表單 input 的 name。這很簡單你用 Chrome 除錯器就可以辦到了,點該 input 的 name 就可以改:

image

這裡改成 event[user_id],送出之後如果直接調用@event.update( params[:event] )的話,那就修改到 user_id 了 🙀🙀🙀

因此在新版的 Rails 中,必須經過 Strong Parameters 過濾後,才能傳進 newupdate 之中,以下這段代碼大家都熟悉:

  def update
    @event = Event.find(params[:id])

    if @event.update(event_params)
      redirect_to admin_events_path
    else
      render "edit"
    end
  end

  protected

  def event_params
    params.require(:event).permit(:name, :description)
  end

其中 params.require(:event).permit(:name, :description) 就是在做白名單的檢查,只能允許 params[:event][:name]params[:event][:description]

5-3 攻擊示範

不過即使有 Strong Parameters 的保護,還是可能因為程序員漫不經心而存在漏洞。讓我們來攻擊看看。

請點選主選單上的修改個人資料,不過我們不是想要修改暱稱,我們想要來修改 Role 角色….

  1. 透過 Chrome 除錯器將暱稱 input 的 name 為 user[role]
  2. 輸入框輸入 admin
  3. 按下送出

image

然後 hacker 就變成 admin 了,你現在可以在主選單上點進後台…. 🙀🙀🙀🙀🙀

5-4 修補漏洞

讓我們看看到底哪裡出了漏洞,請打開 app/controllers/users_controller.rb,問題出在 params.require(:user).permit(:nickname, :role) 這一行太過放縱了,竟然允許用戶可以傳參數 role 進來… :(

雖然在網頁表單上面沒有 role 輸入框,但是用戶依然可以自己想辦法把參數傳進來。

讓我們修補這個漏洞:

  def update
    @user = current_user

    if @user.update(user_params)
      redirect_to user_path(@user)
    else
      render "edit"
    end
  end

  protected

  def user_params
-    params.require(:user).permit(:nickname, :role)
+    params.require(:user).permit(:nickname)
  end

再測試一次,駭客就不能修改 role 了。在 Rails log 之中,會出現

image

Unpermitted parameter: role 的意思就是用戶傳了一個 role 參數進來被過濾掉了。

從這個漏洞我們也可以瞭解為什麽前臺、後台的 controller 會拆開的一個原因,就是前後台的 Strong Parameters 參數白名單是不一樣的。給前臺用戶的 users_controller 不允許修改 role,但是後台 admin::users_controller 是可以允許修改 role 的。如此拆分才會清楚不會搞混造成漏洞。


Copyright © 2010-2021 Wen-Tien Chang All Rights Reserved.