由“Content-Security-Policy“頭缺失引起的總結

概述

最近,使用APPSCAN掃描系統(tǒng)時,掃描出存在"Content-Security-Policy"頭缺失漏洞。

1. 同源策略是什么

協(xié)議相同
域名相同
端口相同

同源策略可分為以下兩種情況:
1、DOM 同源策略:禁止對不同源頁面 DOM 進行操作。這里主要場景是 iframe 跨域的情況,不同域名的 iframe 是限制互相訪問的。
2、XMLHttpRequest 同源策略:禁止使用 XHR 對象向不同源的服務器地址發(fā)起 HTTP 請求。
同源策略的目的

因為存在瀏覽器同源策略,所以才會有跨域問題。那么瀏覽器是出于何種原因會有跨域的限制呢。其實不難想到,跨域限制主要的目的就是為了用戶的上網安全。

如果瀏覽器沒有同源策略,會存在什么樣的安全問題呢。下面從 DOM 同源策略和 XMLHttpRequest 同源策略來舉例說明:

  • 如果沒有 DOM 同源策略,也就是說不同域的 iframe 之間可以相互訪問,那么黑客可以這樣進行攻擊:
    1、做一個假網站,里面用 iframe 嵌套一個銀行網站 http://mybank.com
    2、把 iframe 寬高啥的調整到頁面全部,這樣用戶進來除了域名,別的部分和銀行的網站沒有任何差別。
    3、這時如果用戶輸入賬號密碼,我們的主網站可以跨域訪問到 http://mybank.com 的 dom 節(jié)點,就可以拿到用戶的賬戶密碼了。

  • 如果沒有 XMLHttpRequest 同源策略,那么黑客可以進行 CSRF(跨站請求偽造) 攻擊:
    1、用戶登錄了自己的銀行頁面 http://mybank.comhttp://mybank.com 向用戶的 cookie 中添加用戶標識。
    2、用戶瀏覽了惡意頁面 http://evil.com,執(zhí)行了頁面中的惡意 AJAX 請求代碼。
    3、http://evil.comhttp://mybank.com 發(fā)起 AJAX HTTP 請求,請求會默認把 http://mybank.com 對應 cookie 也同時發(fā)送過去。
    4、銀行頁面從發(fā)送的 cookie 中提取用戶標識,驗證用戶無誤,response 中返回請求數(shù)據(jù)。此時數(shù)據(jù)就泄露了。
    5、而且由于 Ajax 在后臺執(zhí)行,用戶無法感知這一過程。

2.跨域解決方法

一、請求跨域

CORS
JSONP
WebSocket
代理轉發(fā)

二、頁面跨域

postMessage
document.domain
window.name
location.hash

請求跨域解決方法

  • CORS
優(yōu)點:

1、CORS 通信與同源的 AJAX 通信沒有差別,代碼完全一樣,容易維護。
2、支持所有類型的 HTTP 請求。

缺點:

1、存在兼容性問題,特別是 IE10 以下的瀏覽器。
2、第一次發(fā)送非簡單請求時會多一次請求。

  • JSONP 實現(xiàn)跨域
    由于 script 標簽不受瀏覽器同源策略的影響,允許跨域引用資源。因此可以通過動態(tài)創(chuàng)建 script 標簽,然后利用 src 屬性進行跨域,這也就是 JSONP 跨域的基本原理。

直接通過下面的例子來說明 JSONP 實現(xiàn)跨域的流程:

// 1. 定義一個 回調函數(shù) handleResponse 用來接收返回的數(shù)據(jù)
function handleResponse(data) {
    console.log(data);
};

// 2. 動態(tài)創(chuàng)建一個 script 標簽,并且告訴后端回調函數(shù)名叫 handleResponse
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.src = 'http://www.laixiangran.cn/json?callback=handleResponse';
body.appendChild(script);

// 3. 通過 script.src 請求 `http://www.laixiangran.cn/json?callback=handleResponse`,
// 4. 后端能夠識別這樣的 URL 格式并處理該請求,然后返回 handleResponse({"name": "laixiangran"}) 給瀏覽器
// 5. 瀏覽器在接收到 handleResponse({"name": "laixiangran"}) 之后立即執(zhí)行 ,也就是執(zhí)行 handleResponse 方法,獲得后端返回的數(shù)據(jù),這樣就完成一次跨域請求了。
優(yōu)點

