內容安全策略(CSP),防禦 XSS 攻擊的好助手
什麼是 CSP?
其核心思想十分簡單:網站通過發送一個 CSP 頭部,來告訴瀏覽器什麼是被授權執行的與什麼是需要被禁止的。
這裡有一個 PHP 的例子:
<?php
header("Content-Security-Policy: <your directives>");
?>
一些指令
你可以定義一些全局規則或者定義一些涉及某一類資源的規則:
default-src 'self' ;
# self = 同埠,同域名,同協議 => 允許
基礎參數是 default-src
:如果沒有為某一類資源設置指令規則,那麼瀏覽器就會使用這個默認參數值。
script-src 'self' www.google-analytics.com ;
# 來自這些域名的 JS 文件 => 允許
在這個例子中,我們已經授權了 www.google-analytics.com 這個域名來源的 JavaScript 文件使用到我們的網站上。我們也添加了 'self'
這個關鍵詞;如果我們通過 script-src
來重新設置其它的規則指令,它將會覆蓋 default-src
規則。
如果沒有指明協議(scheme)或埠,它就會強制選擇與當前頁面相同的協議或埠。這樣做防止了混合內容(LCTT 譯註:混合內容指 HTTPS 頁面中也有非 HTTPS 資源,可參見: https://developer.mozilla.org/zh-CN/docs/Security/MixedContent )。如果頁面是 https://example.com,那麼你將無法載入 http://www.google-analytics.com/file.js 因為它已經被禁止了(協議不匹配)。然而,有一個例外就是協議的提升是被允許的。如果 http://example.com 嘗試載入 https://www.google-analytics.com/file.js,接著協議或埠允許被更改以便協議的提升。
style-src 'self' data: ;
# Data-Uri 嵌入 CSS => 允許
在這個例子中,關鍵詞 data:
授權了在 CSS 文件中 data 內嵌內容。
在 CSP 1 規範下,你也可以設置如下規則:
img-src
有效的圖片來源connect-src
應用於 XMLHttpRequest(AJAX),WebSocket 或 EventSourcefont-src
有效的字體來源object-src
有效的插件來源(例如,<object>
,<embed>
,<applet>
)media-src
有效的<audio>
和<video>
來源
CSP 2 規範包含了如下規則:
child-src
有效的 web workers 和 元素來源,如<frame>
和<iframe>
(這個指令用來替代 CSP 1 中廢棄了的frame-src
指令)form-action
可以作為 HTML<form>
的 action 的有效來源frame-ancestors
使用<frame>
,<iframe>
,<object>
,<embed>
或<applet>
內嵌資源的有效來源upgrade-insecure-requests
命令用戶代理來重寫 URL 協議,將 HTTP 改到 HTTPS (為一些需要重寫大量陳舊 URL 的網站提供了方便)。
為了更好的向後兼容一些廢棄的屬性,你可以簡單的複製當前指令的內容同時為那個廢棄的指令創建一個相同的副本。例如,你可以複製 child-src
的內容同時在 frame-src
中添加一份相同的副本。
CSP 2 允許你添加路徑到白名單中(CSP 1 只允許域名被添加到白名單中)。因此,相較於將整個 www.foo.com 域添加到白名單,你可以通過添加 www.foo.com/some/folder 這樣的路徑到白名單中來作更多的限制。這個需要瀏覽器中 CSP 2 的支持,但它很明顯更安全。
一個例子
我為 Web 2015 巴黎大會上我的演講 「CSP in Action」製作了一個簡單的例子。
在沒有 CSP 的情況下,頁面展示如下圖所示:
不是十分優美。要是我們啟用了如下的 CSP 指令又會怎樣呢?
<?php
header("Content-Security-Policy:
default-src 'self' ;
script-src 'self' www.google-analytics.com stats.g.doubleclick.net ;
style-src 'self' data: ;
img-src 'self' www.google-analytics.com stats.g.doubleclick.net data: ;
frame-src 'self' ;");
?>
瀏覽器將會作什麼呢?它會(非常嚴格的)在 CSP 基礎規則之下應用這些指令,這意味著任何沒有在 CSP 指令中被授權允許的都將會被禁止(「blocked」 指的是不被執行、不被顯示並且不被使用在網站中)。
在 CSP 的默認設置中,內聯腳本和樣式是不被授權的,意味著每一個 <script>
,onclick
事件屬性或 style
屬性都將會被禁止。你可以使用 style-src 'unsafe-inline' ;
指令來授權使用內聯 CSS。
在一個支持 CSP 的現代瀏覽器中,上述示例看起來如下圖:
發生了什麼?瀏覽器應用了指令並且拒絕了所有沒有被授權的內容。它在瀏覽器調試終端中發送了這些通知:
如果你依然不確定 CSP 的價值,請看一下 Aaron Gustafson 文章 「More Proof We Don't Control Our Web Pages」。
當然,你可以使用比我們在示例中提供的更嚴格的指令:
- 設置
default-src
為 'none' - 為每條規則指定你的設置
- 為請求的文件指定它的絕對路徑
- 等
更多關於 CSP 的信息
支持
CSP 不是一個需要複雜的配置才能正常工作的每日構建特性。CSP 1 和 2 是候選推薦標準!瀏覽器可以非常完美的支持 CSP 1。
CSP 2 是較新的規範,因此對它的支持會少那麼一點。
現在 CSP 3 還是一個早期草案,因此還沒有被支持,但是你依然可以使用 CSP 1 和 2 來做一些重大的事。
其他需要考慮的因素
CSP 被設計用來降低跨站腳本攻擊(XSS)的風險,這就是不建議開啟內聯腳本和 script-src
指令的原因。Firefox 對這個問題做了很好的說明:在瀏覽器中,敲擊 Shift + F2
並且鍵入 security csp
,它就會向你展示指令和對應的建議。這裡有一個在 Twitter 網站中應用的例子:
如果你確實需要使用內聯腳本和樣式的話,另一種可能就是生成一份散列值。例如,我們假定你需要使用如下的內聯腳本:
<script>alert('Hello, world.');</script>
你應該在 script-src
指令中添加 sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng=
作為有效來源。這個散列值用下面的 PHP 腳本執行獲得的結果:
<?php
echo base64_encode(hash('sha256', "alert('Hello, world.');", true));
?>
我在前文中說過 CSP 被設計用來降低 XSS 風險,我還得加上「……與降低未經請求內容的風險。」伴隨著 CSP 的使用,你必須知道你內容的來源是哪裡與它們在你的前端都作了些什麼(內聯樣式,等)。CSP 同時可以幫助你讓貢獻者、開發人員和其他人員來遵循你內容來源的規則!
現在你的問題就只是,「不錯,這很好,但是我們如何在生產環境中使用它呢?」
如何在現實世界中使用它
想要在第一次使用 CSP 之後就失望透頂的方法就是在生產環境中測試。不要想當然的認為,「這會很簡單。我的代碼是完美並且相當清晰的。」不要這樣作。我這樣干過。相信我,這相當的蠢。
正如我之前說明的,CSP 指令由 CSP 頭部來激活,這中間沒有過渡階段。你恰恰就是其中的薄弱環節。你可能會忘記授權某些東西或者遺忘了你網站中的一小段代碼。CSP 不會饒恕你的疏忽。然而,CSP 的兩個特性將這個問題變得相當的簡單。
report-uri
還記得 CSP 發送到終端中的那些通知么?report-uri
指令可以被用來告訴瀏覽器發送那些通知到指定的地址。報告以 JSON 格式送出。
report-uri /csp-parser.php ;
因此,我們可以在 csp-parser.php 文件中處理有瀏覽器送出的數據。這裡有一個由 PHP 實現的最基礎的例子:
$data = file_get_contents('php://input');
if ($data = json_decode($data, true)) {
$data = json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
);
mail(EMAIL, SUBJECT, $data);
}
這個通知將會被轉換成為一封郵件。在開發過程中,你可能不會需要比這更複雜的其它東西。
對於一個生產環境(或者是一個有較多訪問的開發環境),你應該使用一種比郵件更好的收集信息的方式,因為這種方式在節點上沒有驗證和速率限制,並且 CSP 可能變得亂鬨哄的。只需想像一個會產生 100 個 CSP 通知(例如,一個從未授權來源展示圖片的腳本)並且每天會被瀏覽 100 次的頁面,那你就會每天收到 10000 個通知啊!
例如 report-uri.io 這樣的服務可以用來簡化你的通知管理。你也可以在 GitHub上看一些另外的使用 report-uri
(與資料庫搭配,添加一些優化,等)的簡單例子。
report-only
正如我們所見的,最大的問題就是在使用和不使用 CSP 之間沒有中間地帶。然而,一個名為 report-only
的特性會發送一個稍有不同的頭部:
<?php
header("Content-Security-Policy-Report-Only: <your directives>");
?>
總的來說,這個頭部就是告訴瀏覽器,「表現得似乎所有的 CSP 指令都被應用了,但是不禁止任何東西。只是發送通知給自己。」這是一種相當棒的測試指令的方式,避免了任何有價值的東西被禁止的風險。
在 report-only
和 report-uri
的幫助下你可以毫無風險的測試 CSP 指令,並且可以實時的監控網站上一切與 CSP 相關的內容。這兩個特性對部署和維護 CSP 來說真是相當的有用!
結論
為什麼 CSP 很酷
CSP 對你的用戶來說是尤其重要的:他們在你的網站上不再需要遭受任何的未經請求的腳本,內容或 XSS 的威脅了。
對於網站維護者來說 CSP 最重要的優勢就是可感知。如果你對圖片來源設置了嚴格的規則,這時一個腳本小子嘗試在你的網站上插入一張未授權來源的圖片,那麼這張圖片就會被禁止,並且你會在第一時間收到提醒。
開發者也需要確切的知道他們的前端代碼都在做些什麼,CSP 可以幫助他們掌控一切。會促使他們去重構他們代碼中的某些部分(避免內聯函數和樣式,等)並且促使他們遵循最佳實踐。
如何讓 CSP 變得更酷
諷刺的是,CSP 在一些瀏覽器中過分的高效了,在和書籤欄小程序一起使用時會產生一些 bug。因此,不要更新你的 CSP 指令來允許書籤欄小程序。我們無法單獨的責備任何一個瀏覽器;它們都有些問題:
- Firefox
- Chrome (Blink)
- WebKit
大多數情況下,這些 bug 都是禁止通知中的誤報。所有的瀏覽器提供者都在努力解決這些問題,因此我們可以期待很快就會被解決。無論怎樣,這都不會成為你使用 CSP 的絆腳石。
via: https://www.smashingmagazine.com/2016/09/content-security-policy-your-future-best-friend/
作者:Nicolas Hoffmann 譯者:wcnnbdk1 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive