跨站洩露 (XS-Leaks)

跨站洩露(也稱為 XS-Leaks)是一類攻擊,攻擊者的網站可以透過利用允許網站之間互動的 Web 平臺 API,獲取有關目標網站或使用者與目標網站之間關係的資訊。洩露的資訊可能包括,例如:

  • 使用者是否訪問過目標網站。
  • 使用者是否已登入目標網站。
  • 使用者在該網站上的 ID 是什麼。
  • 使用者最近在該網站上搜索過什麼。

這可能看起來比例如跨站指令碼攻擊造成的危害要小得多,但它仍然可能對使用者造成嚴重後果。例如:

  • 使用者可能在不希望公開的網站上擁有賬戶。將這些資訊洩露給攻擊者可能會使他們面臨敲詐或來自壓迫性政府的報復(例如,針對尋求特定醫療程式資訊的使用者)。
  • 知道使用者在某個網站上擁有賬戶,尤其是如果可以確定他們的使用者 ID,可以使後續的釣魚攻擊更具說服力。

與其他攻擊(例如 XSS點選劫持)不同,跨站洩露不是單一的技術。相反,它們是利用瀏覽器隔離網站方式中的弱點的整類攻擊的統稱。

在本指南中,我們不會嘗試描述所有的跨站洩露攻擊和防禦。相反,我們將首先描述一些示例攻擊,然後概述導致這些攻擊的常見底層弱點,最後描述一些可以抵禦許多已知攻擊的通用防禦措施。

跨站洩露示例

在本節中,我們將描述三種不同的跨站洩露,以瞭解它們的工作原理。

  • 使用錯誤事件洩露頁面存在:在此攻擊中,攻擊者可以透過嘗試將目標網站中的特定端點作為資源載入,並監聽 errorload 事件,來確定它們是否返回 HTTP 錯誤程式碼。如果某些頁面僅對登入使用者可用,攻擊者可以確定使用者是否已登入目標網站。
  • 使用視窗引用進行幀計數:在此攻擊中,攻擊者獲取託管目標網站中頁面的 window 物件的引用,例如作為對 window.open() 呼叫返回的值。攻擊者隨後可以確定目標頁面中 <iframe> 元素的數量,這可能再次揭示使用者是否已登入目標網站。
  • 使用 CSP 洩露重定向:在此攻擊中,攻擊者的頁面具有一個 內容安全策略 (Content Security Policy),該策略只允許載入目標網站中的特定頁面,然後嘗試載入該頁面。如果頁面載入被阻止,攻擊者就知道目標重定向了請求。此重定向可能根據網站的工作方式指示使用者是否已登入(或未登入)。

所有三種攻擊都以相同的方式部署:攻擊者製作一個實現攻擊的頁面,然後說服使用者訪問該頁面,例如透過向他們傳送包含連結的電子郵件或分享帖子。當用戶訪問該頁面時,攻擊會自動執行。

在本節的其餘部分,我們將更詳細地描述這三種攻擊,以便您具體瞭解它們的工作原理。儘管這三種攻擊針對 Web 平臺的完全不同部分,但它們有一個共同的根本原因:瀏覽器透過框架、載入子資源或開啟新視窗等機制使網站相互連線和互動的程度。

注意: 有關更完整的跨站洩露目錄,請參閱 XS-Leaks WikiOWASP 跨站洩露備忘單

使用錯誤事件洩露頁面存在

在此攻擊中,攻擊者透過觀察嘗試將特定頁面作為資源嵌入是否會產生錯誤,來測試目標網站中的特定頁面是否可以載入。如果這些頁面僅對登入使用者可用,攻擊者可以確定使用者是否已登入。

此攻擊依賴於網站從另一個網站載入資源的能力,例如透過將 <script> 元素的 src 屬性設定為資源的 URL:

js
const script = document.createElement("script");
script.src = "https://example.org/admin";
document.head.appendChild(script);

這會導致向 https://example.org/ 網站發出 HTTP 請求。如果請求包含網站用於識別使用者的 cookie,並且請求的頁面僅對登入使用者可用,那麼請求的成功或失敗會揭示使用者是否已登入。

如果請求失敗,例如因為伺服器返回 HTTP 404 狀態碼,則元素會觸發 error 事件。如果請求成功,則元素會觸發 load 事件。透過監聽這些事件,攻擊者可以發現使用者是否已登入。

js
const url = "https://example.org/admin";
const script = document.createElement("script");

script.addEventListener("load", (e) => {
  console.log(`${url} exists`);
});