使用簡便,沒有兼容性問題,目前最流行的一種跨域方法。

缺點

只支持 GET 請求。
由于是從其它域中加載代碼執(zhí)行,因此如果其他域不安全,很可能會在響應中夾帶一些惡意代碼。
要確定 JSONP 請求是否失敗并不容易。雖然 HTML5 給 script 標簽新增了一個 onerror 事件處理程序,但是存在兼容性問題。

  • WebSocket跨域

Websocket 是 HTML5 規(guī)范提出的一個應用層的全雙工協(xié)議,適用于瀏覽器與服務器進行實時通信場景。

全雙工通信傳輸?shù)囊粋€術語,這里的“工”指的是通信方向。

“雙工”是指從客戶端到服務端,以及從服務端到客戶端兩個方向都可以通信,“全”指的是通信雙方可以同時向對方發(fā)送數(shù)據(jù)。與之相對應的還有半雙工和單工,半雙工指的是雙方可以互相向對方發(fā)送數(shù)據(jù),但雙方不能同時發(fā)送,單工則指的是數(shù)據(jù)只能從一方發(fā)送到另一方。

下面是一段簡單的示例代碼。在 a 網站直接創(chuàng)建一個 WebSocket 連接,連接到 b 網站即可,然后調用 WebScoket 實例 ws 的 send() 函數(shù)向服務端發(fā)送消息,監(jiān)聽實例 ws 的 onmessage 事件得到響應內容。

let ws = new WebSocket("ws://b.com");
ws.onopen = function(){
  // ws.send(...);
}
ws.onmessage = function(e){
  // console.log(e.data);
}
  • 請求代理

我們知道瀏覽器有同源策略的安全限制,但是服務器沒有限制,所以我們可以利用服務器進行請求轉發(fā)。

以 webpack 為例,利用 webpack-dev-server 配置代理, 當瀏覽器發(fā)起前綴為 /api 的請求時都會被轉發(fā)到 http://localhost:3000 服務器,代理服務器將獲取到響應返回給瀏覽器。對于瀏覽器而言還是請求當前網站,但實際上已經被服務端轉發(fā)。

// webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
};

// 使用 Nginx 作為代理服務器
location /api {
    proxy_pass   http://localhost:3000;
}

頁面跨域解決方法

請求跨域之外,頁面之間也會有跨域需求,例如使用 iframe 時父子頁面之間進行通信。常用方案如下:

postMessage
document.domain
window.name(不常用)
location.hash + iframe(不常用)

  • postMessage

window.postMessage(message,targetOrigin) 方法是 HTML5 新引進的特性,可以使用它來向其它的 window 對象發(fā)送消息,無論這個 window 對象是屬于同源或不同源。這個應該就是以后解決 dom 跨域通用方法了。
調用 postMessage 方法的 window 對象是指要接收消息的那一個 window 對象,該方法的第一個參數(shù) message 為要發(fā)送的消息,類型只能為字符串;第二個參數(shù) targetOrigin 用來限定接收消息的那個 window 對象所在的域,如果不想限定域,可以使用通配符 *。
需要接收消息的 window 對象,可是通過監(jiān)聽自身的 message 事件來獲取傳過來的消息,消息內容儲存在該事件對象的 data 屬性中。

頁面 http://www.laixiangran.cn/a.html 的代碼:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 1. iframe載入 "http://laixiangran.cn/b.html 頁面后會執(zhí)行該函數(shù)
    function test() {
        // 2. 獲取 http://laixiangran.cn/b.html 頁面的 window 對象,
        // 然后通過 postMessage 向 http://laixiangran.cn/b.html 頁面發(fā)送消息
        var iframe = document.getElementById('myIframe');
        var win = iframe.contentWindow;
        win.postMessage('我是來自 http://www.laixiangran.cn/a.html 頁面的消息', '*');
    }
</script>

頁面 http://laixiangran.cn/b.html 的代碼:

