[譯]構建現代Web應用的安全指南

dmc3 9年前發布 | 33K 次閱讀 安全相關 web

原文:Security for building modern web apps
譯者:杰微刊—張迪


這篇文章的靈感來自于另一篇文章,它是關于“在今天,構建Web應用之前要知道的事情”的。并不長,但遺漏了一些關于安全性的建議,所以我就此動筆,分享一些這方面的知識。


本文重點是寫給那些來自初創公司,并且想要從頭開始開發一個Web應用的開發者,他們并不知道太多信息安全的知識,也不想花太多時間考慮其應用程序的安全性。一些重要的內容就不在這里討論了,諸如威脅建模(threat modeling),持續交付安全(continuous delivery security)等等。這篇文章的目標不是要取代現有的代碼安全檢查表(例如,OWASP,SANS),而是要從今天的視角補充一下它們。畢竟,安全概念是很老的,(例如,安全設計原則是在70年代被定義的),它會在今天乃至未來都繼續存在,所以安全也需要與時俱進,適應現實。


注:雖然像這類的文章都是有益的,但是安全是一個過程,必須從一開始就與開發過程緊密關連。要始終考慮找一個應用安全專家來幫助你。


客戶端 Client


輸出過濾(Output filtering):著名的跨站點腳本(Cross-Site Scripting),也被稱為“XSS”或“HTML注入”,在沒有輸出過濾和執行某些代碼時就會出現問題。防御方法依賴于上下文,如: HTML標簽屬性上的動態值(onclick、onload等),或標簽內部(如,$("p:first").innerHTML=dangerousVariable)。只有在把動態變量存儲在HTML標簽的屬性中時,這種危險代碼才會生效。過濾輸入對安全會有幫助,但是記住,XSS取決于上下文,所以不是所有的過濾都是有效的。這里有我對XSS的詳細解釋(PT-BR)。


使用靜態頁面(Use Static Pages):單頁應用程序(SPA)的優點除了由于ajax請求而減少的通信阻塞外,還有就是它擁有一個靜態前端。這就意味著有更少的攻擊面和更低的成本,因此你可以在 Amazon S3上存儲你的所有內容,并讓Amazon保證其安全,在你沒有一個安全技術團隊或者你的安全技術團隊不如Amazon擅長這個領域的情況下,讓 Amazon提供安全保證是非常棒的。SPA的缺點是缺乏自定義HTTPS的證書支持(custom https certificate support)。你需要轉移到Amazon CloudFront(CDN)上,這很容易實現,它將提升你的web應用的可用性。缺點是需要處理文件夾失效(assets invalidations),但不會太多。他們用一些使用文件名的版本管理技術,雖然很糟糕,但有總比沒有好。


避開第三方網站的JSONP反應指令和多種JS文件(甚至廣告網絡):如果允許第三方網站在你的網站注入JavaScript代碼,并且毫無保留地信任它們,結果就是加大了你的網站的被攻擊可能性。小心那些響應數據的API,不要讓他們輕易被執行。看看Troy Hunt的案例吧。


不要留下HTML注釋:有的安全工具可以用于搜索HTML注釋,并呈現給攻擊者,以查看是否有任何用處,例如OWASP WebScarab。刪除HTML注釋。如果你需要注釋,就在頁面生成的時候使用動態語言來添加注釋,這些注釋就不會出現在響應中了。


客戶端校驗(服務器端當然也要執行):服務器端校驗不能被替代,有兩個優點:1)更好的用戶體驗,因為反饋迅速;2)阻止了后臺的無用請求,從而提高有效性。


退出(logout)應在每一個頁面都是可見的:請不要忘記這一點。最好是在預期的地方,如點擊用戶的頭像之后的右上角。


如果你將數據存儲在客戶端,一定要謹慎:相同的威脅適用于移動端,也適用于其他客戶存儲的設備,會造成數據丟失和被盜。如果你將數據存儲在客戶端了,就要假設有人會看到它,所以不要存儲重要信息。存儲就要加密,并把key保存在cookie里(沒有可被JavaScript讀取到的HTTPOnly標記),至少保存到當前會話結束。當用戶注銷的時候要刪除所有信息。根據數據,你可能想要使用例如HMAC的技術來防止完整性違規(integrity violations)。無論如何,記得這樣使用它。當然,服務器中也要保存key。當用于session存儲機制時,Rails的cookie會和服務器的APP SECRET一起使用。為了加強這個概念,可以使用Json Web Tokens(JWT),它是目前做這件事(data + signature + algorithm used + expiration + base64 encoding + json format)的標準。


