朱曄的網際網路架構實踐心得S1E6:給飛機換引擎和安全意識十原則

朱曄的互聯網架構實踐心得S1E6:給飛機換引擎和安全意識十原則

本文有兩個部分,先介紹一下給飛機換引擎這個事情我的一些經驗,因為篇幅較短然後介紹一下安全意識方面的一些心得。

給飛行中的飛機換引擎

所謂給飛行中的飛機(或飛馳的汽車)換引擎說的是我們需要對一個正在飛速發展的系統進行大幅度的架構改造,比如把All-in-one的架構改造成微服務架構,儘可能減少或消除停服的時間。一般而言,我們可以這麼來考慮方案,從重構的徹底性來說,分為這麼幾種:

· 徹底重新做,直接從前到後拋棄老系統

· 大規模重構,保留對用戶的這層皮,後面從服務到數據全部替換

· 小規模重構,保留對用戶的這層皮以及數據結構,逐一替換核心邏輯到微服務

在做換引擎方案選擇和設計的時候需要考慮到這麼幾個現實的情況:

· 業務需要發展。意味著會不斷有新需求需要開發,如果重構的時間拖的很長的話,我們需要在這段很長的時間內為兩套系統同時做新需求,新的系統還要不斷開發新需求,會增加更多的時間,如果新系統的開發不夠快的話,甚至一直上不了線。我們最好在重構之前對新增業務有一條界限,新系統只是覆蓋到這個版本做需求封板,然後和產品商量出一個妥協是否對於之後我們留1周作為系統切換期,這段時間不上線新需求。這1周分成幾個環節,需要2天的時間來做系統切換,然後需要5天的時間來觀察新系統的穩定性,修復新系統的Bug,隨後我們才能認為系統正式切換成功,把時間精力放到新業務的開發上。

· 數據需要遷移。我們是為一箇舊飛機在換引擎,無法拋棄飛機上的乘客。如果我們更改數據結構的話,需要對數據進行遷移。我們需要做下面的工作:

o 準備遷移腳本

o 準備緩存預熱腳本

o 使用既有的數據來測試這2個腳本,然後觀察新系統的運行情況

o 做反向數據遷移的腳本,我們要考慮到切換到新系統後運行不流暢需要整體回滾的情況,這個時候我們需要把數據從新的數據庫遷移回老的數據庫

這種方式的遷移是需要有短暫的停機的,兩個腳本的執行和驗證需要一段時間(數據量大的話導數據費時)。如果希望儘可能減少停機時間的話可以採用兩段走的方式,先同步今天前的99.9%的數據到新數據庫,在停機後再同步今天發生的那0.1%的數據。極端點希望徹底不停機的話,需要讓業務同時走兩套系統進行雙寫,這種方案大大增加複雜度,但是可以換來幾乎0停機的時間,除非是需要24小時在線的金融系統,一般不會考慮新老系統雙活的方案。

你可能會覺得對於重構,我們應該考慮不要改底層數據庫,這會大大增加複雜性。這還是取決於我們希望多徹底進行重構,很多時候底層的數據庫設計不夠靈活這是最致命的,如果不切換到新的數據庫,即使搞成了微服務架構數據還是揉在一起,只是形態上是微服務,底層還是亂七八糟,這堆業務代碼最終還是要進行一次重構。

· 最好沒有停機讓用戶沒有感知。沒有停機意味著老的系統一直在線上穩定運行,新的系統可以以獨立的形態重新部署一套,用戶對我們新系統的開發沒有感知。新老系統的面子可以長得一模一樣,但是底層的調用面目全非。由於兩套系統都在線上運行,我們可以在內部對新系統進行充分測試,還可以比較同樣的業務流程在數據呈現上是否有差異。可惜現實往往沒有想象的這麼簡單,很多時候我們會依賴各種三方數據,對於金融系統來說往往對接了銀行的系統,這不是依賴這麼簡單了,而是完完全全的依賴。如果我們依賴的第三方會受到我們重構的影響,或是我們就是在切換依賴方的話,是否停機就不是自己可以掌控的了,第三方需要有停機我們就只能停機。這個時候我們能做的是,讓網站進行部分功能的停機而不是整體停機,用戶可以以只讀形式使用網站的所有功能,但是不能做寫入操作,待三方系統切換完成後切換到新系統了才能使用所有功能,這樣用戶也會有安全感。

