前端網絡安全必修 1 同源策略和CSRF

原文連接:ssshooter.com/2019-11-08-…javascript

本文主要涉及三個關鍵詞:php

  • 同源策略(Same-origin policy,簡稱 SOP)
  • 跨站請求僞造(Cross-site request forgery,簡稱 CSRF)
  • 跨域資源共享(Cross-Origin Resource Sharing,簡稱 CORS)

同源策略 SOP

同源

先解釋何爲同源:協議、域名、端口都同樣,就是同源。css

url 同源
niconico.com 基準
niconico.com/spirit o
sub.niconico.com/spirit x
niconico.com/spirit x
niconico.com:8080/spirit x

限制

你之因此會遇到 跨域問題,正是由於 SOP 的各類限制。可是具體來講限制了什麼呢?html

若是你說 SOP 就是「限制非同源資源的獲取」,這不對,最簡單的例子是引用圖片、css、js 文件等資源的時候就容許跨域。前端

若是你說 SOP 就是「禁止跨域請求」,這也不對,本質上 SOP 並非禁止跨域請求,而是在請求後攔截了請求的迴應。這就就會引發後面說到的 CSRFjava

其實 SOP 不是單一的定義,而是在不一樣狀況下有不一樣的解釋:node

  • 限制 cookies、DOM 和 Javascript 的命名區域
  • 限制 iframe、圖片等各類資源的內容操做
  • 限制 ajax 請求,準確來講是限制操做 ajax 響應結果,本質上跟上一條是同樣的

下面是 3 個在實際應用中會遇到的例子:ios

  • 使用 ajax 請求其餘跨域 API,最多見的狀況,前端新手噩夢
  • iframe 與父頁面交流,出現率比較低,並且解決方法也好懂
  • 對跨域圖片(例如來源於 <img> )進行操做,在 canvas 操做圖片的時候會遇到這個問題

若是沒有了 SOP:ajax

  • 一個瀏覽器打開幾個 tab,數據就泄露了
  • 你用 iframe 打開一個銀行網站,你能夠肆意讀取網站的內容,就能獲取用戶輸入的內容
  • 更加肆意地進行 CSRF

繞過跨域

SOP 帶來安全,同時也會帶來必定程度的麻煩,由於有時候就是有跨域的需求。繞過跨域的方案因爲篇幅所限,而且網上也不少相關文章,因此不在這裏展開解決跨域的方案,只給出幾個關鍵詞:json

對於 ajax

  • 使用 JSONP
  • 後端進行 CORS 配置
  • 後端反向代理

對於 iframe

  • 使用 location.hash 或 window.name 進行信息交流
  • 使用 postMessage

跨站請求僞造 CSRF

簡述

CSRF(Cross-site request forgery)跨站請求僞造,是一種常見的攻擊方式。是指 A 網站正常登錄後,cookie 正常保存,其餘網站 B 經過某種方式調用 A 網站接口進行操做,A 的接口在請求時會自動帶上 cookie。

上面說了,SOP 能夠經過 html tag 加載資源,並且 SOP 不阻止接口請求而是攔截請求結果,CSRF 偏偏佔了這兩個便宜。

因此 SOP 不能做爲防範 CSRF 的方法

對於 GET 請求,直接放到<img>就能神不知鬼不覺地請求跨域接口。

對於 POST 請求,不少例子都使用 form 提交:

<form action="<nowiki>http://bank.com/transfer.do</nowiki>" method="POST">
  <input type="hidden" name="acct" value="MARIA" />
  <input type="hidden" name="amount" value="100000" />
  <input type="submit" value="View my pictures" />
</form>
複製代碼

歸根到底,這兩個方法不報跨域是由於請求由 html 控制,你沒法用 js 直接操做得到的結果。

SOP 與 ajax

對於 ajax 請求,在得到數據以後你能肆意進行 js 操做。這時候雖然同源策略會阻止響應,但依然會發出請求。由於執行響應攔截的是瀏覽器而不是後端程序。事實上你的請求已經發到服務器並返回告終果,可是迫於安全策略,瀏覽器不容許你繼續進行 js 操做,因此報出你熟悉的 blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

