iT邦幫忙

2022 iThome 鐵人賽

DAY 29
0
Modern Web

Rails,我要進來囉系列 第 29

第二十九天:Rails 的 Security,Rails 開發者需要注意哪些攻擊方法?

  • 分享至 

  • xImage
  •  

開場白

鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了 Rails 的多資料庫,今天來看 RailsGuide 的 Security 篇,看看 Rails 預設幫我們做了哪些資安的防禦,夠夠~

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day29-1.png

先了解有哪些攻擊,再針對這些攻擊,制定防禦策略,這一篇 RailsGuide 就是以這樣的角度來寫的,我們也跟著這個思路來看 Rails 有哪些策略

Security depends on the people using the framework, and sometimes on the development method. - RailsGuide

RailsGuide 涵蓋的主題

  1. Sessions
  2. CSRF (Cross-Site Request Forgery)
  3. Redirection and Files
  4. Intranet and Admin Security
  5. User Management
  6. Injection
  7. Unsafe Query Generation
  8. HTTP Security Headers
  9. Environment Security
  10. Dependency Management and CVEs
  11. Additional Resources

Sessions

RailsGuide 的 ActionController Session 篇可以知道,Rails 的 Session 可以存放在 Cookie(預設)、CacheActiveRecord 等地方,以下有幾個跟 Session 相關的攻擊方式:

Session Hijacking

駭客各種透過 cookies,偷取使用者個資的一種攻擊方式,其中有幾種相關的攻擊方法:

  1. 如果網站並不是透過 https(沒有 SSL 加密保護傳輸資料),而是用 http 的話,駭客可能透過查看封包,從 header 撈到 cookies,用這個 cookies 偽裝成使用者

    • Rails 的解決方法:提供強制啟用 SSL 的設定選項,使用 SSL 將傳輸資料加密,防止 cookies 洩漏

      config.force_ssl = true
      
  2. 在公共裝置使用後,沒有登出,駭客可以直接使用,或者一樣透過 cookies 拿到 session

    • Rails 的解決方法:沒有,請記得登出,還有開發者記得要做登出按鈕 XD
  3. 有些 Cross-site scripting (XSS) 就是以 cookies 為目標

  4. 還有 Session Fixation 也算是一種 Session Hijacking

這類型的攻擊,主要就是要賣使用者的個資來賺錢,有趣的是,RailsGuide 竟然把個資的價格範圍也列出來了 XDDD

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day29-2.png

Rails 的 Cookie 預設會加密,所以即使駭客拿到 cookies,駭客只能利用這個 cookies,不能去改 cookies 裡面的內容、也不能知道這個 cookies 裡面裝什麼內容,除非他也偷到 server 的 secret key

Replay Attacks

駭客透過得到 cookies 來重複發送請求,如果是一般的請求是沒差,但如果是跟錢有關係的請求,假設網站把點數存在 cookies 裡面,那駭客可以先複製 cookies,再用點數消費,消費完再把原本的 cookies 覆蓋回去,如此就得到無限點數了

  • 解決方法:不要把這種類型的資料存在 cookies 裡面

Session Fixation

駭客透過把駭客已知的 session ID,想方設法(e.g. 透過 XSS 強制修改使用者的 cookies)讓使用者也使用這個 session ID,這樣使用者在自己裝置做的事情,駭客也可以透過同一個 session ID 在自己的電腦也會同步到

  • Rails 的解決方法:
    • 方法一:加入 reset_session,讓每次登入之後,就重新更新 session ID,並且把舊的 session ID 失效,這個方法同樣也適用 Session Hijacking 攻擊,即使駭客拿到舊的 session ID 也沒用(但我猜他能拿到一次,就能拿到更多次 XD)
    • 方法二:在 session 存使用者相關的資料,這樣可以驗證 session ID 和使用者資料是不是吻合,不吻合就 reject 掉
    • Rails 常見的 Devise gem 會自動過期 session

Cross-Site Request Forgery (CSRF)

CSRF,aka. 跨站請求偽造,換句話說就是,讓使用者誤信假網站,假網站卻能對真網站做壞壞的事,的一種攻擊方式

駭客仿造做出了 A’ 網站,讓使用者相信 A’ 就是真正的 A 網站,而駭客在 A’ 網站放了會去執行 A 網站的某些動作,讓使用者點了 A’ 網站的按鈕或圖片之後,卻影響了 A 網站的資料

例如,駭客做了假的 FB 網站,讓使用者點了某個圖片,但這圖片實際上是去執行真正的 FB 的刪除帳號之類的指令(這個舉例是不可能發生的,只是為了好理解)