script.addEventListener("error", (e) => {
  console.log(`${url} does not exist`);
});

script.src = url;
document.head.appendChild(script);

攻擊者甚至可以透過迭代嘗試載入頁面,檢視是否存在類似 https://example.org/users/my_username 的頁面,來發現使用者的 ID。

使用視窗引用進行幀計數

在幀計數攻擊中,攻擊者發現目標頁面中當前載入的幀數。反過來,這會洩露有關目標網站狀態的資訊,這可能使攻擊者能夠了解例如使用者當前是否已登入該網站。

如果攻擊者網站獲取了包含目標網站的 Window 物件的引用,攻擊者可以透過讀取 window.length 屬性來計算目標網站中的幀數。

攻擊者可以透過呼叫 window.open() 獲取 Window 物件:

js
const target = window.open("https://example.org");
const frames = target.length;

或者,攻擊者可以將目標網站嵌入到 <iframe> 中,並檢索幀的 contentWindow 屬性:

html
<iframe src="https://example.org"></iframe>
js
const target = document.querySelector("iframe").contentWindow;
const frames = target.length;

使用 CSP 洩露重定向

在某些網站中,伺服器會根據使用者是否已登入(或在該網站上具有某種特殊狀態)來重定向請求,或不重定向。例如,假設有一個網站,管理員可以在 https://admin.example.org/ 看到一個頁面。如果使用者未登入並請求此頁面,則伺服器可能會將他們重定向到 https://login.example.org/。這意味著如果攻擊者可以確定嘗試載入 https://admin.example.org/ 是否導致重定向,那麼他們就知道使用者是否是該網站的管理員。

在此描述的攻擊中,攻擊者使用 內容安全策略 (CSP) 功能來檢測跨站請求是否被重定向。

  • 首先,他們建立一個由 CSP 管理的頁面,該 CSP 只允許 <iframe> 元素包含來自 https://admin.example.org/ 的內容。

  • 接下來,他們在頁面中新增一個事件監聽器,監聽 securitypolicyviolation 事件。

  • 最後,他們建立一個 <iframe> 元素並將其 src 屬性設定為 https://admin.example.org/

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="frame-src https://admin.example.org/" />
  </head>
  <body>
    <script>
      document.addEventListener("securitypolicyviolation", () => {
        console.log("Page was redirected");
      });
      const frame = document.createElement("iframe");
      document.body.appendChild(frame);
      frame.src = "https://admin.example.org/";
    </script>
  </body>
</html>
  • 如果使用者以管理員身份登入,則 <iframe> 將載入,瀏覽器不會觸發 securitypolicyviolation 事件。
  • 如果使用者未以管理員身份登入,伺服器會重定向到 https://login.example.org/。由於此 URL 不受攻擊者 CSP 的允許,瀏覽器將阻止 <iframe> 並觸發 securitypolicyviolation 事件,攻擊者的事件處理程式將執行。

請注意,即使目標網站使用諸如 frame-ancestors 等機制禁止嵌入,此攻擊也有效。

跨站洩露防禦

跨站洩露利用 Web 平臺中允許網站相互互動的機制。相應地,防禦跨站洩露通常涉及透過停用或控制這些跨站互動來將目標網站與潛在攻擊者隔離

由於跨站洩露可以透過多種不同方式工作,因此沒有單一的防禦措施可以抵禦所有這些洩露。但是,有幾種實踐可以抵禦其中許多洩露,我們在此將它們總結如下。

Fetch 元資料

Fetch 元資料是用於表示一組 HTTP 請求頭的術語,這些請求頭提供有關 HTTP 請求上下文的資訊,包括:

Fetch 元資料頭本身並不是防禦機制,但它使伺服器能夠實現一項策略,該策略將拒絕用於跨站洩露以及其他攻擊(例如 跨站請求偽造 (CSRF) 攻擊)的請求。

例如,使用錯誤事件洩露頁面存在攻擊依賴於攻擊者能夠發出跨站請求,將屬於目標的頁面作為資源載入:

js
// Attempt to load a page in the target as a resource
const script = document.createElement("script");
script.src = "https://example.org/admin";
document.head.appendChild(script);

伺服器可以使用 Fetch 元資料拒絕這些請求,如以下 Express 程式碼所示:

