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

發表於2019-05-11
原文: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,甚至在facebook上你可以看到這種事情發生。例如,編輯使用者帳戶的細節時,你能確保如果使用者輸入嵌入了另一個使用者的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。一定要遠離!
完整內容點此檢視
回覆

相關文章