其中利用的也是剛剛 Session 篇提到的,在對 A 網站發送請求的時候,瀏覽器會自動把 A 網站的 cookies 帶出去,所以才會導致影響到真正的資料

聽起來很荒謬,這種攻擊方式在日常也是很少,在 2006 年只佔小於 0.1% 的攻擊比例

  • Rails 的解決方法:
    • 提供 config.action_controller.default_protect_from_forgery 的預設設定,在 development 之外的環境,預設開啟,防止 CSRF 攻擊
      • 防止的方法是,自動生成一個 csrf token,這個 token 會出現在每個 form,如果發送 form 的時候,沒有帶這個 token 或帶的 token 是錯的,那 Rails 就會報錯

      • 這個 token 會預設加入 X-CSRF-Token 的 header,如果發送 ajax 請求的時候,沒有這個 header 的話,那就不會接受請求

      • 這東西預設在我們的 app/views/layouts/application.html.erb 的 header 裡面,以 <%= csrf_meta_tags %> 的形式出現

        # app/views/layouts/application.html.erb
        <!DOCTYPE html>
        <html>
          <head>
            <title>YourApp</title>
            <meta name="viewport" content="width=device-width,initial-scale=1">
            <%= csrf_meta_tags %>
            <%= csp_meta_tag %>
        
            <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
            <%= javascript_importmap_tags %>
          </head>
        
          <body>
            <%= yield %>
          </body>
        </html>
        
    ps. 如果是 XSS 攻擊的話,那 CSRF 也擋不住,駭客可以直接操作畫面上的元件

別相信使用者輸入的參數、檔案

  1. redirect_to 接使用者傳進來的參數,可能會導致導轉到可疑的網站,建議用白名單方法,把可接受的參數用白名單列出來比對

  2. 使用者傳進來的檔名,記得也要用白名單過濾,避免駭客用 “../../../etc/passwd” 把 linux 重要的設定檔覆蓋掉

    1. 不要拿掉或取代可疑的字元,像是 ../ 等字元,因為如果駭客傳 ....//,取代掉之後就變成 ../
    2. 同時也要注意 Web App 的執行權限,如果開成系統權限,那檔案真的就會被覆蓋到系統資料夾,權限不要開那麼大,如果真的檔名漏抓了也不至於到整個系統被更改
  3. 注意使用者傳的檔案是可執行的程式碼,這篇有舉例 Apache 的 DocumentRoot 有某些特定副檔名的檔案的話,會直接被執行,如果使用者傳的是惡意的程式碼、同時你收檔案的位置又是 DocumentRoot 或是會直接被執行的路徑的話,那這樣就會直接被執行

  4. 不只使用者上傳的檔案要注意,連要傳給使用者的檔案也要注意,如果傳給使用者的檔案,會依照參數而定的話,也要注意會有以下狀況發生,如果 params[:filename]“../../../etc/passwd”,那重要的檔案就要被拿走了

    send_file('/var/www/uploads/' + params[:filename])
    

SQL Injection

如果我們有個 query 是 Project.where("name = '#{params[:name]}'"),這時候駭客傳進 name 的值 ' OR 1 —,這樣會變成 SELECT ***** FROM projects WHERE name **=** '' OR 1 *--'*,變成拿取空字串或所有,後面的單引號就被註解掉,這樣不管駭客有沒有權限,他都能從資料庫取得想要的資料,這是著名的 SQL Injection

  • Rails 的解決方法:有內建 filter 對於 '" 、NULL、\n 等特殊字元,預設在 Model.find(id)Mode.find_by_sth(something) 會啟動
    • 如果是 xxx.where("…")connection.execute()Model.find_by_sql() 就需要人工處理
    • 可以採用 positional handlers,像是 Model.where("zip_code = ? AND quantity >= ?", entered_zip_code, entered_quantity).first 來處理

Cross-Site Scripting (XSS)

駭客想方設法(典型有幾種的方法)把 JS 程式碼餵給受害者的瀏覽器執行,駭客可能會偷盜使用者的 cookie,再進一步去偷取使用者個資

  • Rails 的解決方法:提供 sanitize 方法,sanitize 是消毒的意思,搭配白名單,阻擋駭客的惡意輸入

    tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
    s = sanitize(user_input, tags: tags, attributes: %w(href title))
    

