這是我六月初在新加坡 Reddot Ruby Conf 給的演講。原稿投影片放在 Github 上。這篇文章是我自己整理的 Note。
其他的演講多半是重談 OWASP 那些標準需要注意的地方。這個演講當時在設計時,刻意跟其他演講不太同調。原始設計的出發點著重於:如果是一個熟 Rails 架構的 Hacker
(1) 會如何進攻你的 Application
(2) 初學者會在架構設計上犯哪些錯?
(3) 如果你是 Application 設計者,如何一開始就設計出相對安全的架構
在談 Security 之前,我們要來談談 Rails 是不是個「安全」的框架?
平心而論,Rails 的預設是比許多框架要「安全」許多的,它的設計預設就防了很多攻擊手段、屏蔽了開發者設計上的盲點,或者是預設就內置許多最佳實務。
- Helper / View 預設提供了 HTML Escape : 防止 XSS
- ORM 預設提供了 SQL Escape : 防止 SQL Injection
- 表單提供 Authenticity Token :防止 CSRF
- 在 Production mode 錯誤時直接扔 500 頁面:防止 PHP 忘記關 debug mode 結果吐程式碼這種慘劇...
- 熱門的使用者驗證 Gem 如 Devise 直接內置密碼 Bcrypt 加密 :有些開發者懶得自己寫加密 function 乾脆明碼 password 直上
- Sensitive data filtered from log:Rails 的 Log 自動會濾掉一些關鍵字如 [password],以免幹到 Log 就超精彩..
- 熱門的上傳 Gem 如 Paperclip 內建 Filename Sanitization 的實作:防止 path attack
很多傳統打 PHP 網站的方式,是很難拿來直接打 Rails 的,因為一些常見的路都預設被堵死了....
但。這就表示 Rails 不好打嗎?
這也未必。Rails 是個「Framework」。所以其實也有「Pattern」可以依循。一般要打下一個站,去找 Framework 0day 效率通常太低了。找常見的「人為錯誤」其實是比較快的方式...
1. Massive Assignment
Rails 在表單設計上提供了強大的 Helper,表單的 attribute 直接對應到資料庫的欄位。
會生成
看出規律了嗎?所以如果是這樣,從 Chrome DOM Inspect 塞...
通常就可以達到目的...
因為大部分的 Rails Developer 都會寫出這樣的程式碼:
注意 @topic.update_attributes(params[:topic])
那一段。
也有風險的地方:role_ids
除了單一 attribute 會被攻擊外,另外還有一個虛擬的 attribute 設計會有風險。
在這個 user model 裡,使用者擁有很多角色。
Rails 提供一個十分方便的設計。user 可以會自動擁有一個虛擬的 attribute role_id
可以用。role_id
是 Getter 也是 Setter。
如果你在系統的 rails console 下這樣的指令
Rails 會自動幫這個 user 設上 Role id = 1, 2, 3 的三個角色。之所以會有這個「貼心」設計,是因為「大家」都用 checkbox 作多選角色,所以內建了這個設計方便一次設定上多個角色。範例 code 如下:
但這也就表示,其實你的 application 有可能被這樣的假 DOM 玩到的風險....
特別是大家都曉得 role_id = 1
通常就是 admin
。
需要被檢查的地方:
-
has_many
,has_many :through
involve OWNERSHIP, Permission -
user_roles
,group_users
, …. -
UPDATE
action
幾個可能有效的解法:
Rails 3 可以用的解法
( 這個方法在 Rails4 裡被移除了)
- 使用 whitelist attribute
- 打開
config.active_record.whitelist_attributes = true
- 在 model 裡設定
attr_protected :roles
推薦的作法,同時也是 Rails 4 解法
- 使用 Strong parameters。( Rails 4 如果沒過 permit 會直接丟 exception,算直接根除)
params.require(:topic).permit(:title, :body)
進階做法
使用 Reform。出發點是 validation 的責任不應該在 Model 身上,也不該丟給 Controller 去解決,這件事應該要在 Form 層面被解決掉。Reform 的作者同時也是 Cells 的作者 apotonick。
2. Admin
第二部分要談的是 Admin 的設計。這部分也是很讓人無言的。根據非官方統計,高達...99% 的開發者會把 admin 後台放在 ... /admin
...XD
這樣的設計有幾個 concern :
- 容易被猜到放哪裡
- 容易被 XSS 打到
通常建議的解法
把 admin 拆到其他地方去,如 subdomin,或者是不同 domain。
一些其他的基本解法
- 拆到 Intranet 上。(Ineternet 上基本找不到)
-
WiteList.contains?(request.remote_ip)
只准白名單 - 拆到另外一個 Admin App 去管
也有風險的地方:admin?
這也是另外一個有風險的部份。關於 admin?
的實作。多數的開發者,是這樣做的:
然後,就被針對 massive assignment 的攻擊打下來了...
一些解法
-
Setting.admin_emails.include?(email)
// 起碼沒有那麼直觀 - 使用第三方驗證 gem : 如 Github 的 team warden-github-rails 作驗證。想法是:Github 比你家難打太多了....XD 而且其實會是 Admin 的人通常也是 Github 這個 repo 的參加者,用這種方式去驗證比較方便...
下篇: Secure Your Application : The Basic (2)
上篇: Secure Your Application : The Basic (1)
3. bypass RESTful
RESTful 是初上手 Rails 的開發者,相當討厭的一個課題。因為沒有很好懂,加上被認為不自由。總之,不遵守 RESTful 設計原則原因有很多
- 老子就是討厭 RESTful
- 我...不懂 RESTful 要怎麼寫
- 真的有必要把任何 action 都走 RESTful 嗎?
但是,很多開發者不知道的是:雖然 Rails 提供了 CSRF protection,攻擊者發了錯的 request,就會被導錯誤 422。但是,這樣的預設防禦只在遵守 Rails RESTful 設計規範的情形下運作。
在 Rails 3 的 routes.rb
最下面有一段被註解掉的 code
很多開發者在不知道怎麼設 routing 的情況下就把 match ':controller(/:action(/:id(.:format)))'
這行打開了。
因為打開,程式就可以動了 .....(汗)。
可以繼續快樂的寫 http://example.com/usses/eat/1234 這種直觀的 routing,而不用去管 Rails RESTful 的原則...
這等於把自家大門敞開任人打 XD。
因為 Rails 防禦 CSRF 攻擊的原則上是若對 routing 送了錯誤的 http verb 或者是送了錯的 Authenticity Token,Rails 就視為非法 request 導掉。但是 match
everything 的結果是,所有的 action 都變成了 GET
,一些需要保護的 POST
、PUT
action 就門戶洞開了。
可能的解法:
- 從
routes.rb
幹掉match ':controller(/:action(/:id(.:format)))'
這行 - 設立 coding policy,嚴禁任何人再把他加回去。
Rails4 起也已把預設的這行 example 拿掉了...
4. match in routing
這要從 routing 開始講起。
在 Rails 3 時,因為...我也不知道的原因,match
等於 matches all HTTP verb
。而所有的 custom routing 的教學都推薦用 match
去實作。所以很多 application 裡的 route 很歡樂的到處都是 match
。
但是,到處都是 match 就會造成下面的這種類似的 code 會有中招的風險。(allow using GET to massive delete articles)
解法
- 檢查
routes.rb
幹掉所有的 `match,改用正確的 HTTP verb - Rails 提供正確的 verb 用法可以取代掉
match
:get
,post
,put
,delete
所以上面的 example 可以改成
- 或者是改用
via
下篇: Secure Your Application : The Basic (3)
上篇: Secure Your Application : The Basic (2)
5. bypass HTML Escape
Rails 預設對所有 helper 出來的字串,先都過了一層 html escape。所以是相當安全的。
但是,在開發者設計一些複雜的元件時,通常會不小心打破這個原則。如美術設計師對該元素下了複雜的 HTML,程式設計師為了貪方便可能就會這樣串接,最後再下一個 raw
/ html_safe
...
原來那層天然防護罩就不見了。
網站上哪一些設計容易有這種問題:
- 列表 list
- 麵包屑 breadcrumb
- user name with glyphicons
解法:
回歸最初,該是 HTML 的就不要用 helper,用 partial
也可能有問題的地方:TinyMCE on UGC
有些網站被迫要讓使用者使用 TinyMCE 這種所見即所得的工具。但是這種工具危險之處,在於裡面塞的 HTML 可以非常的有創意...如:
- img
- table
- tbody
- div
- span
都是高危險地帶。
可能的解法:
Rails 提供消毒 Helper。可以設白名單黑名單..
6.bypass SQL escape
Rails 在 ORM 裡面也預設提供 SQL escpe,所以這種 query 是安全的:
但是有些時候,開發者為了實作某些功能,就會不小心用了 find_by_sql
:
或者是開發者根本就不知道 where
的正確用法,在網路上 google 到一個 sample 會動就丟進去了:
高危險地帶:
- Search Functions
- actions with complex options, ex.
:date
,:order
,:field
- actions with complex joins
-
find_by_sql
,count_by_sql
解法
如果只是搜尋的話,可以改用 ransack 這種 gem,可以用相當漂亮的 DSL 設計出複雜的 SQL search,都沒有 SQL Injection 問題..
設計 Application 時,優先使用 ORM 解,非不得已為了效能才手 drop query
延伸資料
- http://rails-sqli.org/ Rails SQL Injection 大全。
下篇: Secure Your Application : The Basic (4)
上篇: Secure Your Application : The Basic (3)
7. Same secret token
Rails project 裡的 config/initializers/secret_token.rb
這個檔案。裡面的 secret_token
是用來 verify signed cookie 的。在產生 Rails project 時,每個 project 也都會生一把不同的 key...
狀況 1 : fork opensource project
不過如果你的 project 不是你產生的,而是 Github 上 clone 的呢?那麼有 99% 的機率,你這個 application 已經暴露在高度風險之上。
- clone 的人忘記跑
rake secrect
產生一把全新的。 - opensource 的人忘記把
secret_token.rb
設進去.gitignore
-
google://secret_token.rb site:github.com
超精彩...
狀況 2 : opensource project owner
- opensource 的人忘記把
secret_token.rb
設進去.gitignore
- 而且 production 用的就是這一把...
該檢查的地方以及怎麼處理
- 如果你的 application 是從 github 上 fork 下來的。抓下來第一件事就是砍
secret_token.rb
,重跑rake secrect
- 如果你是 opensource project 維護者,發布之前,請砍掉
secret_token.rb
,設進 .gitignore,然後換 key。 - Redmine 的作法是預設移除,然後設進 .gitignore
- Discourse 的作法是保留,但用 condition 設定 production 和 development 跑不同的 token,production 跑的是環境變數:
ENV[‘SECRET_TOKEN’]
。
8. scopes
很多 Application 是這種常見的設計,利用一個 check_permission
去應付多種狀況。
但是 check_permission
的狀況很容易不小心寫漏了,就能夠進到該頁面。
問題與解法
- by case 設計在 controller 不是好解法,因為 helper / view 有時候也需要類似邏輯
- 邏輯經過幾次擴充之後就變成漿糊了。擴充了 Controller 忘記改 View ...etc.
- 改用 Cancan 這種 Rule Engine 式的設計方法,全上黑名單,再一個一個設白名單解開,而且寫一次可以套用在所有地方。
9. Upgrades
Rails 在 3.2.11 前的版本,都有一個致命漏洞:可以被 Remote code execution。Remote code execution 表示攻擊者可以對你的 application 你的 server 作 任何事
原理詳見:http://blog.codeclimate.com/blog/2013/01/10/rails-remote-code-execution-vulnerability-explained/
基本上只要這個洞還留著。其他地方再怎麼防都沒有用
解法
- 升級到 3.2.11+
Recap
Security 是一件讓人很頭痛的事。Rails 的 core member : Aaron Patterson 就曾經抱怨,其實他最討厭處理 securtiy
事件。
- 因為,attacker 不會主動回報,就算它跟你講了,你也不知道這個洞已經多久了
- 即便他好心跟你講(寫信到 Rails security mailing list),你也只有「很短」的時間修。很短可能是幾天,也可能是幾小時,但總之...火會燒得超級快。每次遇到資安事件,security team 的人就不用睡了...
- patch 得馬上得 Release。不同於其他 feature release,feature release 都會有時間公測。唯有 security patch 沒有這個時間。而這種 patch 最危險的是不保證會不會炸了其他沒問題的地方。(某次 patch 炸了 Github ...)
- 你努力了拯救了整個世界,但沒人會感謝你。但如果你寫的 patch 炸爛了大家的 production,肯定一堆人角你...
洞是真的防不慎防的。
有時候就算框架本身是沒問題的。但是還是會因為人為疏失被摸進去。
不過如果是人為疏失,其實還是有幾招可以降低發生的機率:
- 注意使用者塞給你的東西 Pay attention of user geneate content
- 不要繞過 Rails 的預設機制 Avoid bypass default design
- 盡量套用 Rails 4 的新機制 Apply new features introduce by Rails 4
- 不要忘記更新 Don’t forgot to upgrade