密碼安全與會話安全
對于登錄大家并不陌生,訪問系統(tǒng)幾乎都需要登錄。因?yàn)橄到y(tǒng)也不知道是誰在訪問,所以需要你告訴系統(tǒng)你是誰,還需要證明你真的是你,如何證明?給系統(tǒng)展示你的密碼,因?yàn)槊艽a只有你才擁有,你有這個密碼,你就能證明你真的是你,這就是一個登錄。
看似簡單的幾個步驟,但里面涉及的安全問題卻有很多。
密碼儲存安全
首先我們看關(guān)于密碼存儲安全的問題。系統(tǒng)服務(wù)器需要存儲用戶密碼,才能在用戶登錄時驗(yàn)證密碼的正確性,但存儲就會有泄露的風(fēng)險,比如數(shù)據(jù)庫被偷,服務(wù)器被入侵,內(nèi)部員工泄露數(shù)據(jù),被撞庫等風(fēng)險。因此我們需要認(rèn)真地考慮如何安全存儲用戶密碼。
我認(rèn)為作為一名軟件開發(fā)工程師, 嚴(yán)禁明文存儲密碼是common sense。那該如何解決不能明文存儲密碼的問題?也許看官你會說,md5 ?沒錯,md5 可以。那么md5屬于什么?它是一種單向散列算法,單向散列算法主要就是通過算法生成一個摘要,來解決密碼不能明文化問題。比如:
- md5(123456) = e10adc3949ba59abbe56e057f20f883e
這樣雖然能解決密碼不明文化,但是它還是會存在安全問題。因?yàn)楝F(xiàn)代計(jì)算機(jī)的計(jì)算能力實(shí)在太強(qiáng)了,一秒可以計(jì)算十億次 ,可以通過窮舉對比的方式破解密碼。這也就是所謂的彩虹表攻擊。
解決被彩虹表攻擊的問題對密碼也有一定的要求,比如要求密碼的復(fù)雜度,需要不同類型的字符進(jìn)行組合,在生成摘要時加點(diǎn)鹽來防止窮舉破解密碼。但這就安全了嗎?還不夠。一次算法遠(yuǎn)遠(yuǎn)不夠滿足安全要求,如md5(md5(md5(password+salt))),現(xiàn)在往往采用自適應(yīng)的方式來存儲密碼,可以設(shè)置重復(fù)計(jì)算一萬次,鹽使用隨機(jī)生成的16+位字符串。
這種方式雖然性能不會很好,但對于密碼生成摘要存儲來說,性能不好往往是好事,畢竟用戶注冊或修改密碼只是一次操作,用戶是可以接受的,但對于黑客來說,這是致命的,黑客從原來的一秒產(chǎn)生幾百萬甚至上千萬的md5值,變成了一秒只能產(chǎn)生一個,黑客想要破解一個密碼,從現(xiàn)代的計(jì)算機(jī)算力來看,需要上千年的時間。目前推薦的使用密碼存儲算法已不再推薦md5了,推薦采用Bcrypt Scrypt pdkdf2算法。
(很多可以通過MD5/SHA值進(jìn)行反向查詢,都是已經(jīng)存儲了大量的彩虹表)
密碼傳輸安全
解決了密碼存儲安全,再來看密碼傳輸安全。有人會說使用https就能解決網(wǎng)絡(luò)傳輸?shù)陌踩珕栴},但這還是不夠。瀏覽器到認(rèn)證服務(wù)器之間請求可能會經(jīng)過一層或多層gateway,如nginx,zuul,spring cloud gateway等。很多系統(tǒng)都是在gateway處安裝證書設(shè)置https協(xié)議,瀏覽器到gateway處是加密傳輸?shù)模玤ateway到認(rèn)證服務(wù)器還是http協(xié)議。
攻擊人可以在這條鏈路上竊取明文密碼,那全鏈路https不就可以解決問題了?還不夠,這些gateway內(nèi)部都可能會接觸到明文密碼,都有密碼泄露的風(fēng)險,有些gateway不是我們負(fù)責(zé)的,無法保證他人會不會開個后門拿出明文密碼,或者安全意識較低的程序員打印日志不小心把明文密碼打印出來。那如何解決這個問題?我們可以采用瀏覽器傳輸密碼之前就對密碼先加密的方法。
加密方式分為對稱加密與非對稱加密。
對稱加密:加密與解密用的是同一個密鑰。如DES,AES非對稱加密:加密與解密用的是不同的密鑰,一個叫公鑰,一個叫私鑰,公鑰加密的數(shù)據(jù)只能由對應(yīng)的私鑰才能解,如RSA。
如果采用對稱加密方式,需要瀏覽器在調(diào)登錄api之前,先獲取認(rèn)證服務(wù)器的密鑰,拿到密鑰后對密碼進(jìn)行加密傳輸,經(jīng)過的gateway都只能獲取密文,密碼到了認(rèn)證服務(wù)器,認(rèn)證服務(wù)器再通過密鑰對密文進(jìn)行解密,獲取到密碼明文,就可以進(jìn)行密碼驗(yàn)證。但有一個安全問題,瀏覽器獲取密鑰也會經(jīng)過gateway,如果gateway把密鑰也打印到了日志中,密文也打印到了日志中,那攻擊人同樣可以通過日志獲取明文密碼。
既然對稱加密不可取,我們來看看非對稱加密。瀏覽器登錄前經(jīng)過gateway獲取認(rèn)證服務(wù)器的公鑰,使用公鑰進(jìn)行加密,最終密文到認(rèn)證服務(wù)器,再通過私鑰解密拿到明文密碼進(jìn)行密碼認(rèn)證。這種方式gateway只能拿到公鑰和密文,無法解密,就算打印到日志中,攻擊人無法拿到明文密碼了。
但這樣就安全了嗎?
如果攻擊人拿gateway中的密文直接去調(diào)認(rèn)證服務(wù)器中的登錄api,認(rèn)證服務(wù)器一樣可以通過私鑰進(jìn)行解密,并登錄成功。所以我們還需要加一些限制:保證密文有過期時間,并且是只能使用一次。
瀏覽器獲取認(rèn)證服務(wù)器公鑰時,攜帶用戶名到認(rèn)證服務(wù)器,認(rèn)證服務(wù)器生成隨機(jī)數(shù)并與用戶名關(guān)聯(lián),隨機(jī)數(shù)只保存5分鐘,隨機(jī)數(shù)與公鑰一起返回給瀏覽器。瀏覽器使用隨機(jī)數(shù)加密碼通過公鑰一起加密調(diào)登錄api,認(rèn)證服務(wù)器通過私鑰解密,獲取到明文密碼與隨機(jī)數(shù),驗(yàn)證隨機(jī)數(shù)的有效性與合法性,都正常就進(jìn)行正常登錄,比較完隨機(jī)數(shù)后立刻刪除隨機(jī)數(shù),如不正常拒絕登錄。
攻擊人就算獲取到了密碼密文,公鑰,隨機(jī)數(shù),也只能在5分鐘之內(nèi)趕在gateway正常請求登錄之前,發(fā)起登錄攻擊,但這個難度太大。還有就是認(rèn)證服務(wù)器保證客戶端是gateway或可信的服務(wù)發(fā)起的請求,認(rèn)證服務(wù)器可以對客戶端做白名單限制,方式有很多種,在這就不一一贅述了。
但現(xiàn)在就安全了嗎?還真不一定。如果攻擊人攻破了gateway,在瀏覽器請求認(rèn)證服務(wù)器獲取公鑰時,gateway返回攻擊人頒發(fā)的公鑰,待用戶輸入完賬號密碼后,瀏覽器雖然進(jìn)行了加密,數(shù)據(jù)到了gateway,攻擊人再通過自己的私鑰進(jìn)行解密拿到明文密碼,再通過明文密碼在登錄頁進(jìn)行正常的登錄,同樣可以登錄成功。因此瀏覽器也需要做安全驗(yàn)證,驗(yàn)證公鑰的合法性。
認(rèn)證服務(wù)器可以采用CA機(jī)構(gòu)頒發(fā)的公鑰,認(rèn)證服務(wù)器與瀏覽器都相信CA機(jī)構(gòu)(做安全總得相信點(diǎn)東西,如果什么都不信任就沒法做安全了,有永無止境的安全問題),通過CA機(jī)構(gòu)的方式驗(yàn)證公鑰的合法性來避免中間人篡改公鑰的問題(講得不是很清楚,比如CA機(jī)構(gòu)是個啥,為什么CA機(jī)構(gòu)可信?這里面可聊的話題太多,有興趣可以查看《密碼學(xué)與網(wǎng)絡(luò)安全》等書籍或一起探討研究)。
那密碼安全了嗎?還是遠(yuǎn)遠(yuǎn)不夠。比如黑客知道了你密碼的長度,可以不斷地調(diào)登錄或修改密碼的接口來試錯,總會試出來正確的密碼,因此需要對任何會驗(yàn)證密碼合法性的接口都需要加頻率限制。如登錄連續(xù)錯了5次鎖5分鐘,再錯5次鎖半小時,防止黑客試出密碼。但這種方式也有問題。如競爭對手公司不斷地使用用戶的賬號和錯誤的密碼去登錄,導(dǎo)致用戶的賬號一直處于被鎖狀態(tài),正常用戶也沒法使用,這就違背了安全中的可用性。那就需要加ip限制和驗(yàn)證碼機(jī)制了。為了用戶的體驗(yàn)性,可以做成第一次登錄用戶可以正常登錄,錯誤之后,就需要使用驗(yàn)證碼的方式登錄,超過5次鎖定賬號,同一ip登錄錯誤次數(shù)過多,將ip加入黑名單中。
無密碼安全
密碼有很多安全問題,復(fù)雜密碼對于用戶來說也挺麻煩的,那采用無密碼技術(shù)。沒有密碼是不是就安全了呢?雖然現(xiàn)在可以采用指紋登錄與刷臉登錄,但新的安全問題也隨之而來。密碼是需要私密性的,但指紋可以從照片中獲取,美國國防部某個官員因在拍照時露出了大拇指,隨后就有了這個大拇指的清晰指紋圖(拍照的時候不要剪刀手或點(diǎn)贊了,最好指紋對準(zhǔn)自己吧,手動狗頭)。
還有就是存在不確定性,刷臉登錄時,如果燈光太暗或太亮,臉部受傷了,化妝了,那登錄能保證成功嗎?臉部相似的人,登錄時能保證區(qū)分開來嗎?如果不能就違背了賬號唯一性,日后審計(jì)也是個問題。還有一個問題就是不可修改。當(dāng)密碼泄露了可以修改密碼,但你的指紋已經(jīng)作為登錄憑據(jù)了,換個手指頭就好了,當(dāng)十個手指頭都用過了,那是不是該用腳指頭了?當(dāng)然無密碼肯定是比有密碼使用上更方便快捷,隨著技術(shù)的發(fā)展,這些問題也都會解決,只是也會有更多的安全問題。
我們再來看會話安全(密碼安全還有各種各樣的問題,篇幅有限,不再聊了)。
會話標(biāo)識儲存安全
登錄完成后,用戶不可能每一次操作都需要輸入密碼。因此系統(tǒng)需要記錄用戶的登錄狀態(tài),又稱會話狀態(tài)。常見的做法是系統(tǒng)保存session。session存入用戶信息,生成隨機(jī)數(shù)sessionId,將sessionId返回瀏覽器,并存入瀏覽器的cookie中,下一次用戶訪問系統(tǒng),攜帶cookie,系統(tǒng)通過cookie找到session,就可以知道用戶是誰
對于集群服務(wù),用戶首次登陸,訪問的A服務(wù)器,A服務(wù)器存入session,下次訪問到了B服務(wù)器,B服務(wù)沒有session,認(rèn)為用戶沒有登陸,提示用戶需登陸,這是一個bug。我們將每臺服務(wù)器都識別到有session就可以解決這個問題了。session存入redis,登陸時往redis存session,之后都從redis取session。或者每臺服務(wù)器都有session,每臺服務(wù)器的session同步也能解決這個問題。
不管采用哪種方式,都有一個安全風(fēng)險,sessionId給出去了,不論sessionId是隨機(jī)數(shù)生成還是加密算出來的字符串,黑客并不關(guān)心,黑客只關(guān)心這個字符串代表了用戶的會話狀態(tài)。黑客也不需要拿到密碼只需要拿到這個字符串,就可以模擬用戶進(jìn)行詐騙,轉(zhuǎn)賬,發(fā)表非法政治評論等非法活動。
保護(hù)sessionId不被非法利用與保護(hù)密碼同等重要。大多數(shù)情況下sessionId存儲在cookie中,我們先了解cookie。
這是登錄okta后生成的其中一個cookie,有name,value,domain,path,Expires/Max-Age,Httponly,Secure等屬性,這里重點(diǎn)介紹其中幾個。
- Domain:cookie對于哪個域有效。這個cookie的域是thoughtworks.okta.com,則只有訪問thoughtworks.okta.com下的api,瀏覽器才會將該cookie發(fā)送至后端服務(wù)器。這個值可以包含子域,如設(shè)置domain為okta.com時,訪問thoughtworks.okta.com也會帶上該cookie。
- HttpOnly:當(dāng)值為true時,告訴瀏覽器不能通過js訪問到該cookie,只有在發(fā)送請求到后端時,才會攜帶該cookie。
- Secure:當(dāng)值為true時,告訴瀏覽器,只有訪問協(xié)議問https的api時,才會攜帶該cookie。
- Expires/Max-Age:cookie有兩種,本地cookie與session cookie。如果設(shè)置了cookie的過期時間則為本地cookie,不設(shè)置為session cookie。session cookie的特點(diǎn)是沒有具體的過期時間,隨著瀏覽器關(guān)閉而清除。本地cookie即使瀏覽器關(guān)閉也不會清除,而是到了時間自動清除。這也是為什么關(guān)閉瀏覽器后再次打開瀏覽器有些系統(tǒng)需要重新登錄,而有些不需要的原因。
知道cookie的幾個特性后我們再來看看攻擊人常用的幾種攻擊方式:XSS攻擊,CSRF攻擊。
會話標(biāo)識傳輸安全
(1) XSS攻擊叫做跨站腳本攻擊,指用戶的輸入拼接了正常的html+js+css,變成了帶有攻擊性的html+js+css。瀏覽器可能無法識別具有攻擊性的html+js+css,按照正常的邏輯執(zhí)行代碼,這可能會導(dǎo)致攻擊人偷走cookie(XSS還有其他的危害,但這里僅討論與會話標(biāo)識相關(guān))。如果黑客在html中插入隱藏的form表單,通過document.cookie()獲取到瀏覽器中cookie,作為參數(shù)并自動發(fā)送post請求到攻擊人的后端api中,攻擊人就可以拿到用戶的cookie,也就可以拿到sessionId了。這種方式可以通過設(shè)置cookie的HttpOnly為true來防止js獲取cookie值。從而避免通過XSS攻擊獲取sessionId。
(2) CSRF攻擊叫做跨站請求偽造。XSS攻擊是指本網(wǎng)站的代碼執(zhí)行攻擊腳本造成了對本網(wǎng)站的影響。CSRF攻擊則是用戶打開了其他網(wǎng)站,瀏覽器執(zhí)行了其他網(wǎng)站的攻擊腳本,卻對本網(wǎng)站造成了傷害。舉個例子,當(dāng)我在瀏覽器中登錄了某銀行的網(wǎng)站,進(jìn)行了轉(zhuǎn)賬操作,瀏覽器調(diào)用了
https://www.xxx.com/transfer?toBankId=123456&money=100,我的賬戶少了100塊,收到短信扣了100塊。這時來了一封郵件,標(biāo)題為你想得到力量嗎?內(nèi)容是一個鏈接,我點(diǎn)擊這個鏈接,看到url是www.yyy.com/index.htm,立馬又收到一個短信,我賬號又少了1000塊,我刷新下頁面,又少1000塊。打開頁面查看源碼,發(fā)現(xiàn)有個隱藏的標(biāo)簽,
src=https://www.xxx.com/transfer?toBankId=123456&money=1000。也就意味著每次刷新頁面,瀏覽器都會執(zhí)行一次
https://www.xxx.com/transfer?toBankId=123456&money=1000 GET請求。大多數(shù)瀏覽器有同源策略(協(xié)議\主機(jī)\端口組成源),其中一個限制是同源的網(wǎng)頁才會共享cookie。但瀏覽器對html標(biāo)簽有白名單,img就是其中之一,通過img標(biāo)簽的src就可以發(fā)送get 請求,因訪問的是xxx(銀行)的域名,攜帶了cookie,銀行認(rèn)為是合法請求,轉(zhuǎn)賬成功。因img是get請求,那把轉(zhuǎn)賬等高危操作改成post接口不就可以了? 也不行,因?yàn)閒orm表單的post請求也在白名單中。
CSRF攻擊之所以成功,是因?yàn)楣羧丝梢酝耆珎卧煊脩舻恼埱?,那讓攻擊人無法偽造就可以解決這個問題了。在轉(zhuǎn)賬時,要求用戶再次輸入密碼或輸入驗(yàn)證碼,就可以解決CSRF攻擊。轉(zhuǎn)賬操作可以這么做,發(fā)表評論這類的操作,每次都要求用戶輸入密碼或驗(yàn)證碼用戶體驗(yàn)就很很差了。
(3) 還有Referer check,瀏覽器發(fā)送請求時,攜帶Referer header,值為網(wǎng)站url中的域名,異常轉(zhuǎn)賬時,雖然調(diào)用的www.xxx.com的api,但referer 值為www.yyy.com。在服務(wù)端只要驗(yàn)證Referer值就可以判斷這是不是一個CSRF攻擊。這種方式也有問題,就是完全相信了第三方(瀏覽器)。對于低版本的瀏覽器已經(jīng)有辦法可以篡改Referer值,高版本的瀏覽器目前無法篡改,如用戶使用低版本的瀏覽器,Referer check將無法保證安全性。
那還有什么辦法可以解決CSRF攻擊的問題? 我們來看下okta是如何做到解決這個問題的。我們登陸okta成功后,打開網(wǎng)頁源代碼查看html,搜索token可以看到
在span中保存了一個token值 我們再創(chuàng)建一個tab頁
打開瀏覽器的f12,查看網(wǎng)絡(luò)請求,可以看到request header中有x-okta-xcrftoken這個header。
這就是為了解決CSRF攻擊的方式:CSRF Token方式。
csrf token工作原理就是在用戶登錄成功后,服務(wù)端生成token并保存一段時間,返回給瀏覽器,瀏覽器保存在html標(biāo)簽中。當(dāng)用戶操作訪問后端api時,將該token放入request header中。后端驗(yàn)證該token 的合法性即可判斷是否是CSRF攻擊。這種方式能生效的重點(diǎn)在于攻擊人無法拿到目標(biāo)網(wǎng)站的html。
最近在思考一個問題,就是如果黑客同時發(fā)起XSS攻擊和CSRF攻擊,這種方式是不是也失效了?黑客通過XSS攻擊,獲取到了CSRF token,攻擊人立馬發(fā)送釣魚郵件給目標(biāo)用戶,目標(biāo)用戶點(diǎn)擊了鏈接,網(wǎng)站打開時,先從黑客處獲取CSRF Token,并攜帶CSRF Token發(fā)起了CSRF攻擊,還有個前提是瀏覽器版本太低沒有Referer,那不就可以攻擊成功了?(我杞人憂天了嗎?) cookie+session有這么多安全需要考慮,那不要cookie+session不就沒這么多問題嗎?現(xiàn)在流行的jwt就可以做到無session的登錄認(rèn)證,但jwt也有各種各樣的安全問題。
【本文是51CTO專欄作者“ThoughtWorks”的原創(chuàng)稿件,微信公眾號:思特沃克,轉(zhuǎn)載請聯(lián)系原作者】