<script type="text/javascript">
    // 注冊 message 事件用來接收消息
    window.onmessage = function(e) {
        e = e || event; // 獲取事件對象
        console.log(e.data); // 通過 data 屬性得到發(fā)送來的消息
    }
</script>
  • document.domain跨域

對于主域名相同,而子域名不同的情況,可以使用 document.domain 來跨域。這種方式非常適用于 iframe 跨域的情況。

比如,有一個頁面,它的地址是 http://www.laixiangran.cn/a.html,在這個頁面里面有一個 iframe,它的 src 是 http://laixiangran.cn/b.html。很顯然,這個頁面與它里面的 iframe 框架是不同域的,所以我們是無法通過在頁面中書寫 js 代碼來獲取 iframe 中的東西的。

這個時候,document.domain 就可以派上用場了,我們只要把 http://www.laixiangran.cn/a.htmlhttp://laixiangran.cn/b.html這兩個頁面的 document.domain 都設成相同的域名就可以了。但要注意的是,document.domain 的設置是有限制的,我們只能把 document.domain 設置成自身或更高一級的父域,且主域必須相同。例如:a.b.laixiangran.cn 中某個文檔的 document.domain 可以設成 a.b.laixiangran.cn、b.laixiangran.cn 、laixiangran.cn 中的任意一個,但是不可以設成 c.a.b.laixiangran.cn ,因為這是當前域的子域,也不可以設成 baidu.com,因為主域已經不相同了。

例如,在頁面 http://www.laixiangran.cn/a.html 中設置document.domain:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()">
<script>
    document.domain = 'laixiangran.cn'; // 設置成主域
    function test() {
        console.log(document.getElementById('myIframe').contentWindow);
    }
</script>

在頁面 http://laixiangran.cn/b.html 中也設置 document.domain,而且這也是必須的,雖然這個文檔的 domain 就是 laixiangran.cn,但是還是必須顯式地設置 document.domain 的值:

<script>
    document.domain = 'laixiangran.cn'; // document.domain 設置成與主頁面相同
</script>

這樣,http://www.laixiangran.cn/a.html 就可以通過 js 訪問到 http://laixiangran.cn/b.html 中的各種屬性和對象了。

  • window.name跨域

window 對象有個 name 屬性,該屬性有個特征:即在一個窗口(window)的生命周期內,窗口載入的所有的頁面(不管是相同域的頁面還是不同域的頁面)都是共享一個 window.name 的,每個頁面對 window.name 都有讀寫的權限,window.name 是持久存在一個窗口載入過的所有頁面中的,并不會因新頁面的載入而進行重置。

通過下面的例子介紹如何通過 window.name 來跨域獲取數(shù)據(jù)的。

頁面 http://www.laixiangran.cn/a.html 的代碼:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 2. iframe載入 "http://laixiangran.cn/b.html 頁面后會執(zhí)行該函數(shù)
    function test() {
        var iframe = document.getElementById('myIframe');
        
        // 重置 iframe 的 onload 事件程序,
        // 此時經過后面代碼重置 src 之后,
        // http://www.laixiangran.cn/a.html 頁面與該 iframe 在同一個源了,可以相互訪問了
        iframe.onload = function() {
            var data = iframe.contentWindow.name; // 4. 獲取 iframe 里的 window.name
            console.log(data); // hello world!
        };
        
        // 3. 重置一個與 http://www.laixiangran.cn/a.html 頁面同源的頁面
        iframe.src = 'http://www.laixiangran.cn/c.html';
    }
</script>

頁面 http://laixiangran.cn/b.html 的代碼:

<script type="text/javascript">
    // 1. 給當前的 window.name 設置一個 http://www.laixiangran.cn/a.html 頁面想要得到的數(shù)據(jù)值 
    window.name = "hello world!";
</script>
  • location.hash跨域

location.hash 方式跨域,是子框架修改父框架 src 的 hash 值,通過這個屬性進行傳遞數(shù)據(jù),且更改 hash 值,頁面不會刷新。但是傳遞的數(shù)據(jù)的字節(jié)數(shù)是有限的。
頁面 http://www.laixiangran.cn/a.html 的代碼:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
  // 2. iframe載入 "http://laixiangran.cn/b.html 頁面后會執(zhí)行該函數(shù)
  function test() {
      // 3. 獲取通過 http://laixiangran.cn/b.html 頁面設置 hash 值
      var data = window.location.hash;
      console.log(data);
  }