考慮用Json Web Tokens(JWT)取代session:你可以使依賴于JWT的無狀態服務器,而不是session和數據庫。缺點是保密性差,看上一條就知道了。這個方法可以提高應用的有效性,如果把它們存儲在 LocalStorage而不是cookie中,還可以防止CSRF攻擊。CSRF發生時瀏覽器無反應(dumbness),即使是跨域請求,cookie也有被傳輸到服務器的風險。


切記,LocalStorage會受到XSS影響,HttpOnly標識的Cookies則不會:雖然這有利于存儲session標識符(cookie w/ HttpOnly標識),但仍有CSRF的風險。這是一個權衡,記住這一條即可。


服務器端 Server


選擇一個web框架,至少是MVC:遠離構建web應用程序的腳本。最常用的框架已經給了你一些保護(例如,CSRF保護,Security頭),如果你正在寫PHP,直接使用它們就好了。但是,要小心,你可能會在下面一條跌倒:


避免太過異想天開:我認為這是開發人員中最常見的缺陷。他們對某些有用的功能或框架十分滿意,并且盲目地相信它們。這為許多安全漏洞和bug的產生留下了空間。最常見的例子是 OAuth庫。使用SSO前,一定要了解它的工作細節。否則你會身份驗證失敗。在開發過程中也沒有免費的午餐。在開發之前,在你的應用程序里插入一些未知代碼,做一些code review,靜態分析,檢查已知bug(CVE),并在可能的情況下閱讀一下RFC,但是不要盲目地去做,尤其是在web應用程序的關鍵部分,如身份驗證、授權、責任和支付處理/儲值卡。


驗證CORS源(CORS Origin):除非你打算向整個世界開放API,你應該只允許單頁應用的源地址被調用,以避免其他網站的瀏覽器內(in-browser)調用。


默認設置Cookie標識HTTPOnly:HTTPOnly 標識有更多的Cookies是必須的,這能防止Javascript訪問cookie值(如會話cookie),這樣做能保護Cookies中的信息,即使發生XSS。實際上,恕我直言,HTTPOnly應該是默認屬性才對,non-httponly只有在異常中才使用。沒有這個標識的cookie僅能用于客戶端訪問,例如一個根據用戶偏好顯示或隱藏菜單的標識符。LocalStorage對它的支持也很好,所以我們應該不再使用沒有HTTPOnly的 Cookie。


默認為Cookie設置secure標示:secure 標示允許cookie只能通過HTTPS連接傳輸,這是偉大的,但你需要有一個HTTPS端口監聽工具。如今,它應該是一個必備設置,不僅為了安全,而且為了增加你的谷歌搜索查詢排名。據我所知,你不可以在Amazon S3上使用自定義證書。你需要將你的自定義證書部署到Amazon CloudFront(CDN)上,這對你的密鑰來說是有害的,但對于小團隊來說別無選擇。CloudFlare想到了這一點,開發出了無需key的 SSL,但你需要建立一個能處理所有SSL握手的服務器,至少是使用這個鑰匙的一部分標頭,這也意味著需要更多的服務器和更高的成本。


避免業務邏輯Bypass:最常見的缺陷之一就是授權bypass,甚至在非死book上你可以看到這種事情發生。例如,編輯用戶帳戶的細節時,你能確保如果用戶輸入嵌入了另一個用戶的user_id時,你的應用能夠阻止這次更新么?你需要在所有的控制器(controller)上仔細確認。這通常是一些開發人員必須自己實現的驗證,所以通常被忽略,或實現得很難看。你自己測試一下,也邀請一個有做安全程序背景的人來測試一下,甚至做一些單元測試來驗證你的controller。質量分配漏洞(Mass Assignment Vulnerability)也值得注意,homakov利用它攻擊過GitHub。你需要將你的模型參數列入白名單,否則攻擊者會通過猜測他們的名字,利用“framework magic”,通過請求參數構建出模型對象。


在你的API中放置CSRF保護: Web框架通常建議你使用CSRF保護,當你構建API時,看到“請求中缺少CSRF token”的消息時,你一般會禁用它之后繼續編碼。不要那么做。CSRF真的很危險,提醒你自己,確保添加一個CSRF token,即使是在API被調用時。你可以通過以下3種方式做到這一點:


① 有狀態session:在每一個session上添加CSRF隨機token,檢查每一個請求中它們是否匹配。


② 無狀態的雙Cookie提交技術:攻擊者可以操縱請求體(request body),但不能操縱cookies,因為它們來自另一個域,在cookie和請求中向服務器發送相同的隨機值,并檢查它們是否匹配;如果你的用戶(或第三方腳本,如廣告)可以控制任何子域,你也有一些技術可以bypass。從Blackhat的文章中得到更多的信息。


③ 無狀態的Json Web Token:存儲在LocalStorage中,并在每個請求中發送。攻擊者不能訪問跨域的LocalStorage。


