讀軟體開發安全之道:概念、設計與實施16安全開發最佳實踐

躺柒發表於2024-09-03

1. 安全測試的最佳實踐

1.1. 編寫可靠的安全測試用例是提升任何程式碼庫安全性的重要方式

1.2. “測試驅動的開發”(Test Driven Development,TDD)

  • 1.2.1. 在編寫新程式碼的同時編寫測試用例

1.3. 利用整合測試

  • 1.3.1. 整合測試(integration testing)可以把系統置於它們的目標環境中,確保透過單元測試的所有元件能夠按照預期工作

1.4. 最明智的做法是把工作進行細分,每部分工作都有一個里程碑,各部分都可以把工作繼續向前推進一步

1.5. 如果團隊中來了一些新人需要學習程式碼,可以讓他們編寫安全測試用例

  • 1.5.1. 不僅是幫助他們學習程式碼的最好方法,而且可以為程式碼產生持久的價值

2. 程式碼質量

2.1. 如果我們可以提升程式碼的質量,就可以在很長時間內讓程式碼更加安全,而無論我們能不能意識到這種安全性的提升

2.2. 所有漏洞都是錯誤(bug),所以錯誤越少意味著漏洞和漏洞鏈越少

2.3. 程式碼清潔

  • 2.3.1. 程式碼異味(code smell)、麵條式程式碼(spaghetti code)和標記程式碼需要進一步完善的TODO 註釋往往都是產生漏洞的沃土

  • 2.3.2. 使用工具來標記問題

  • 2.3.2.1. Lint和其他靜態程式碼分析工具可以提供更豐富的程式碼審查,有時可以為我們解釋程式碼的錯誤和漏洞

  • 2.3.2.2. 頻繁使用相關工具來減少可能的錯誤數量

  • 2.3.3. 找個時間進行一些清理工作並不是一件簡單的事情

  • 2.3.3.1. 我們可以用增量的方式,哪怕每週花上一兩個小時,隨著時間的推進,我們都可以給專案帶來巨大的改善,這個過程也可以讓我們更好地熟悉巨大的程式碼庫

2.4. 異常和錯誤處理

  • 2.4.1. 認識到異常處理不佳的巨大風險,然後思考所有正確的響應方式,找到即使最不可能發生的意外情況

  • 2.4.2. 最好在儘可能接近源頭的地方處理異常,因為越接近源頭,與環境的關係就越密切,進一步產生複雜性的機率就越小

  • 2.4.3. 大型系統可能需要一個頂層處理器來處理所有突發的未處理異常事件

  • 2.4.4. 在錯誤處理中,異常處理不佳往往都會與潛在的漏洞關聯

  • 2.4.5. 最好的方法就是從一開始就實施可靠的錯誤處理

  • 2.4.5.1. 在協同攻擊中,引發錯誤可能正是攻擊者的主觀目的

  • 2.4.6. 為了能夠正確地執行錯誤和異常處理,進行可靠的測試至關重要

  • 2.4.6.1. 確保所有程式碼路徑都測試到位,尤其是那些不常用的程式碼路徑

  • 2.4.7. 積極調查並且修復那些間歇發生的異常情況,因為如果聰明的攻擊者知道如何觸發異常情況,他們恐怕就可以對其進行微調,從而惡意利用其中的漏洞

2.5. 記錄安全性

  • 2.5.1. 對於關鍵程式碼,或者那些我們需要對安全性加以解釋的程式碼,註釋格外重要,因為註釋可以讓那些考慮修改程式碼的人瞭解其中的利害關係

  • 2.5.2. 好的註釋可以解釋清楚問題所在

  • 2.5.3. 目的是對那些不夠明顯、在未來很容易被忽略的問題進行註釋,以警告讀者

  • 2.5.4. 註釋永遠無法替代那些知識淵博的程式人員,這類人員會對安全隱患時刻保持警惕,因為他們深知這些任務有多麼艱鉅

  • 2.5.5. 撰寫一份好的安全測試用例是一種理想的文件備份方法,可以防止有人將來不知不覺地破壞程式碼的安全性

  • 2.5.5.1. 這種測試作為一種模擬攻擊的方法,不僅可以防止有人不小心對程式碼進行了不利的變更,還可以準確地顯示出程式碼中可能出現的錯誤

2.6. 安全程式碼審查

  • 2.6.1. 同級程式碼審查

  • 2.6.1.1. 涵蓋了審查人員應該留意的潛在問題清單,包括程式碼是否正確、是否可讀、程式碼的風格等

  • 2.6.1.2. 能夠明確地包含安全性審查

  • 2.6.2. 考慮程式碼的安全性,也就是從安全性的角度重新審視這些程式碼,這一步可以在第一次閱讀程式碼之後執行

  • 2.6.3. 重視安全性的程式碼都值得我們萬分留意,我們對程式碼質量的要求也格外高

  • 2.6.4. 作為審查人員,如果我們認為某些輸入可能存在問題,就應該撰寫一份安全測試用例,看看會發生什麼,而不是純粹靠猜測

3. 依賴關係

3.1. 當今系統往往都建立在大量外部元件上,這種依賴關係在很多方面都是麻煩重重

3.2. 使用包含已知漏洞的舊版外部程式碼,是整個產業至今無法系統性解決的最大現實威脅之一

3.3. 在軟體供應鏈中選擇了惡意元件也是一大風險

3.4. 選擇安全的元件

  • 3.4.1. 基本因素

  • 3.4.1.1. 相關元件及其廠商的安全追蹤記錄如何?

  • 3.4.1.2. 這個元件的介面是私有的嗎?有沒有其他能夠相容的產品?

>  3.4.1.2.1. 選擇越多就越有可能有安全的產品可供選擇
  • 3.4.1.3. 什麼時候發現了元件中的安全漏洞?

  • 3.4.1.4. 我們對(產品的)廠商能夠及時響應並提供修復方案有把握嗎?

  • 3.4.1.5. 讓這個元件時刻保持更新狀態的操作成本(包括我們需要付出的努力、元件的當機時間和我們需要支付的費用)有多高?

  • 3.4.2. 要讓系統整體更加安全,這個系統的所有元件就都必須是安全的

  • 3.4.3. 元件之間的介面也必須是安全的

  • 3.4.4. 用來處理隱私資料的元件必須保證不會洩露資訊

  • 3.4.4.1. 軟體會記錄資料的內容,或者把它儲存在不安全的介質中,這就會增加資料洩露的風險

  • 3.4.5. 不要使用元件正式釋出之前的原型(prototype)元件,其他不滿足高質量產品要求的元件也要避免運用在系統當中

3.5. 保護介面

  • 3.5.1. 一個擁有良好記錄的介面應該明確指出自己的安全和隱私屬性

  • 3.5.2. 不要使用已經被棄用的API

  • 3.5.2.1. 棄用API的原因並不僅僅是安全性一項,但是作為API的呼叫者,我們一定要弄清楚API棄用的原因是不是存在安全隱患

  • 3.5.3. 那些包含複雜配置選項的API也需要我們格外警惕

  • 3.5.3.1. 遵守預設防禦模式,把如何安全地配置系統記錄下來,同時儘量提供輔助方法來確保配置的方法正確無誤

  • 3.5.3.2. 如果我們必須暴露一些不安全的功能,就要儘可能確保沒有人可以在不知道這些功能的作用時就無意中使用到這些功能

3.6. 不要做重複的工作

  • 3.6.1. 只要條件允許,就應該使用標準的、高質量的庫來提供基本的安全功能

  • 3.6.2. 用一個庫或者框架來解決一個潛在的安全問題往往就是最好的方法

  • 3.6.3. 安全漏洞可能非常微妙,攻擊方式有很多種,攻擊者只要成功一次就足夠了

  • 3.6.4. 人工引入的錯誤永遠都會帶來發起攻擊的良機,所以透過解決方案讓人們把事做對,這才是最保險的方法