· 萬一遷移失敗怎麼辦。之前說了我們遷移數據需要考慮回滾方案,這裡還會有一些比較噁心的點是如果新老系統有一些內部外部依賴是公用的話,儘量要隔離清楚,讓新老系統徹底獨立,這樣才好回滾,如果遷移後新系統在跑了汙染了老系統的數據,這個時候再要回滾會出現回不去的情況,因為老系統無法適應新的數據。內部要進行徹底的隔離是比較簡單,對於三方的數據(比如CDN)我們也要考慮到數據在邊緣節點和用戶端緩存的情況,在設計方案的時候就要考慮到切換後回退的可能性,儘可能讓兩套系統使用獨立的數據不要產生數據覆蓋的情況造成汙染。

還是要具體的事情具體來分析,要給飛行中的飛機換引擎,最重要的就是1、得到產品方面的配合對需求做好鎖定;2、在開發過程中對新系統做好充分測試減少上線後修Bug的工作量;3、對遷移方案以及回滾方案做好自動化的腳本以及充分的驗證。

安全意識十原則

對於安全我不是專家,但是我發現有這麼一個問題,那就是做業務開發的同學往往一點安全意識都沒有,如果有的公司沒有安全方面的部門或專家的話,那麼安全問題真的會很嚴重,外面所謂的一些安全公司的外包滲透服務往往是淺層次的機器做一下掃描和滲透,很少在代碼和邏輯層面做深入的分析,安全要做好還是要靠一線程序員和產品經理的點點滴滴的意識。

在這裡,我介紹一下我總結出來的偏產品技術(網絡層面還有很多工作需要做,這裡沒有涉及)方面的安全意識十原則(黑客白帽子肯定會有自己的一些成體系的方法論,我這裡更多的是根據之前踩過的坑自己總結的一些經驗):

1. 安全問題是木桶效應。

整個系統的安全程度取決於木桶最短的那塊板。很多時候我們會召集安全專家和架構師和主站主流程主域名的系統進行安全分析和滲透測試,而黑客知道這點也往往喜歡找邊緣化的子站點或非核心邏輯進行攻破,這些模塊或站點往往是由初級程序員打造,有的甚至還不是主站統一的技術架構,總體上會防備薄弱。黑客能夠攻破任意站點進去到內網,就有種種可能。針對這點,我們需要做的是對於安全的排查,需要全面覆蓋,除非子站在部署上用戶體系上徹底隔離。

2. 開發層面:不要信任客戶端的任何東西。

對於HTTP協議,不管是頭裡面的東西(來源、客戶端類型、Cookie)還是正文裡面的東西,任何數據都是可以偽造的。我們往往會覺得Get的東西暴露在瀏覽器地址上,裡面的參數不安全,Post過來的數據因為不暴露就安全,然後會信任Cookie中的數據做一些權限控制,會用頭裡面的一些數據做一些安全性控制。這些不是說不能做,而是要從根子裡面有這個意識所有客戶端的東西可以用但不能不經過判斷直接相信。有一個容易犯的錯誤是,在設計Controller的時候我們可能會在參數裡讓客戶端傳過來UserID、Price等信息。這裡的問題在於,登錄後的UserID應該是保存在服務端的,客戶是誰不是客戶自己說了算的,應該是我們根據SessionID在服務端獲得的,我們需要全面排查代碼,不允許在Controller的方法裡存在類似於用戶ID這樣的字段。對於Price也是一樣的道理,如果這是一個購物的過程,那麼訂單的價格一定是在服務端計算的,我們只能依靠客戶端傳過來的ItemID來計算價格,不能相信和直接使用客戶端傳過來的價格入庫(僅僅作為呈現是可以的,客戶端來的還是呈現在客戶端,但是不能用於計算)。其實更麻煩的是,很多時候我們會用框架生成的Controller的CRUD接口的代碼,框架會在參數內直接放上完整的Entity,然後代碼裡會使用這個Entity直接對接數據訪問層入庫,因為客戶端和服務端用JSON在通訊,雖然我們看到客戶端傳給服務端的只是ItemID,但是你再傳一個Price確實也是可以生效進入Update語句入庫的(Entity某些字段為空那麼就不會進入Update語句,不為空就會更新)。下面會再提到這個問題,很多時候用戶看不到的不等於程序邏輯上不可行,服務端的接口層面往往會有比看到的更多的權限(甚至可以由貫穿數據庫的完整CRUD Restful API)。

3. 開發層面:數據就是數據代碼就是代碼。