其他

  1. 小心管理後台的權限控管:可以考慮限制 source IP,因為我們不會需要在全世界都想要登入後台,如果你是公司的話,限定公司的固定 IP
  2. 暴力破解帳號密碼:顯示「帳號或密碼錯誤」而不是「帳號錯誤」、「密碼錯誤」,讓駭客不好組合
  3. 預防駭客的機器人攻擊:使用 CAPTCHA(就是要看圖片填歪七扭八的數字),還可以在 form 加入 hidden 的欄位(叫做 honeypot field,就是誘餌的意思),因為有些爬蟲不會管 field 是不是 hidden,反正有欄位要填就填,這樣機器人就中計了
  4. 小心 Log 會記錄機密資訊:Rails 提供 config.filter_parameters << :password 設定,可以把密碼或其他機密欄位給過濾掉(預設有 :passw, :secret, :token
  5. 使用白名單比黑名單好:使用白名單,就只有我們允許的可以通過,但使用黑名單,卻有一些我們想不到的 edge case 會被鑽漏洞
  6. 別更正使用者的 input,而是直接 reject 掉請求:e.g. "<sc<script>ript>".gsub("<script>", ""),你想把危險的部分拿掉,反而是更危險的

值得注意的點

  1. Ruby 的正規表達式(Regular Expression):在驗證 email、帳號、密碼是否符合規則的時候,正規表達式是最有效率的比對方法
    • 但看了這篇才知道 Ruby 的正規有個漏洞,正常在比對開頭、結尾,會使用 ^ 作為開頭、$ 作為結尾

    • 在 Ruby 的 ^, $行的開頭和結尾,所以假設我們的 pattern 是 /^https?:\/\/[^\n]+$/i,駭客只要傳入像是下面的多行字串,http://hi.com 是行的開頭,所以依舊符合 pattern,駭客就能通過 pattern、並且植入 JS 程式碼

      javascript:exploit_code();/*
      http://hi.com
      */
      
    • 所以要使用 \A 取代 ^、用 \z 取代 $

綜合以上,Rails 針對以上攻擊的普遍防禦方法

  1. Rails 預設會塞入這些 headers

    config.action_dispatch.default_headers = {
      'X-Frame-Options' => 'SAMEORIGIN',  # 防止 cross domain 的 iframe 植入
      'X-XSS-Protection' => '0',  # 不啟用瀏覽器的 XSS 防護,這是比較舊的 header 了,預設是不啟用
      'X-Content-Type-Options' => 'nosniff',  # 阻止瀏覽器去根據檔案內容猜測檔案類型
      'X-Download-Options' => 'noopen', 
      'X-Permitted-Cross-Domain-Policies' => 'none',
      'Referrer-Policy' => 'strict-origin-when-cross-origin'
    }
    

    還有一些常見的 headers 可以自行在 config/application.rbconfig/environments/*.rb 加上去

    • X-Content-Security-Policy
    • Access-Control-Allow-Origin
    • Strict-Transport-Security

    也可以拿掉預設的 headers:config.action_dispatch.default_headers.clea**r**

  2. Rails 有針對 CSP 的設定,可以對不同的資源類型設定不同的規則

    # config/initializers/content_security_policy.rb
    Rails.application.config.content_security_policy do |policy|
      policy.default_src :self, :https
      policy.font_src    :self, :https, :data
      policy.img_src     :self, :https, :data
      policy.object_src  :none
      policy.script_src  :self, :https
      policy.style_src   :self, :https
      # Specify URI for violation reports
      policy.report_uri "/csp-violation-report-endpoint"
    end
    

    也可以對特定資源自訂規則,覆蓋掉 Global 的設定

    class PostsController < ApplicationController
      content_security_policy do |policy|
        policy.upgrade_insecure_requests true
        policy.base_uri "https://www.example.com"
      end
    end
    

    或是對某個 action 取消掉 CSP

    class LegacyPagesController < ApplicationController
      content_security_policy false, only: :index
    end
    
  3. sanize 過濾掉使用者輸入的危險部分

  4. config.force_ssl 避免竊聽

  5. config.action_controller.default_protect_from_forgery 防止 CSRF 攻擊

  6. master.key 加密重要資料為 credentials.yml.enc

總結

這篇 RailsGuide 的文本量超大,再加上有些攻擊方法,沒有實際遇過,很難想像駭客可以怎麼攻,才知道大概怎麼防,其實 Rails 能做的真的也不多,蠻多篇幅都是在提醒開發者可能的陷阱有哪些,剩下的就要靠開發者自己去防治了

好的,這個系列就已經到尾聲了,明天會統整一下這三十篇的心得,我們明天見~


上一篇
第二十八天:讓 Rails 接多個資料庫 - Multiple Databases
下一篇
第三十天:挑戰總結 - 有開源真好
系列文
Rails,我要進來囉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言