不要讓所有操作都獲得訪問你AWS帳戶全部資源的權限:你不會浪費太多時間為你應用的AWS訪問憑證找出正確的許可。不要傻到允許訪問所有東西。如果你將key上傳到一個公共的GitHub庫,你就完蛋了,會被攻擊,設置權限降低風險吧。


不要將證書存儲在源代碼里:從源代碼部署以外的環境或文件中去讀取證書。剛開始會有些麻煩,但一些函數庫使它非常容易,如ruby的dotenv gem


當進行服務端到服務端的通信時,驗證端點證書(endpoint),考慮pin它或它的公鑰:當你瀏覽一些HTTPS網站,瀏覽器會驗證其信任的CA。但當你進行從服務端到服務端的通信時,誰來做驗證呢?通常沒人,所以你需要自己設置邏輯去驗證端點證書。驗證通過之前,不要允許別的操作,否則SSL/TLS就沒意義了。除了在傳輸過程中加密數據,HTTPS的另一個目標是驗證端點的真偽,從而防止中間人攻擊。可以考慮使用證書pinning(Certificate Pinning),或者更好的公鑰pinning(Public Key pinning)。OWASP有一篇很好的文章詳細解釋了這一點,所以我不贅述了。最基本的是你只能和你所期待的人交談,例如,從給定的X509證書中生成一個摘要(digest),并把它與硬編碼摘要(hard coded digest)作比較。但是有一個問題,如果證書撤銷或者改變,服務將會被拒絕。更好的選擇是使用公鑰鎖定,因為公鑰存在于X509證書中,除非證書使用其他密鑰對重新生成,否則無論是被撤銷還是改變,都可以順利的通過公鑰被驗證。這些對移動應用程序也是必須的。


設置安全頭(Security Headers):通過在響應中設置安全頭,即可保護web應用免遭點擊劫持(Clickjacking)、反射型XSS(Reflected XSS)和 IE內容探測(IE content guessing)的攻擊(注:如果你發送配置正確,Ruby on Rails能為你做大部分的工作)。更多細節,請查看OWASP頁面。

① X-FRAME-OPTIONS:用“否認”或“同源”來防止“點擊劫持”。


② X-XSS-Protection:“1;mode=block”迫使XSS反射保護,在Chrome中是默認的, IE中不支持。


③ X-Content-Type-Options:“nosniff”遺憾的是,IE試圖猜測web頁面的內容,即使這個content/type意味著其他內容類型。如果IE檢測HTML代碼,它將允許txt文件執行腳本。通過使用這個標頭禁用它。


④ Strict- Transport-Security:“max-age=16070400;includeSubDomains”HTTP Strict-Transport-Security(HSTS)保證安全(HTTP通過SSL/TLS)的連接服務器。即使是用戶類型的 HTTP(user types http),瀏覽器都將強制HTTPS,這是很棒的。


⑤ 還有其他的,例如Content Security Policy(CSP),就不在這里討論了。


在“注冊”和“忘記密碼”頁面使用驗證碼:多虧了谷歌的reCaptcha,如今的驗證碼已經不是很煩人了。今天,你可以驗證用戶是否是基于他的行為而不僅僅是人類挑戰,從而防止假賬戶和瘋狂的發送電子郵件。


存儲API密鑰就像你存儲密碼一樣(或盡可能這么做):如果雙方泄漏的影響是相同的,那么為什么儲存一個比另一個更安全?實際上是有一些不同之處的,但關鍵是不要在明文中存儲API密鑰。API密鑰應該是系統生成的隨機字符,所以他們不會受到字典攻擊(dictionary attack),就像密碼,但是,在數據庫/文件系統/ OS中,API密鑰將在未經加密的文字或數據中可用。也就是說,至少一些hash是必要的。如果你使用像scrypt或BCrypt這樣的工具,你就要小心了。scrypt或BCrypt因為其緩慢的哈希計算,非常建議用于密碼。緩慢的哈希計算也會導致服務被拒絕。你輸入一次密碼,得到一個session ID;但是API就不同了,API驗證時刻都要被調用,所以速度緩慢會降低應用的可用性。存儲API Key的摘要,足夠滿足你的使用了SHA256或SHA512算法的應用了。遠離MD5和SHA1。一定要遠離!


至少為用戶使用UUID作為主鍵,而不是順序ID:防止用戶帳戶的猜測/暴力破解和輕易復制。有更多的優勢和少數劣勢,但它是值得的。注意:相較于連續整數,UUID并不會使應用更安全,僅是從安全的維度增加了不可法猜測性和模糊度。