js
function isAllowed(req) {
  // Allow same-origin, same-site, and user-initiated requests
  const secFetchSite = req.headers["sec-fetch-site"];
  if (
    secFetchSite === "same-origin" ||
    secFetchSite === "same-site" ||
    secFetchSite === "none"
  ) {
    return true;
  }

  // Allow cross-site navigations, such as clicking links
  const secFetchMode = req.headers["sec-fetch-mode"];
  if (secFetchMode === "navigate" && req.method === "GET") {
    return true;
  }

  // Deny everything else
  return false;
}

app.get("/admin", (req, res) => {
  res.setHeader("Vary", "sec-fetch-site, sec-fetch-mode");
  if (isAllowed(req)) {
    // Respond with the admin page if the user is admin
    getAdminPage(req, res);
  } else {
    res.status(404).send("Not found.");
  }
});

由於攻擊者的請求是跨站的且不是導航,因此無論使用者是否登入,此伺服器始終會返回錯誤。

請注意,我們還發送了 Vary 響應頭。這確保如果響應被快取,快取的響應將僅提供給 Fetch 元資料頭具有相同值的請求。

這樣的策略稱為資源隔離策略。要了解更多關於使用 Fetch 元資料實現隔離策略的資訊,請參閱 使用 Fetch 元資料保護您的資源免受 Web 攻擊隔離策略

SameSite cookies

SameSite cookie 屬性決定了 cookie 是否會在源自不同站點的請求中傳送。

SameSiteLax 值表示跨站請求只有在請求是頂級導航(基本上意味著瀏覽器位址列中的值更改為目標站點)並且使用 安全 方法(最值得注意的是,這排除了 POST 請求)時才會包含 cookie。

這可以防止一些跨站洩露。例如,使用錯誤事件洩露頁面存在攻擊依賴於攻擊者發出包含使用者會話 cookie 的跨站資源請求。將使用者會話 cookie 上的 SameSite 設定為 Lax 將阻止此攻擊,因為 cookie 不會包含在攻擊者的請求中,並且永遠不會返回需要登入的頁面。

通常,SameSite 應被視為一種縱深防禦措施,應與更明確的隔離策略(例如基於 Fetch 元資料的策略)一起部署。

框架保護

許多跨站洩露依賴於攻擊站點能夠將目標嵌入為 <iframe>。例如,這是攻擊者獲取目標 window 引用的一種方法,以實現幀計數攻擊。

這意味著,除非您需要允許嵌入,否則阻止站點被嵌入是一種良好的實踐;如果您確實需要允許嵌入,則應儘可能限制它。

這裡有兩個相關的工具:

frame-ancestors 指令是 X-Frame-Options 的替代品。儘管 瀏覽器對 frame-ancestors 的支援非常好,但一些非常老的瀏覽器,特別是 Internet Explorer,不支援 frame-ancestors

如果同時設定了 frame-ancestorsX-Frame-Options,那麼支援 frame-ancestors 的瀏覽器將忽略 X-Frame-Options。這意味著沒有理由不同時設定 X-Frame-Optionsframe-ancestors,從而即使在不支援 frame-ancestors 的瀏覽器中也能防止嵌入。

跨域開啟策略 (COOP)

正如我們在幀計數攻擊中看到的那樣,獲取目標 window 物件的另一種方法是作為對 window.open() 呼叫的返回值:

js
const target = window.open("https://example.com");

Cross-Origin-Opener-Policy 響應頭決定文件是否將在與其開啟它的文件相同的 瀏覽上下文組 中開啟。

如果您的伺服器傳送此頭並將其設定為除預設值 "unsafe-none" 之外的任何值,那麼如果來自不同源的文件嘗試使用 window.open() 開啟您的頁面,您的頁面將載入到不同的瀏覽上下文組中。除其他外,這意味著開啟者將無法獲取到您的頁面的 window 物件的引用,因此無法在幀計數攻擊中使用它。

防禦總結清單

跨站洩露包括針對 Web 平臺不同部分的各種攻擊。單一的防禦措施無法抵禦所有這些攻擊,而且一些洩露,例如利用 CSP 洩露重定向的洩露,目前還沒有任何防禦措施。

在本指南中,我們概述了一些有助於將您的網站與潛在攻擊者隔離的防禦措施,我們建議您全部實施:

  • 使用 Fetch 元資料實施資源隔離策略。
  • 如果可能,將會話 cookie 的 SameSite 屬性設定為 Strict;如果必要,則設定為 Lax
  • 使用 frame-ancestors CSP 指令 和 X-Frame-Options 響應頭來防止您的網站被嵌入,或控制哪些網站可以嵌入您的網站。
  • 傳送 Cross-Origin-Opener-Policy 響應頭,以防止其他網站訪問您的 window 全域性物件。

另見