3.7. 對抗傳統安全

  • 3.7.1. 安全技術的發展總是相對滯後一些

  • 3.7.2. 凡事皆有“保質期“

  • 3.7.3. 如果使用密碼進行認證的方式很容易受到網路釣魚攻擊,那就採用雙因素認證的方式

  • 3.7.4. 隨著量子計算的不斷成熟,高安全性的系統開始具備抵抗後量子時代演算法攻擊的能力

  • 3.7.5. 慣性本身就是一種強大的力量

  • 3.7.5.1. 系統往往都是用增量的方式漸漸演化的,所以沒有人會質疑如今認證和授權的執行方式

  • 3.7.6. 企業安全架構往往都要求所有子系統能夠相互相容,所以所有變更都意味著每個元件要用全新的方式進行互操作

  • 3.7.7. 那些過時的元件也會帶來各式各樣的問題,因為傳統軟硬體可能無法支援當今的安全技術

  • 3.7.8. 沒有什麼簡單的方法可以解決傳統安全方法帶來的隱患,但是威脅建模可以幫我們找出傳統安全方法可能帶來的問題,讓這些技術引發的風險更加清晰可見

4. 漏洞分類

4.1. DREAD評估

  • 4.1.1. 最早由傑森·泰勒(Jason Taylor)構思

  • 4.1.2. DREAD評級是相當主觀的

  • 4.1.3. 潛在破壞(Damage potential)

  • 4.1.3.1. 如果攻擊者利用這個風險,它可以給我們造成多大的破壞?

  • 4.1.4. 成功率(Reproducibility)

  • 4.1.4.1. 攻擊是每次都會成功,有時會成功,還是很少成功?

  • 4.1.5. 利用難度(Exploitability)

  • 4.1.5.1. 從技術難度上看,利用這個漏洞需要攻擊者付出多少努力和資金?

  • 4.1.5.2. 攻擊路徑有多長?

  • 4.1.6. 影響的使用者(Affected user)

  • 4.1.6.1. 攻擊會影響所有使用者、部分使用者,還是一小部分使用者?

  • 4.1.6.2. 攻擊是針對特定目標,還是隨機選擇受害者?

  • 4.1.7. 發現難度(Discoverability)

  • 4.1.7.1. 攻擊者發現這個漏洞的可能性有多大?

4.2. 大多數安全問題一旦被發現就不難修復,我們的團隊都能迅速對修復的方法達成一致

4.3. 除非有重要限制條件規定人們必須採取權宜之計,否則只要有任何可以被加以利用的漏洞,我們都應該對這個漏洞進行修復

4.4. 幾個小的程式碼錯誤結合在一起就會形成一個重大的漏洞,漏洞鏈由此產生

4.5. 編寫利用漏洞的有效程式碼

  • 4.5.1. 用概念驗證的方式構建一個有效的攻擊,是解決漏洞問題的最強方法

  • 4.5.2. 對初學者來說,構建一個能夠利用漏洞的示範程式碼需要完成大量的工作

  • 4.5.3. 實際利用漏洞的程式碼需要我們在發現底層漏洞之後進行大量的細化工作

  • 4.5.4. 即使是一位身經百戰的滲透測試專家,也不能因為自己無法建立出利用漏洞的程式碼,就認定這個漏洞不可能被人利用

4.6. 做出分類決策

  • 4.6.1. 發現一個潛在的漏洞卻置之不理,這樣的錯誤只能用“可悲”來形容,這也正是我們應該著意避免的情況

  • 4.6.2. 趁早修復這樣的缺陷才是長治久安之道

  • 4.6.3. 對於所有特權程式碼或者訪問有價值資產的程式碼,其中的錯誤都應該修復,然後需要認真地進行測試,防止引入新的錯誤

  • 4.6.4. 那些被隔離在所有攻擊面之外而且看起來無害的錯誤可以推遲修復

  • 4.6.5. 如果聽到某個錯誤是無害的,一定要仔細加以確認:有時候,修復這個錯誤比評估它有可能給我們帶來的影響更容易

  • 4.6.6. 儘快主動修復那些有可能屬於某個漏洞鏈上的錯誤

  • 4.6.7. 如果是麻煩,我就建議你儘早解決

  • 4.6.7.1. 安全第一,沒有後悔藥可吃

  • 4.6.8. 不要浪費時間討論假設的條件

  • 4.6.8.1. 應該把注意力集中在理解問題的各個方面

5. 維護一個安全的開發環境

5.1. 即便是開發過程中一次小小的失誤,惡意程式碼也有可能趁機潛入產品,因此這時我們應該停下手裡的工作

5.2. 開發和生產環境相分離

  • 5.2.1. 在開發軟體的時候,開發人員不可以訪問到生產環境中的資料

5.3. 保護開發環境

  • 5.3.1. 如果我們希望開發的成果安全無虞,那麼參與開發的所有計算機都必須是安全的,同樣所有原始碼庫和其他服務也必須是安全的,因為漏洞也有可能從這些地方潛入最終的產品

  • 5.3.2. 安全地進行配置、定期更新開發裝置

  • 5.3.3. 限制開發裝置的使用人員

  • 5.3.4. 系統地審查新的元件和依賴關係

  • 5.3.5. 安全地管理那些用來開發和釋出產品的計算機

  • 5.3.6. 安全地管理金鑰(包括程式碼簽名金鑰)

  • 5.3.7. 使用強大的認證機制,同時對登入證書進行妥善管理

  • 5.3.8. 定期稽核異常活動的源變更方案

  • 5.3.9. 給原始碼和原始碼的開發環境儲存一份安全的備份資料

5.4. 釋出產品

  • 5.4.1. 使用正式的釋出流程把開發和生產聯絡起來

  • 5.4.2. 建立一個共享的儲存庫,這個庫只有開發人員可以修改,操作人員只有只讀許可權

  • 5.4.3. 許可權分離不僅可以明確各方的責任,而且可以落實各方的責任

6. 及時採取行動

6.1. 錯誤越少就代表能夠利用的漏洞越少

6.2. 稱職的設計方案、盡職的程式碼編寫、完整的軟體測試和文件記錄

7. 安全是每個人的職責

7.1. 安全分析最好由最理解這個軟體的人來完成

7.2. 只有在安全成為整個軟體生命週期中一個不可或缺的環節時,安全性才能得到最好的保障,但是長期花錢聘請安全顧問畢竟是不切實際的

7.3. 安全思維並不複雜,但是安全思維非常抽象

7.4. 所謂“廣泛的安全參與”應該理解成整個團隊一起努力,每個人都做自己最擅長的工作

7.5. 外部專家非常適合執行差距分析或者滲透測試這類任務,這樣可以彌補組織機構自己的能力,用豐富的經驗從全新的視角審視我們的產品

  • 7.5.1. 即使專家能夠為整體安全例項做出重要貢獻,一天時間到了之後他們也會立刻告辭

8. 避免亡羊補牢

8.1. 橋樑、道路、建築、工廠、商店、水壩、港口、火箭等,都要在紙上進行精心設計和仔細審查確保安全之後,才能開工建造

8.2. 大多數軟體都是先開發出來,然後才考慮保護措施的

8.3. 早期進行安全調查不僅可以幫我們節約時間,而且可以讓我們獲得可觀的回報,提升產品的質量

8.4. 在最壞的情況下,把安全性問題放到設計階段進行考慮的最具說服力的理由是避免因設計產生的安全漏洞

8.5. 好的安全設計決策可以讓我們獲益更多

  • 8.5.1. 安全設計可以在最大限度上減少在實施中引入漏洞的可能性,但是它並沒有魔力,所以不可能讓軟體刀槍不入

8.6. 專注於軟體安全性的設計方案審查是非常重要的一步,因為軟體的功能審查採取的是完全不同的視角,提出的問題也從來不以安全為重

8.7. 不安全的設計方案也可以輕鬆透過這些測試,同時由此實現的軟體也很容易遭到攻擊的破壞

9. 未來安全

9.1. 我們每個人都可以做出貢獻的方式是確保我們開發的軟體質量可靠,因為這類軟體都是在漏洞中汲取經驗的產物

9.2. 隨著我們的系統在功能和規模兩個維度不斷擴充套件,系統的複雜性也難免會隨之增加

  • 9.2.1. 隨著軟體系統不斷擴大,管理複雜性的難度也變得越來越高,這些系統也因此變得越來越脆弱

  • 9.2.2. 對一個大型資訊系統,劃分大量元件的對應安全模組是成功的必備要求

9.3. 提升安全性的一種方法是對傳統的錯誤進行更好的分類,也就是考慮每項錯誤可以如何成為攻擊鏈中的一個環節,然後對優先修復哪些錯誤進行排序

9.4. 從最低程度的透明性到最高程度的透明性

  • 9.4.1. 如果你無法測量,你就無法改進

9.5. 軟體發行商基本都能在不影響未來安全的情況下提供更多的資訊

9.6. 未來,負責任的軟體發行商應該自行披露完整的資訊,然後根據需要對披露的資訊進行編輯,以免降低軟體的安全性

9.7. 當代大型軟體系統都是由大量元件構成的,這些元件都必須是由可靠實體從安全的子元件中使用安全工具集建立出來的,同時它們的來源也必須是真實的

  • 9.7.1. 我們都會使用一些必備工具來確保軟體構建的完整性,而這些工具如今可以免費獲得,我們也可以預設這些軟體能夠正常工作

9.8. 軟體有一種獨特的屬性,它們完全是由位元(也就是一串0和1)組成的,所以我們完全可以憑空想象出一個軟體

  • 9.8.1. 唯一的限制就是我們自己的想象力和創造力

  • 9.8.2. 在實現一個系統的過程中,我們應該保留所有關鍵決策和操作的記錄以供審計之用,這樣系統的安全屬性才能落到實處

10. 移動裝置上的資料安全

10.1. 資料保護的層級

  • 10.1.1. 對於移動裝置來說,讓使用者(使用密碼、指紋或者面部識別)解鎖加密金鑰才能訪問受保護的資料,相當於每次開啟金庫都要去找銀行經理

  • 10.1.2. 即使限制最低的保護級別——稱為首次解鎖後(After First Unlock,AFU),也存在嚴重的限制,這種保護級別會在啟動之後要求使用者提供證書來重構加密金鑰

  • 10.1.3. 大多數App出於方便,採取了不對資料進行保護的做法,所以只要攻擊者可以檢視移動裝置,所有資料也就成了刀俎上的魚肉

10.2. 機密訊息App是這類規則的一大例外,它們採用的是“完全保護”類

10.3. 雲整合對很多App的重要性

  • 10.3.1. 為了利用雲的強大能力,我們必須首先信任雲,允許它處理我們的資料,而不能把加密後的資料鎖在我們自己的本地裝置上

  • 10.3.2. 所有這些無縫的資料訪問都和強大的資料保護機制背道而馳,如果我們弄丟了連線雲端的那臺手機,安全性就更無從談起了

  • 10.3.3. 依靠雲的那些App基本上不會選擇透過加密來保護資料

10.4. 移動裝置處於一個龐雜的生態系統當中,除非資料保護機制可以作用於所有元件和場景,否則這些機制很難發揮它們的作用

10.5. 對於那些你很介意洩露出去的資料,不要把它們儲存在你的手機裡

相關文章