</script>

頁面 http://laixiangran.cn/b.html 的代碼:

<script type="text/javascript">
    // 1. 設置父頁面的 hash 值
    parent.location.hash = "world";
</script>

內容安全策略(CSP)

內容安全策略(Content Security Policy,簡稱CSP)是一種以可信白名單作機制,來限制網站是否可以包含某些來源內容,緩解廣泛的內容注入漏洞,比如 XSS。 簡單來說,就是我們能夠規(guī)定,我們的網站只接受我們指定的請求資源。默認配置下不允許執(zhí)行內聯(lián)代碼(<script>塊內容,內聯(lián)事件,內聯(lián)樣式),以及禁止執(zhí)行eval() , newFunction() , setTimeout([string], …) 和setInterval([string], …) 。

  • CSP使用方式

CSP可以由兩種方式指定: HTTP Header 和 HTML。

通過定義在HTTP響應 header 中使用:

"Content-Security-Policy:" 策略集

通過定義在 HTML meta標簽中使用:

<meta http-equiv="content-security-policy" content="策略集">

策略是指定義 CSP 的語法內容。
如果 HTTP 頭 與 meta 標簽同時定義了 CSP,則會優(yōu)先采用 HTTP 頭的 。
定義后,凡是不符合 CSP策略的外部資源都會被阻止加載。

  • CSP語法

策略
每一條策略都是指令與指令值組成:

Content-Security-Policy:指令1 指令值1

策略與策略之間用分號隔開,例如

Content-Security-Policy:指令1 指令值1;指令2 指令值2;指令3 指令值3

在一條策略中,如果一個指令中有多個指令值,則指令值之間用空號隔開:

Content-Security-Policy:指令a 指令值a1 指令值a2
  • CSP指令
    default-src : 定義針對所有類型(js/image/css/font/ajax/iframe/多媒體等)資源的默認加載策略,如果某類型資源沒有單獨定義策略,就使用默認的。
    script-src : 定義針對 JavaScript 的加載策略。
    style-src : 定義針對樣式的加載策略。
    img-src : 定義針對圖片的加載策略。
    font-src : 定義針對字體的加載策略。
    media-src : 定義針對多媒體的加載策略,例如:音頻標簽和視頻標簽。
    object-src : 定義針對插件的加載策略,例如:、、。
    child-src :定義針對框架的加載策略,例如: ,。
    connect-src : 定義針對 Ajax/WebSocket 等請求的加載策略。不允許的情況下,瀏覽器會模擬一個狀態(tài)為400的響應。
    sandbox : 定義針對 sandbox 的限制,相當于 的sandbox屬性。
    report-uri : 告訴瀏覽器如果請求的資源不被策略允許時,往哪個地址提交日志信息。
    form-action : 定義針對提交的 form 到特定來源的加載策略。
    referrer : 定義針對 referrer 的加載策略。
    reflected-xss : 定義針對 XSS 過濾器使用策略。

  • CSP指令值
    *允許加載任何內容
    ‘none’ 不允許加載任何內容
    ‘self’ 允許加載相同源的內容
    www.a.com 允許加載指定域名的資源
    *.a.com 允許加載 a.com 任何子域名的資源
    https://a.com 允許加載 a.com 的 https 資源
    https: 允許加載 https 資源
    data: 允許加載 data: 協(xié)議,例如:base64編碼的圖片
    ‘unsafe-inline’ 允許加載 inline 資源,例如style屬性、onclick、inline js、inline css等
    ‘unsafe-eval’ 允許加載動態(tài) js 代碼,例如 eval()

  • CSP例子

eg1:
所有內容均來自網站的自己的域:

Content-Security-Policy:default-src 'self'

eg2:
所有內容都來自網站自己的域,還有其他子域(假如網站的地址是:a.com):

Content-Security-Policy:default-src 'self' *.a.com

eg3:
網站接受任意域的圖像,指定域(a.com)的音頻、視頻和多個指定域(a.com、b.com)的腳本

