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]
這樣送出後,Rails 會解析成 params[:event]
剛好是一個 Hash,可以跟大量賦值用法配套在一起:
在早先的 Rails 版本中,可以直接把 params[:event]
丟進 new
或 update
中,例如:
Event.new(params[:event])
@event.update( params[:event] )
不過這樣卻存在一個大漏洞,用戶可以自行修改表單 input 的 name。這很簡單你用 Chrome 除錯器就可以辦到了,點該 input 的 name 就可以改:
這裡改成 event[user_id]
,送出之後如果直接調用@event.update( params[:event] )
的話,那就修改到 user_id
了 🙀🙀🙀
因此在新版的 Rails 中,必須經過 Strong Parameters 過濾後,才能傳進 new
或 update
之中,以下這段代碼大家都熟悉:
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 角色….
- 透過 Chrome 除錯器將暱稱 input 的 name 為
user[role]
- 輸入框輸入
admin
- 按下送出
然後 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 之中,會出現
Unpermitted parameter: role
的意思就是用戶傳了一個 role
參數進來被過濾掉了。
從這個漏洞我們也可以瞭解為什麽前臺、後台的 controller 會拆開的一個原因,就是前後台的 Strong Parameters 參數白名單是不一樣的。給前臺用戶的 users_controller
不允許修改 role,但是後台 admin::users_controller
是可以允許修改 role 的。如此拆分才會清楚不會搞混造成漏洞。