忘記密碼和電子郵件確認的token:為忘記密碼或電子郵件確認生成一個token時,請確保使用安全的偽隨機數生成器(RPNG),否則可能被猜到。使用可以信任的庫/語言API。也為這個token設置截止日期/時間。設想一下使用情景,用戶不想改變自己的密碼,但一周后,有人攔截了電子郵件,訪問了那個URL,并改變他的密碼。這是不必要的風險。


在郵箱更新時通知舊郵箱:賬戶侵權之后最常見的行為是改變帳戶的電子郵箱,來防止其所有者恢復密碼和登錄,所以一定要發送一封電子郵件到過去的電子郵箱,在恢復過程添加一個選項。非死book就是這樣做的。這招還適用于敏感數據更新。無論是誰在操作,但賬戶所有者必須被通知。


禁用端口80而不是重定向到443:這樣做之后會增大攻擊面。如果80端口不需要了,那就禁用它。記住,你的API只應該在443中監聽。如果你想從80重定向到443,在<插入CDN名>這個選項處操作。


總是使用通用類的錯誤信息:記住要始終使用通用的錯誤信息,例如,在登錄嘗試時,不要說“用戶名無效或密碼無效”,只說“證書無效”,讓暴力破解更難,雖然可以在注冊時枚舉電子郵箱,因為你的系統可能會(也應該)讓每個帳戶的電子郵箱是唯一的。如果你的應用程序產生一個異常,只是說“出錯了”,不要暴露異常堆棧。我也建議你使用一些方法來收集所有的異常,并發送到你的郵箱或展現在Raygun, Sentry, Airbrake的dashboard上。


確認用戶的電子郵箱或電話:在發送電子郵件或者通知之前要先確認這個郵箱或者電話是否屬于該用戶。值得推薦的做法是非阻塞法,即讓用戶可以在沒有確認的情況下登錄,但這也會影響線上用戶的使用。看看非死book:你可以使用未經證實的賬戶1天。之后,你必須在登錄之前確認郵件或電話。我常思考10分鐘后郵件失效這樣的服務,像上文提到的,好處并不是發送郵件給并不需要它們的用戶,而是讓你免于被用戶標示為垃圾郵件。


其他方面(不排斥其他安全措施)


不要因為供應商有很酷的功能或超低價格就選擇他們:你的數據是危險的,那么你的名聲也是危險的。有一個叫做“不愿信任”原則,這意味著信任之前你需要小心。減少你的信任也是一件好事。你越相信,就越危險。也就是說,我通常建議安全第一。一開始Bitbucket似乎比GitHub更便宜,但沒有兩因素身份認證。你的源代碼值多少錢呢?AWS引發了公有云市場的競爭;當他們開始關注敏感信息的安全性時,他們似乎做了一件偉大的工作。所以只是在價格便宜的情況下還不足以讓我換一個服務商。所有的事情都要被考慮到,但要知道,靜態頁面接受任何東西,經常會看到企業主頁上宣稱它們通過APT和SSL(不推薦使用)實現了網站安全。盡量不要輕易相信,當你信任時,先驗證!


(REST)面向API的開發:如果你細看AWS,你會發現API是第一位的,然后是web UI,最后是SDKs。API是可怕的,因為它是獨立于語言的。但我個人認為,這是將來發展的必然趨勢。還值得仔細觀察的是HATEOAS。它使各部分之間的可視化隔離變得容易。客戶端是靜態頁面,服務器是接收輸入和為前端產生輸出的大腦。它能更明確地分離角色和記錄,例如web服務器必須驗證輸入。否則non API的web應用程序更會混亂。


委托辦理信用卡:將風險委托給信任的實體是一個好建議。如果你自己去做這件事,就要從一開始就儲存信用卡數據,再想一想,這樣你要擔負多大的責任。如果你委托給可信的支付提供商,如Stripe或PayPal,會不會更好?我覺得會,除非你能做的更好。所以,要確保你的應用不要碰觸信用卡數據。可以重定向到他們的網站來完成整個過程。


現在去哪?


有太多的信息了,去搜索吧。OWASP和SANS會給你很大幫助。他們有很多項目、物品、清單和工具。我還建議關注你的工具和供應商方面的安全建議。除此之外,常去Reddit的/r/netsec逛逛。


我很快就會發布一篇關于小團隊web應用的開發晚期安全性的文章,敬請關注。



----------------------------------------------

相信愛閱讀的您,最近已經注意到了我們。
我們將陸續推出一系列關于業務設計和技術架構方面的好內容。


也歡迎您提出意見、推薦文章,為讓別人更了解這個世界,作出自己的貢獻。

歡迎任何目的的聯系。
我的郵箱:weikan@jointforce.com
我的QQ:3272840549。</span>

 本文由用戶 dmc3 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!