Content-Security-Policy:default-src 'self';img-src *;media-src a.com;script-src a.com b.com
  • CSP默認特性
    一、阻止內聯(lián)代碼執(zhí)行
    CSP除了使用白名單機制外,默認配置下阻止內聯(lián)代碼執(zhí)行是防止內容注入的最大安全保障。

雖然CSP中已經對script-src和style-src提供了使用”unsafe-inline”指令來開啟執(zhí)行內聯(lián)代碼,但為了安全起見還是慎用”unsafe-inline”。

二、eval相關功能被禁用
用戶輸入字符串,然后經過eval()等函數(shù)轉義進而被當作腳本去執(zhí)行。這樣的攻擊方式比較常見。于是乎CSP默認配置下,eval() , newFunction() , setTimeout([string], …) 和setInterval([string], …)都被禁止運行。
同樣CSP也提供了”unsafe-eval”去開啟執(zhí)行eval()等函數(shù),但強烈不建議去使用”unsafe-eval”這個指令。

CSP分析報告
可以用report-uri指令使瀏覽器發(fā)送HTTP POST請求把攻擊報告以JSON格式傳送到你指定的地址。接下來給大家介紹你的站點如何配置來接收攻擊報告。

啟用報告

默認情況下,違規(guī)報告不會發(fā)送。為了能使用違規(guī)報告,你必須使用report-uri指令,并至少提供一個接收地址。

Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi

如果想讓瀏覽器只匯報報告,不阻止任何內容,可以改用Content-Security-Policy-Report-Only頭。

違規(guī)報告語法
該報告JSON對象包含以下數(shù)據(jù):

blocked-uri:被阻止的違規(guī)資源
document-uri:攔截違規(guī)行為發(fā)生的頁面
original-policy:Content-Security-Policy頭策略的所有內容
referrer:頁面的referrer
status-code:HTTP響應狀態(tài)
violated-directive:違規(guī)的指令

違規(guī)報告例子
http://example.com/signup.html 中CSP 規(guī)定只能加載cdn.example.com的CSS樣式。

Content-Security-Policy: default-src 'none'; style-src cdn.example.com; report-uri /test/csp-report.php

signup.html中的代碼類似與這樣:

<!DOCTYPE html>
<html>
 <head>
   <title>Sign Up</title>
   <link rel="stylesheet" href="css/style.css">
 </head>
 <body>
   ... Content ...
 </body>
</html>

你能從上面的代碼找出錯誤嗎?策略是只允許加載cdn.example.com中的CSS樣式。但signup.html試圖加載自己域的style.css樣式。這樣違反了策略,瀏覽器會向 http://example.com/test/csp-report.php 發(fā)送POST請求提交報告,發(fā)送格式為JSON格式。

{
  "csp-report": {
    "document-uri": "http://example.com/signup.html",
    "referrer": "",
    "blocked-uri": "http://example.com/css/style.css",
    "violated-directive": "style-src cdn.example.com",
    "original-policy": "default-src 'none'; style-src cdn.example.com; report-uri /_/csp-reports",
  }
}

原文鏈接:https://blog.csdn.net/guo15890025019/article/details/123179250

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 前言 關于前端跨域的解決方法的多種多樣實在讓人目不暇接。以前碰到一個公司面試的場景是這樣的,好幾個人一起在等待面試...
    andreaxiang閱讀 533評論 1 4
  • 轉載:http://damonare.github.io/2016/10/30/%E5%89%8D%E7%AB%A...
    壯哉我大前端閱讀 585評論 2 9
  • 1. 什么是跨域? 跨域一詞從字面意思看,就是跨域名嘛,但實際上跨域的范圍絕對不止那么狹隘。具體概念如下:只要協(xié)議...
    開車去環(huán)游世界閱讀 615評論 0 0
  • 前言相信每一個前端er對于跨域這兩個字都不會陌生,在實際項目中應用也是比較多的。但跨域方法的多種多樣實在讓人目不暇...
    Www劉閱讀 737評論 2 23
  • 跨域,是一個前端開發(fā)者必須掌握的概念,這篇文章就從幾個基本問題出發(fā),來理解這個概念。 首先,什么是跨域?為何會有跨...
    阿追老師Jason閱讀 728評論 0 2

友情鏈接更多精彩內容