因此再強調一次,同源策略不能做爲防範 CSRF 的方法

不過能夠防範 CSRF 的例外仍是有的,瀏覽器並非讓全部請求都發送成功,上述狀況僅限於簡單請求,相關知識會在下面 CORS 一節詳細解釋。

CSRF 對策

SOP 被 CSRF 佔了便宜,那真的是一無可取嗎?

不是!是否記得 SOP 限制了 cookie 的命名區域,雖然請求會自動帶上 cookies,可是攻擊者不管如何仍是沒法獲取 cookie 的內容自己。

因此應對 CSRF 有這樣的思路:同時把一個 token 寫到 cookie 裏,在發起請求時再經過 query、body 或者 header 帶上這個 token。請求到達服務器,覈對這個 token,若是正確,那必定是能看到 cookie 的本域發送的請求,CSRF 則作不到這一點。(這個方法用於先後端分離,後端渲染則能夠直接寫入到 dom 中)

示例代碼以下:

var csrftoken = Cookies.get('csrfToken')

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method)
}
$.ajaxSetup({
  beforeSend: function(xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader('x-csrf-token', csrftoken)
    }
  },
})
複製代碼

跨域資源共享 CORS

跨域是瀏覽器限制,可是若是服務器設置了 CORS 相關配置,在返回服務器的信息頭部會加上 Access-Control-Allow-Origin,瀏覽器看到這個字段的值與當前的源匹配,就會解鎖跨域限制。

HTTP/1.1 200 OK
Date: Sun, 24 Apr 2016 12:43:39 GMT
Server: Apache
Access-Control-Allow-Origin: http://www.acceptmeplease.com
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: application/xml
Content-Length: 423
複製代碼

對於 CORS,請求分兩種。

簡單請求

  • 請求方法使用 GET、POST 或 HEAD
  • Content-Type 設爲 application/x-www-form-urlencoded、multipart/form-data 或 text/plain

符合上面兩個條件的都爲 CORS 簡單請求。簡單請求都會直接發到服務器,會形成 CSRF。

預檢請求

不符合簡單請求要求的請求都須要先發送預檢請求(Preflight Request)。瀏覽器會在真正請求前發送 OPTION 方法的請求向服務器詢問當前源是否符合 CORS 目標,驗證經過後纔會發送正式請求。

例如使用 application/json 傳參的 POST 請求就是非簡單請求,會在預檢中被攔截。

再例如使用 PUT 方法請求,也會發送預檢請求。

上面提到的能夠防範 CSRF 的例外,就是指預檢請求。即便跨域成功請求預檢,但真正請求並不能發出去,這就保證了 CSRF 沒法成功。

CORS 與 cookie

與同域不一樣,用於跨域的 CORS 請求默認不發送 Cookie 和 HTTP 認證信息,先後端都要在配置中設定請求時帶上 cookie。

這就是爲何在進行 CORS 請求時 axios 須要設置 withCredentials: true

下面是 node.js 的後臺 koa 框架的 CORS 設置:

/**
 * CORS middleware
 *
 * @param {Object} [options]
 *  - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
 *  - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH'
 *  - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
 *  - {String|Array} allowHeaders `Access-Control-Allow-Headers`
 *  - {String|Number} maxAge `Access-Control-Max-Age` in seconds
 *  - {Boolean} credentials `Access-Control-Allow-Credentials`
 *  - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown
 * @return {Function} cors middleware
 * @api public
 */
複製代碼

順帶一提,Access-Control-Allow-Credentials設爲true時,Access-Control-Allow-Origin 強制不能設爲 *,爲了安全,也是挺麻煩啊...

開坑預告

暫時先說到這,如有疑問請在評論區提出,之後應該會講講 XSS、CSP 和 http/https 相關的主題。

參考

重點推薦 Whitepaper: The Definitive Guide to Same-origin Policy

egg.js 網絡安全

CSRF owasp

跨域資源共享 CORS 詳解

瀏覽器同源政策及其規避方法

相關文章
相關標籤/搜索