不管是SQL注入也好XSS也好都是這個問題,把數據和代碼混在了一起。對於客戶端過來的任何信息應該都只是數據,應該很少會讓客戶端來告訴服務端執行的代碼。所以這個事情我們要從兩方面來防範,第一客戶端過來的數據需要讓它當成數據來處理,不管是Encoding一下也好,SQL參數化(Mybatis的$和#問題)也好都是這個方面的措施(從前到後一路數據都以數據的身份在程序中流轉),第二從數據庫裡出來的東西也只能是數據不能讓它變為HTML或JS代碼,也需要Encoding一次再在客戶端上呈現。SQL注入的防範很多人喜歡用敏感字符替換的方式來做,這種做法其實是會有遺留的,我們不應該去考慮排除數據中有代碼的可能性,而是應該從根源上去讓數據只可能成為數據。

4. 開發層面:用戶看不到不等於黑客看不到。

第一個方面想說的是,隨著前後端的分離,現在很多請求都是AJAX請求,AJAX請求會有幾方面的安全疏漏:

· 邏輯分散而明確。對於服務端渲染,我們會把各種邏輯整合在一起,用戶看到的是一個完整的渲染後的頁面不清楚裡面有多少邏輯,對於AJAX請求,我們會以良好的方法命名來命名各種API。這個時候會給黑客可乘之機,會更容易分析理解程序運行的流程,畢竟在尋找突破之前需要先理解程序的流程。

· 容易覺得AJAX請求是前端程序發起的而忽略權限問題。沒錯,AJAX的發起人也是我們的代碼,但是AJAX請求也是HTTP請求誰都可以發起。如果我們對AJAX請求的參數設計有所鬆懈,犯了之前說的兩個錯的話那就很危險了。特別是前後端都是一個人來寫的話,程序員在實現代碼邏輯的時候往往只會考慮數據的傳輸簡單通暢,後端給前端,前端再給後端放鬆安全意識。

第二方面說的是,後端往往會返回更多的數據給前端,前端選擇需要的數據進行呈現,有的時候甚至直接返回的是代碼生成器生成的DbEntity(在三層架構中,DbEntity對應表結構,ServiceEntity是充血的領域實體,ResponseEntity是面向呈現的實體,三者不應該是一套)。這裡的問題在於:

· 服務端返回了過多的數據,這是比較危險的事情,因為有一些內部的字段會返回出去。

· 服務端返回的數據列對應了數據庫實際的列,相當於暴露了表結構,對於以後各種API的嘗試和注入極端危險。

· 一些敏感的數據也直接返回給客戶端了,雖然客戶端不會展現出來,但是對於黑客來說根本不在於用戶看得到看不到這些數據。

這就要求我們在做設計的時候儘可能仔細審視AJAX的接口的權限、數據開放性和脫敏等問題。一些框架提供的腳手架生成工具以及Restful API自動生成工具,即使要用也要做好權限設置。

5. 開發層面:最小化接口權限設計複用性矛盾。

在做設計的時候我們會考慮到複用性的問題讓方法儘可能通用,但是對於對外的接口我們要儘量收縮功能。舉一個之前看到的例子,我們需要驗證遊戲的密保卡,需要用戶告知三個座標的密保數字,比如A1B2C3三個座標,每一個數字是0到99,三個數字同時猜對的可能性是百萬分之一,但是接口的設計居然是直接讓用戶傳三個座標,這裡有兩個嚴重的設計錯誤:

· 第一,為什麼是允許客戶端傳過來三個密保的位置,合理的做法是服務端告知客戶端此次校驗的ID,然後客戶端傳過來ID,服務端在Session或緩存中去獲取座標位置。

· 第二,接口允許傳三個座標也算了,還允許是相同的座標,我們完全可以改造接口傳A1A1A1然後依次暴力破解0到99,馬上就可以得出A1座標的值,幾秒的時間就可以把整個密保卡完全暴破出來。

在設計服務端接口的時候,我們最好針對某個功能設計最小的接口,而不是開放的通用的接口,對外的接口設計安全性需要大於重用性。

6. 開發層面:一開始就要考慮安全,放出去了就沒後悔藥。

比較無奈的是,很多項目我們處於趕進度,一開始第一版的時候並沒有對接口做加密和簽名驗證。如果做的是一個APP,那麼我們要考慮到APP用戶不升級的情況,即使v2的版本的數據都是加密的,參數都是驗籤的,但是我們還不能下架v1版本(強更會損失多少用戶難以估計)。這個時候就比較無奈了,明知道v1版本的安全性有著很大的風險卻不能升級。不僅僅是接口的安全性,安卓客戶端的代碼也要考慮到反編譯的可能性需要混淆。之前遇到過有一個App客戶端裡網絡層直接使用了服務端的接口定義,導致客戶端代碼裡可以對服務端所有接口一清二楚,還不乏一些不能對外使用的內部接口以及已經淘汰的老接口,加上接口的調用又沒有簽名數據傳輸又不加密,拿著這份網絡協議什麼都可以幹。

7. 產品層面:做好防刷和暴力破解控制。

顯性的功能固然重要,但是產品經理也需要在隱性功能和風控策略上下一些功夫,產品經理沒有這方面意識的話開發往往更不太會做深入考慮。包括:

· 公開出去的短信驗證碼服務防刷控制(防止被刷子利用做短信轟炸),在用戶體驗和防刷上做平衡,比如到達一定的頻次後出驗證碼,服務端對客戶端的一些頭做校驗(刷子一般不知道這樣的邏輯)等等。不僅僅是短信驗證碼,公開出去的不需要授權就可以用的服務都需要防刷。

· 登錄是用戶從匿名進入授權的重要環節,對這一環節做一些策略。比如異地登錄提醒、錯誤登錄後出驗證碼、太多次錯誤後禁用等等。

· 涉及到積分、兌換、返現、抽獎這種和錢打交道的業務,要多考慮是否會有刷的可能,不僅僅是程序邏輯方面的漏洞,唯一性的判斷,還要考慮積分流通起來後是否會有擼羊毛小換大的可能。有利益的地方就有羊毛,如果一個業務都是羊毛在參與的話那麼賬面數據可能挺好看,但實際上幾乎帶來不了忠實的有留存的用戶。

重要的業務需要有風控方面的產品經理和業務產品經理一起來參與,共同討論流程,防羊毛,防黑客, 防刷子。

8. 產品層面:注意產品邏輯一體性。

很多業務流程是授權-執行這樣的兩步,但有些時候這兩步在產品設計的時候並不一定是同一個產品經理在設計,會出現授權和執行不一致的情況。見過一個修改綁定郵箱的產品邏輯,用戶在頁面X先進行短信驗證碼驗證,然後就可以跳轉到Y頁面進行修改綁定郵箱,X頁面是通用的短信驗證頁面,Y是新的修改綁定郵箱的需求,這裡出現一個邏輯漏洞,就是在X頁面驗證完成後進入Y頁面,如果這個時候利用另外一個TAB退登登錄切換用戶後,在Y頁面還是可以修改綁定郵箱的,這個時候程序從Session中讀取的到用戶並不是之前那個通過短信驗證的用戶,而是後面登錄的新用戶,相當於我們就可以為任意用戶修改綁定郵箱了。對於多步走的邏輯,我們一定要作為一個整體來思考。黑客在尋找突破的時候特別喜歡尋找ABC幾步走的邏輯,然後嘗試最後一步C是否可以單獨來做尋找邏輯方面的漏洞。

9. 運維層面:做好異常數據監控報警。

在運營層面,我們需要對方方面面的數據有報表或監控,對於異常的數據徒增及時進行調查,業務量的增加如果不伴隨活動或推廣的話不一定是好事情。在運維層面,我們同樣也需要對網絡磁盤的使用徒增以及服務的調用量上升查明原因,看看這是業務導致的還是可能是安全性問題。對於各種三方服務(比如短信通道、CDN、文件存儲等)的使用,最好也有日報級別的監控,以免看到月度的賬單後再發現被刷的問題哭都來不及。

10. 運維層面:對內的數據和權限問題。

我們說內部人是最難防的,內部系統繁雜,授權和審計做的可能不那麼完善,內部人員如果有心的話獲取一些數據做一些壞事造成的影響會比較大,我們需要從幾方面來做一些措施:

· 線上的配置儘量加密,配置信息和代碼分離

· 敏感數據(用戶信息)在數據庫中加密保存

· 線上數據訪問需要使用類似於phpmyadmin的Web網站,不允許使用客戶端工具,這樣可以做比較健全的授權,防止數據導出

· 內部系統涉及到用戶信息部分脫敏顯示,顯示完整的數據需要上級授權

· 所有內部系統的登錄和使用需要有完整的日誌,對日誌進行分析和定期的審計


分享到:


相關文章: