本文分享自華為雲社群《程式碼的安全檢視》,作者:Uncle_Tom。
1. 熵的故事
熵的概念最早起源於物理學,用於度量一個熱力學系統的無序程度。熱力學第二定律,又稱“熵增定律”,表明了在自然過程中,一個孤立的系統總是從最初的集中、有序的排列狀態,趨向於分散、混亂和無序;當熵達到最大時,系統就會處於一種靜寂狀態。
通俗的講:系統的熵增過程,就是由原始到死亡的過程。“熵” 是 “活躍” 的反義詞,代表負能量。
物質總是向著熵增演化:- 屋子不收拾會變亂;
- 手機會越來越卡;
- 耳機線會凌亂;
- 熱水會慢慢變涼;
- 太陽會不斷燃燒衰變
- … …
- 直到宇宙的盡頭 – 熱寂。
自然界除了自然風化的作用,也少不了人為的因素。
1982年3月,詹姆士·威爾遜(James Q. Wilson)及喬治·凱林(George L. Kelling)發表一篇題為《Broken Windows》的文件,提出了犯罪學的一個理論 – 破窗效應。此理論認為環境中的不良現象如果被放任存在,會誘使人們仿效,甚至變本加厲。
一個房子如果窗戶破了,沒人去修補,隔不久,其他的窗戶也可能莫名奇妙地被人打破。環境可以對一個人產生強烈的暗示性和誘導性。這個理論從另一個側面說明了對一個系統需要及時維護,以減少人為的熵增現象。古人云:勿以惡小而為之,勿以善小而不為。
對於我們創造的程式碼,也是一樣,大家制定了編碼規範,以期儘量減緩熵增,從而達到延長軟體的生命週期。
2. 程式碼檢視的意義
程式碼檢視一直被認為是提高程式碼質量的重要手段,同時也是發現潛在問題的重要途經。程式碼檢視有以下重要意義。
2.1. 編寫易於理解的程式碼
程式碼檢視是確保程式碼質量的重要環節,它有助於開發人員編寫易於理解的程式碼。
微軟和谷歌等公司在程式碼檢視中積累了豐富的經驗,強調了程式碼的可讀性對於團隊協作的重要性。當程式碼清晰、結構良好時,其他開發人員能夠更快地理解程式碼的功能和目的,從而提高團隊的整體效率。此外,良好的程式碼可讀性也有助於新成員快速融入團隊,因為他們可以更容易地理解現有的程式碼庫。
史蒂夫·邁克康奈爾的《程式碼大全》,被福布斯技術委員會(Forbes Technology Council)譽為“有史以來最好的軟體開發基礎書” 。
《程式碼大全》書中提到了提高程式碼可讀性的多種方法,包括使用有意義的變數名、避免巢狀過深的語句、使用函式和模組、物件導向程式設計、避免重複程式碼、使用設計模式、編寫註釋和文件、使用除錯工具、進行程式碼審查、學習和使用更高階的程式語言和技術等等。
軟體工程的核心就是管理複雜度。規模更大的專案,如果不對複雜性做出限制,那麼專案的每個步驟都可能崩潰失控。以往,無數大型專案都在砸下巨量資金後失敗了,原因就是其過於複雜,已經無人能理解究竟發生了什麼,就如同過於龐大的巨獸被自身重量所壓垮。
複雜性其實貫穿於軟體開發的各個階段,複雜性在編碼過程中,如果底層程式碼的質量不好,超大規模系統也可能就由此崩潰。所以必須立足底層,立足細節抓程式碼質量,關注每個語句、例程和類,步步為營,以此為基礎才能擴大規模,同時繼續保持程式碼質量,即在設計和架構層級控制複雜性,對複雜性控制要廣泛而深入地體現在軟體開發的各個階段。
唐納德·克努特(Donald Knuth),他在程式設計界被廣泛認為是“計算機程式設計藝術”的創始人之一。克努特曾說過:“Programs are meant to be read by humans and only incidentally for computers to execute.” 這句話強調了程式碼的可讀性對於人類的重要性,意味著程式碼首先應該是為了讓人理解而編寫的,而機器執行只是其次。
這是《A practical guide to object-oriented metrics》中的一個統計圖,作者分析了一個資料庫系統,黃色柱狀體是缺陷的百分比,紅色柱狀體是程式碼的比例,橫座標標識了不同圈複雜度。最左邊程式碼圈複雜度在0-5的時候,程式碼量佔了總量的40%,缺陷數佔了10%,而最右邊圈複雜度為15+, 程式碼量佔比10%,但缺陷數量佔了缺陷總數的40%+。從左向右的可以看到程式碼的圈複雜度越高,缺陷的比例也越高。
通常我們不能一次性編寫出完全滿足需求的程式碼;或者需求在不斷的變化;維護程式碼的人往往不是原始開發的人員,這些都要求我們編寫的程式碼是可被另一個人所理解和修改的。
2.1.1. 維護規範
程式碼檢視是維護編碼規範的關鍵機制。
程式碼的可讀性、可維護性需要具體的規約來保持。我們往往透過編碼規範來維護和保持程式碼的易於理解和避免一些常見的錯誤(或被稱為最佳實踐)。因此各大公司都有了自己的編碼規範,比如我們熟知的google的編碼規範。同時行業也為了維護行業的統一性,也給出了行業的編碼規範,像MISRA C 2012、AUTOSAR C++14 等等。更多的關於規範的資訊,可參考《一圖看懂軟體缺陷檢查涉及的內容》。
透過程式碼檢視,可以確保所有程式碼都遵循了既定的編碼標準和最佳實踐。這有助於保持程式碼庫的一致性,減少由於風格不一致或不遵循規範導致的維護問題。
目前的編碼規範通常會包含兩部分:
一致的程式碼風格。整個專案中保持一致的編碼風格,可以減少開發者在閱讀不同部分程式碼時的認知負擔,因為他們不需要適應不同的風格。這包括:
- 命名規範
使用清晰、有意義的變數名和函式名,可以幫助其他開發者快速理解程式碼的目的和功能。例如,使用calculateTotalPrice代替cTP,使得函式的作用一目瞭然。 - 程式碼格式化
統一的縮排、括號使用和空白使用,可以使程式碼結構更加清晰,便於閱讀和理解程式碼的邏輯結構。 - 註釋和文件
適當的註釋和文件可以為程式碼提供上下文資訊,解釋複雜的邏輯或決策,使得其他開發者能夠更快地理解程式碼的意圖和工作方式。
編碼實踐
每個程式語言都有自己的特點和特定的規約,很多地方往往被開發人員忽略從而埋下潛在的風險。
這部分通常包括:
- 宣告和初始化
- 型別轉換
- 表示式
- 函式或方法
- 併發
- 錯誤和異常處理
- 物件導向的程式設計
- 輸入輸出
- 外部輸入校驗
- 日誌處理
- 其他
規範的維護還有助於提高程式碼的可維護性和可擴充套件性,因為所有開發人員都遵循相同的規則和模式。
目前規範的很多部分都能夠被靜態分析工具檢測到,甚至自動修復。這些工具的告警也會在程式碼檢視的時候得到確認,是否是誤報,還是確實需要修改。對被確認誤報的部分會反饋給工具團隊,進一步的修正檢查工具,起到對工具反饋的作用。
此外,程式碼檢視還可以作為規範更新和改進的反饋機制,團隊可以根據實際開發經驗,不斷調整和完善編碼規範,以適應不斷變化的開發需求和技術進步。
2.1.2. 學習和教育
程式碼檢視為開發人員提供了學習和教育的機會。
在微軟和谷歌等公司,程式碼檢視被視為一種學習和知識共享的平臺。透過檢視他人的程式碼,開發人員可以學習到新的程式設計技巧、設計模式和最佳實踐。
同時,程式碼的作者也可以從其他開發人員的反饋中學習,瞭解自己的程式碼在哪些方面可以改進,這對於個人技能的提升非常有幫助。程式碼檢視還可以作為一種教育工具,幫助新員工快速瞭解公司的程式碼風格和開發流程。透過參與程式碼檢視,新員工可以更快地融入團隊,提高自己的開發能力。
程式碼檢視還可以揭示潛在的設計問題,促進團隊成員之間的知識共享,增強團隊的凝聚力。透過程式碼檢視,開發人員能夠從同事那裡得到反饋,學習如何寫出更清晰、更高效的程式碼,這對於個人技術成長和團隊協作都是有益的。
2.1.3. 把關和事故預防
程式碼檢視在把關和事故預防方面起著至關重要的作用。
程式碼檢視除了確保程式碼的質量,還可以防止潛在的錯誤和缺陷進入生產環境。程式碼檢視可以幫助開發人員發現程式碼中的錯誤,包括邏輯錯誤、效能問題、安全漏洞等,從而在程式碼釋出前進行修復。這不僅可以減少生產環境中的問題,還可以降低維護成本和風險。
雖然靜態分析工具在缺陷檢查上承擔了越來越重要的防護作用,可畢竟檢查能力在開發框架和第三方呼叫上,需要很多的人為配置才能使分析工具在很大程度上識別框架上潛在的呼叫關係和第三方函式的行為。目前靜態分析工具在這方面都還有很大的欠缺,這將導致很多的漏報。所以程式碼檢視在防範程式碼缺陷上還是不可缺少的檢查環節。
隨著外部的惡意攻擊的增加,缺陷的發現和漏報的矛盾也越發的突出。
檢查工具畢竟只是輔助,還是需要開發人員建立安全防範意識,寫出沒有缺陷的程式碼,這個才是安全預防的治本方法。透過程式碼檢視,團隊可以建立起一種預防文化,不斷提高程式碼質量,減少事故發生的機率。
3. 程式碼檢視中需要注意的問題
3.1. 程式碼檢視的文化建設
將程式碼評審用作團隊建設活動,培養對發現缺陷的積極態度。為所有團隊成員提供糾正壞習慣、學習新技巧和擴充套件能力的機會。
尊重和鼓勵、禮貌和專業精神;- 作為稽核人員,保持尊重的態度,鼓勵團隊成員參與程式碼審查;
- 作為開發人員,放棄負面情緒,將其視為學習和成長的機會。
3.2. 程式碼檢視提交
一次程式碼提交不易過大SmartBear對思科系統程式設計團隊的一項研究表明,開發人員一次審查的程式碼行數不應超過200到400行(LOC)。大腦一次只能有效地處理這麼多資訊;超過 400 LOC,發現缺陷的能力會減弱。
在實踐中,在 60 到 90 分鐘內對 200-400 LOC 進行審查應該會產生 70-90% 的缺陷發現。因此,如果程式碼中存在 10 個缺陷,那麼正確進行的審查會發現其中的 7 到 9 個。
SmartBear 研究表明,以每小時 500 LOC 的速度快速下降,缺陷密度顯著下降。在有限的時間內以合理的速度進行合理數量的程式碼審查,從而產生最有效的程式碼審查。
-
完成本地測試;完成程式碼的自檢,是作為一個合格程式碼開發人員的基本素質。
-
每次提交的程式碼量不易過多,通常在 200 到 400 行程式碼之間,以保證審查質量;從前面的研究可以看到,太大的程式碼提交即不便於審閱者快速完成一次提交的審查,也不便於發現錯誤。關於如何將較大的改動拆分成較小的變動,google 在程式碼稽核中給出很好的建議。
-
有清晰的程式碼變更描述。可以包括:摘要、背景、說明、需求或設計的變更單關聯;目前程式碼審查的平臺都提供了很好的輔助,幫助稽核人員快速得到稽核程式碼相關的資訊,方便稽核人員理解被稽核程式碼。
3.3. 程式碼檢視稽核的主要內容
程式碼稽核人員在稽核過程中主要的稽核內容包括:
- 使用檢查清單是消除經常犯的錯誤和應對遺漏發現挑戰的最有效方法。清單為團隊成員提供了對每種型別審查的明確期望,並有助於跟蹤報告和流程改進目的。
- 所有內容都使用了明確的名稱,程式碼符合規定的風格指南;
- 不過度設計,程式碼並沒有比它需要的更復雜;
- 沒有明顯的邏輯錯誤,異常處理完備;
- 程式碼具有適當的單元測試,測試是精心設計的。
4. 程式碼檢視中需要注意的安全問題
結合業界的主要安全編碼問題,總結了程式碼檢視的安全檢查清單如下。
注:下面問題涉及的 CWE 有的是類別,需要包括下面的子CWE編號。4.1. 外部輸入校驗
[ ] 是否有外部輸入,例如:http request, 表單輸入、配置檔案、資料庫讀?
- [ ] 是否對輸入做了正確的轉義?
- [ ] 是否對輸入做了白名單或範圍檢查?
- [ ] 是否對外部輸入的連結執行了跳轉或資訊獲取?連結是否做了白名單檢驗?
防範的問題:
4.2. SQL 檢查
[ ] 是否有 SQL 操作?
- [ ] 是否有外部輸入作為 SQL 語句的組成部分?
- [ ] 是否有拼接 SQL 語句的場景?
- [ ] 是否有刪庫、刪表、索引變更、改許可權的操作?是否合理,並做了條件限制?
- [ ] 獲取的 connection 和 statement 是否關閉?
防範的問題:
4.3. 命令執行檢查
[ ] 是否有命令操作?
- [ ] 是否有外部輸入做為命令操作組成部分?
- [ ] 執行命令是否做了白名單校驗?
防範的問題:
4.4. 檔案操作
[ ] 是否有檔案操作?
- [ ] 檔案的路徑是否做了歸一化和路徑校驗?
- [ ] 是否為 XML 檔案, 是否做了 XXE 防護?
- [ ] 是否對 JSON, XML 等做了反序列化操作? 反序列化檔案是否安全?
- [ ] 是否做了寫檔案操作? 是否寫入了敏感資訊?敏感資訊的儲存是否符合規定?
- [ ] 是否有上傳檔案? 檔案的型別、大小是否做了限定和檢查?
- [ ] 是否有解壓縮操作? 是否對解壓縮的路徑和大小做了校驗?
- [ ] 檔案是否採用了 try-with-resource 方式開啟?否則是否在 finally 裡關閉?
防範的問題:
- CWE-22:對路徑名的限制不恰當(路徑遍歷)
- CWE-611:XML外部實體引用的不恰當限制(XXE)
- CWE-91:XML注入(XPath盲注)
- CWE-502:不可信資料的反序列化
- CWE-538:檔案和路徑資訊洩露
- CWE-499:可序列化的類中包含敏感資訊
- CWE-434:危險型別檔案的不加限制上傳
- CWE-772:未釋放超過有效生命週期的資源
4.5. 不安全反射
[ ] 是否有反射操作?
- [ ] 類是否為外部指定? 是否做了白名單校驗?
- [ ] 反射的方法是否為外部指定? 是否做了白名單校驗?
防範的問題:
4.6. 輸出到頁面
[ ] 是否有頁面輸出操作?
- [ ] 是否有外部輸入直接輸出到頁面?是否做了轉義和校驗?
- [ ] 是否有敏感資訊?
防範的問題:
4.7. 輸出到日誌
[ ] 是否有日誌輸出操作?
- [ ] 是否有外部輸入直接寫如日誌? 是否做了轉義處理?
- [ ] 是否有敏感資訊輸出到日誌中? 是否做了匿名化、脫敏處理?
- [ ] 是否將異常資訊直接輸出到日誌中?是否做了脫敏和遮蔽處理?
防範的問題:
4.8. 資料傳輸
[ ] 是否有資料傳輸操作?
- [ ] 傳輸協議是否符合規定?
- [ ] 是否有敏感資訊傳輸?安全防護措施是否符合規定?
防範的問題:
4.9. 安全加密
[ ] 是否有加解密操作?
- [ ] 是否採用了規定的加、解密函式和處理順序?
- [ ] 金鑰、隨機數的獲取是否正確?
- [ ] 是否存在硬編碼金鑰的場景(包括金鑰寫在註釋中)?
防範的問題:
- CWE-327:使用已被攻破或存在風險的密碼學演算法
- CWE-325:缺少必要的密碼學步驟
- CWE-330:使用不充分的隨機數
- CWE-547:使用硬編碼、安全相關的常數
- CWE-259:使用硬編碼的口令
- CWE-321:使用硬編碼的密碼學金鑰
4.10. 許可權控制
[ ] 是否有許可權驗證、設定、變更操作?
- [ ] 驗證步驟是否符合規範?
- [ ] 是否存在邏輯繞過的可能?
防範的問題:
5. 參考
- Mark Schroeder, “A practical guide to object-oriented metrics”, IT Pro, Nov/Dec 1999
- Google的工程實踐文件
- Modern Code Review A Case Study at Google
- Software Engineering at Google
- Code Reviews at Google are lightweight and fast
- How Code Reviews work at Microsoft
- CWE
點選關注,第一時間瞭解華為雲新鮮技術~