【編者按】千里之堤毀於蟻穴。服務安全有點類似於機場安檢。工作人員會檢查每一個人,禁止他們帶某些東西上飛機等等,儘管99.9999%的人都不會劫持飛機。而機場採取的所有措施針
2021-07-28 03:13:02
【編者按】千里之堤毀於蟻穴。服務安全有點類似於機場安檢。工作人員會檢查每一個人,禁止他們帶某些東西上飛機等等,儘管99.9999%的人都不會劫持飛機。而機場採取的所有措施針對的都是0.0001%的情況。因為後果影響非常大。
作者 | Madalin Ilie
譯者 | 彎月
軟體的安全問題既困難又複雜。許多人認為安全屬於正常開發流程之外的東西。通常被視為某些安全人員的責任,他們只需要關注安全,不需要理解我們在複雜的微服務、事件驅動、API優先戰略以及雲生態系統中快速交付的業務價值。我認為正是由於這種思想,我們很難在早期階段思考安全問題,找出任何有可能破壞系統的人或事。安全導致複雜的壞境又多了一層複雜。安全不僅僅體現了現代架構的技術複雜性,而且還有很多其他方面的問題,比如快速走向市場、嚴格的期限、團隊內部問題、效能問題、流程太多、會議太多等等。雖然安全非常複雜,但是它並不會在某一天裡突然破壞你的系統。可能在經過數月/數年後,才會有人發現漏洞。那麼,為什麼我們要從第一天起就關注安全呢?話雖沒錯,第一天就出現安全問題的可能性非常小。我們都希望集中精力完成具有直接價值的功能,而不是預防未來可能出現的問題。然而,關鍵在於,安全問題可能會導致整個系統癱瘓。這對於你和你的使用者都非常不利。
那麼,如何在不過度設計安全性和偏執於一切之間取得平衡,同時仍然還能專注於業務價值呢?這其實是一種思維模式,而不是一個單獨的關注點。我並不是說每個人都需要成為安全專家以及無所不知。我是說人們應該開發安全軟體,就像他們開發軟體一樣。通過一種方式將引入漏洞的可能性降到最低。
灌輸這種思維模式的最佳方式是採用一套標準和實踐來培養習慣。繼續拿機場安檢做類比,你不能讓安檢人員自行做決定:
「這個人看上去是好人,他的手提行李可以攜帶剪刀和刀。」
「這位先生看上去嚴重脫水,他可以攜帶一大瓶液體上飛機!」
你需要創建一套平等且適用於每個人的規則和程式(即標準)。此外,你還需要制定一套關於如何處理特定情況的指導方針(即實踐):如果你發現手提行李中有可疑物品,則可以單獨檢查。
接下來,我將詳細介紹涵蓋整個系統發展生命週期的標準和實踐。雖然這個列表無法做到詳盡(對於有些小節的內容,你可能有更多良好的實踐),但我希望能夠幫助你思考一些不太常見的情況。
我簡單地將安全問題分為了兩個主要領域:
基礎設施安全:與應用程式在生產中的部署和運行相關的安全問題。
應用程式安全性:與應用程式的實現以及業務背景細節相關的安全問題。
關於如何解決這兩個問題,有很多資源:
PCI 安全要求(https://www.pcisecuritystandards.org/):側重於財務軟體,但也可作為其他系統的最佳實踐。
安全程式碼(https://safecode.org/category/resource-publications/):有關開發、測試、架構以及開發運維的通用實踐。
有關安全程式設計的十大最佳實踐(https://wiki.sei.cmu.edu/confluence/display/seccode/Top+10+Secure+Coding+Practices):側重於某些程式語言的安全程式設計最佳實踐,也有一些通用的實踐。
OWASP 備忘單系列(https://cheatsheetseries.owasp.org/index.html):覆蓋了大多數軟體開發領域的備忘單。
上述資源非常全面,包括許多解決系統發展生命週期內有關安全問題的細節和實踐。每位開發人員都應該定期做檢查,並掌握最新資訊。但在實踐中,我們很難做到。下面,我將設法總結出我們需要思考的一些重要問題(注意:這些實踐與業務領域無關)。雖然這不是一份詳盡的清單,也不是靈丹妙藥,但它可以幫助你建立堅實的基礎,將發生安全問題的可能性降到最低。
基礎設施的安全比較容易解決,因為很多人都會使用產品或雲服務。這些產品和服務已經包含了非常好的安全功能,如果你使用Web應用程式防火牆,則可以相信該產品能夠保障安全,不需要由你來實現安全。我並不是說這樣做會更容易,而是說你有更多控制。
應用程式的安全則更加難以預測。你需要依靠開發人員的技術來實現安全。你需要確保他們不會做愚蠢的事情,比如在原始檔中儲存明文密碼。
以下是我認為能夠幫助你建立安全思維方式的實踐列表,主要面向常規開發人員。這裡的「常規」指的是實際實現程式碼的人,而不是其他專注於設計、規劃或管理的人。這些規則關注的都是構建 (REST) API 時的應用程式安全。雖然表面看起來它們與安全並沒有直接的聯絡,但最終它們都可以減少引入安全問題的可能性。
注意:本文的大多數示例都使用了Java。
如上所述,標準的使用是建立思維方式的主要機制。所有項目都應該採用同一套標準。並非所有人都喜歡標準,有些人可能會認為標準限制了人們的選擇和創造力。但我認為,這是一種簡單的獲得統一性的方法,尤其是當多個團隊在同一平臺上工作時。建立標準之後,新成員可以更輕鬆地加入項目,而且還能降低引入bug、不一致或在某些愚蠢的事情(比如空格還是製表符)上發生爭執的可能性。我們可以將這些時間節省出來討論更有意義的問題。標準不一定要涉及很多細節,大多數標準應該說明建立在現有實踐之上的原則和選擇。
需要考慮的關鍵事項:
將程式碼的介面以及API合同寫在文件中;
定義文件策略:
整體的文件策略是什麼?
項目的 README.md 檔案中包含什麼?
是否需要更新更廣泛的文件?
採用哪些畫圖工具?
是否使用輕量級架構決策記錄?
文件應當儲存在Git程式碼庫中?還是採用單獨的工具?
如果儲存在項目中,推薦的資料夾結構是什麼?
需要考慮的關鍵事項:
使用藍圖/模板/原型作為所有(微)服務的起點;
將藍圖與所有公共庫、插件等捆綁在一起,並且必須符合標準;
每個(微)服務必須能夠通過運行一個命令來啟動;
(微)服務只能通過API/事件處理資料,沒有後門;
每個(微)服務都是獨立的;
所有(微)服務都符合應用的十二要素,以及其他標準。
選擇一種程式碼風格,並貫徹到底。如果可以,在提交前自動格式化。
選擇一種約定,並貫徹到底。
需要考慮的關鍵事項:
遵循 REST 命名實踐(名詞、複數以及其他常見的標準),網上有很多指南,選擇一種,並貫徹到底;
與命名規則統一。該標準適用於一切,比如負載物件命名、屬性等,而不僅限於端點。無論是駝峰式、蛇式還是烤肉串式,選擇一種,並貫徹到底;
POST、PUT、PATCH的響應包含有意義的主體;
使用有意義的 HTTP 狀態碼,不要遇到錯誤就返回 400;
所有端點必須返回有意義的錯誤情況;
提供錯誤列表(更多細節請參見錯誤處理部分的介紹);
考慮 OpenAPI,以及契約優先開發,即剛開始的時候編寫 OpenAPI 契約,與(內部)消費者溝通,這樣可以實現更好的並行開發;
使用有意義的描述和示例記錄 OpenAPI 契約;
所有(內部)API 必須使用 CorrelationId/TraceId 的標頭;
預設情況下,所有 API 輸入都必須有非常嚴格的限制;
所有 API(內部或外部)都必須經過身份驗證,最好採用實時授權;
所有 API 必須重用相同的公共資料結構:可以採用通用的地址、人、國家等,也可以定義特定於業務的資料結構;
所有 API(內部或外部)只能通過 HTTPS 公開;
考慮在 API 的響應中返回安全標頭,例如:Cache-Control: no-store,Content-Security-Policy: frame-ancestors 'none',Content-Type, Strict-Transport-Security,X-Content-Type-Options: nosniff,X-Frame-Options: DENY等。
內部 API 之間的通訊不應該通過網際網路(除非是架構有意為之,或有此類要求);
不要通過網際網路公開管理端點;如果有這種要求,請使用身份驗證;
確保所有 API 都會嚴格驗證收到的請求:不允許使用未記錄的 JSON 欄位、拒絕格式錯誤的 JSON 等;
正確使用資料類型,不要將所有內容都作為字元串;
儘可能使用列舉值;
定義字元串的長度限制,定義數字的最小值/最大值;
定義每個字元串的輸入限制模式;
某些屬性有明確的定義,因此定義正則表示式很容易,比如國家程式碼一律以「[A-Z]+」開頭。但有些屬性很難定義正則表示式,比如考慮到所有語言中人們的姓氏,這個屬性(lastName)的要求就會非常寬鬆。建議定義一些能夠防止出現奇怪字元的模式,比如 Unicode 控制字元、分隔符或符號;推薦的正則表示式物件如下:「^[^p{C}p{Z}p{So}]*[^p{C}p{so}]+[^p{C}p{Z}p{So}]*$」,但這並不能確保程式可以免受任何類型的注射,你仍然需要很好地理解資料的去向和處理方式,但至少可以確保表情符號不會破壞你的系統。
需要考慮的關鍵事項:
日誌的格式:使用逗號分隔的鍵值對(key=value)?還是使用JSON物件?選擇適合工具的格式;
日誌的每一行都包含 CorrelationId/TraceId,這樣可以方便工具創建儀表板;
日誌中記錄方便理解實際情況的資訊:哪個實體?業務領域?成功了嗎?失敗了?
一些參考做法(https://www.javacodegeeks.com/2011/01/10-tips-proper-application-logging.html);
在實際的日誌實現之上使用抽象層;例如在 Java中,使用由logback實現的slf4j;
將日誌作為橫切關注點,利用面向切面的程式設計,僅在異常情況下記錄日誌,限制敏感內容的記錄;
不能將日誌作為記錄一切備用資訊的手段,將所有請求和響應都完整地記錄下來。應當謹慎地記錄日誌,即使在偵錯級別或較低級別中也應如此。
更多有關日誌記錄的參考資訊(https://ludovicianul.github.io/2021/07/06/incomplete-list-of-security/)。
需要考慮的關鍵事項:
使用現有的 ISO 標準處理廣為人知的物件:貨幣、日期、金額等;
定義可重複使用的特定於業務的物件;
API物件、資料庫實體和事件也需要採用這些標準。
需要考慮的關鍵事項:
在處理資料之前清理資料,下面是一個通用的清理正則表示式:「^[^p{C}p{Z}p{So}]*[^p{C}p{so}]+[^p{C}p{Z}p{So}]*$」,雖然無法杜絕所有的問題,但是可以刪除可能會導致系統崩潰的奇怪字元;
確保不會將輸入資料傳輸到內部的訪問操作,如資料庫查詢、命令列執行等。使用參數化的資料庫查詢,具體指定獲取的內容與傳遞的內容;
當需要針對特定輸入的處理施加限制時,請使用白名單(而不是黑名單);
採用防禦性程式設計實踐;
確保使用不易受到 XXE 或類似攻擊的高效 XML 解析器。最好不要使用 XML 作為輸入,除非上下文有這樣的強制要求。
需要考慮的關鍵事項:
不要記錄敏感資料。如果出於某種原因仍然需要記錄,請遮蔽敏感資料。這裡的敏感程度取決於具體的業務與法規;
創建/使用一個庫,預設遮蔽平臺中最敏感的資料,例如,如果你正在處理付款,則預設遮蔽銀行卡號,不要讓每個開發者自行決定;
考慮在每次新增新的敏感資料時擴展該庫,如果需要新增大量資料,則必須平衡效能;
日誌記錄庫必須允許特定的配置,以便每個單獨的服務可以遮蔽額外的資料,而無需擴展該庫;
日誌記錄庫必須提供清理方法(即通過呼叫特定的方法),確保所有情況都可以使用同一種清理技術;
日誌記錄庫必須在記錄資料之前清理資料(比如刪除所有與「p{Z}p{C}p{So}」相匹配的字元);
日誌記錄庫必須刪除 CR 和 LF 字元,以防止 CRLF 注入;
建立明確的日誌歸檔策略。
需要考慮的關鍵事項:
只能儲存與當前上下文或可預見的將來相關的資料,資料儲存不能作為以備不時之需的手段;
資料的儲存必須遵守相關規定,你必須知道這一點;
某些資料不能明文儲存(比如信用卡號),可以使用硬體或軟體加密;
不能在純文字檔案的版本控制中儲存隱私資料(密碼、加密金鑰、ssh 金鑰、私鑰等),必須使用專用產品或服務,例如 Vault、HSM等;
在執行敏感資料的加密或雜湊操作時,必須「加鹽」和/或「加胡椒」,以防止暴力攻擊;
考慮構建(或使用)令牌化敏感資料的集中式服務;
令牌化所有受某種監管的資料:銀行卡號、PII 資料等。所有(微)服務都應該使用令牌(而不是實際的資料),並僅在需要時還原令牌,這可以最大限度地減少合規所需的工作,還可以更好地控制資料;
加強令牌化解決方案的安全性,不允許從外部訪問其 API。
需要考慮的關鍵事項:
創建一個事件列表,讓每個人都知道每個事件的目的;
使用事件規範進行驗證;
避免使用通用事件來發送一切訊息,避免在不知情的情況下洩露敏感資訊;
考慮用交換令牌代替包含敏感資訊的實際資料。
需要考慮的關鍵事項:
避免在原始檔中硬編碼配置;
考慮使用集中式配置管理;
按環境分隔配置;
不要在原始檔或版本控制中儲存隱私資料(密碼、api 金鑰、ssh 金鑰、私鑰等),使用適當的隱私資料系統;
不要保留任何可部署單元(雲服務、產品或自己的(微)服務)的預設憑據;
不要將僅用於測試的程式碼或配置放入生產;
不要只在服務中構建測試後門;
使用版本控制來記錄配置更改;
使用適當的配置完整性檢查機制。
需要考慮的關鍵事項:
將異常和錯誤視為橫切關注點,利用面向切面的程式設計,使用 ControllerAdvices 等類似的工具;
將常見的異常/錯誤(驗證問題、未找到資源、格式錯誤訊息)處理邏輯放入到共享庫中,這可以減少(微)服務之間的互動摩擦;
編寫一個錯誤列表;
使用錯誤程式碼(例如 MICRO-4221代表結構驗證導致請求出錯,MICRO-4222 代表業務驗證導致請求出錯);
不要在響應中洩漏內部狀態,避免傳遞 e.getMessage(),返回的每個錯誤都必須由其根本原因創建,同時不要洩漏內部資料;
使用 catch-all 機制,以避免因意外異常而洩漏內部狀態。你可以利用全局錯誤處理程式捕獲異常並返回 500;
所有錯誤都應該返回同一個物件,以實現統一的體驗;
在API文件中使用適當的 HTTP 狀態碼記錄所有的錯誤情況。如果使用 OpenAPI,請記錄所有可能的 HTTP 狀態碼,即使它們返回相同的 OpenAPI 物件。
需要考慮的關鍵事項:
使用簡單的分支策略,基於trunk的方式、GitHub工作流等,隨便選一個;
儲存庫和分支採用有意義的命名;
使用描述性提交,以方便將來追蹤變更;
分多次提交少量的程式碼,以更好地分隔變更;
使用智慧提交,即提供指向任務(來自任務管理系統)的連結;
考慮使用預提交鉤子來驗證提交;
提交訊息中不要包含敏感資訊;
儲存庫啟用遠端訪問時需要特別小心,特別是託管在雲中的儲存庫。
需要考慮的關鍵事項:
必須執行程式碼審查(保持善意和自信,提供具體說明,以及其他良好的習慣);
機械的檢查交給工具來做,專心審查功能以及是否符合標準和實踐;
如果你反覆遇到同一個問題,請將其新增到標準中;
建立檢查列表,直到所有人都養成關注同一個問題的習慣。
需要考慮的關鍵事項:
制定引入新工具的流程。進行權衡分析,並推廣到更廣泛的群體,得到所有人的接受/同意,並確保覆蓋更廣泛的情況;
選擇開源軟體時注意許可;
創建許可列表,在其中列出所有無需詢問即可使用的許可、需要討論的許可以及不允許使用的許可;
在發現新工具/庫/產品時,不要著急使用,你需要考慮:是否穩定?是否有良好的維護?是否有成功案例?
考慮使用 OWASP Dependency Check、License Plugin 之類的工具,或其他更復雜的工具,例如 Black Duck等;
制定一個普遍認可的工具/庫創建列表,供所有人挑選;
定期更新依賴項。
需要考慮的關鍵事項:
使用一種或多種工具來分析程式碼;
必須擁有一種(至少)常見的程式碼分析工具,以及一種注重安全實踐的工具;
常見的(Java)程式碼分析工具:Sonarqube、PMD、SpotBugs;
常見的安全程式碼分析工具:Veracode、Checkmarx、Sonarqube;
你不需要遵循這些工具提供的所有標準規則(儘管通常這些標準都與行業建議一致),可以根據實際情況選出適合自己的規則。
需要考慮的關鍵事項:
執行各個級別的自動化測試:單元測試、整合測試、元件測試、API測試、端到端測試等;
除了正面測試之外,還需要關注負面測試以及邊界測試,CATS 是一款優秀的 API 測試工具;
不要忽視失敗的測試,即使是間歇性失敗的測試,其中可能隱藏了嚴重的潛在問題;
測試必須具有彈性,而且必須充分;
測試必須使用類似且可預測的方法;
測試不得依賴於複雜的外部設定,必須能夠通過模擬依賴項、使用記憶體設定來完成測試,或採用測試容器,或僅依賴於已部署的(微)服務。任何其他步驟只會使設定更加複雜化;
考慮在管道內新增一些安全測試;
考慮突變測試。
需要考慮的關鍵事項:
在重要的環節新增質量檢測,並作為檢查點,如果檢測失敗,則構建也會失敗;
質量檢測必須符合這些標準,並自動檢查每個(微)服務;
下面是一個 CI/CD 管道示例:
編譯和構建;
檢查格式;
運行測試並檢查覆蓋率;
運行突變測試;
運行程式碼分析;
運行安全程式碼分析;
檢查第三方庫的漏洞;
檢查第三方庫的許可;
部署;
運行 API 測試;
運行其他類型的測試。
雖然看上去很冗長,但在微服務上運行這些檢查非常快;
編寫自己的流水線指令碼;
不要將流水線與(微)服務融合到一起;
使用適合於所有(微)服務的流水線模板。
需要考慮的關鍵事項:
不要自行構建身份驗證和授權,請使用標準的產品和服務;
所有內部與外部API都需要身份認證,選擇一些可靠的認證服務;
外部和內部呼叫需要使用單獨的身份驗證和授權機制:使用一組憑據/機制來驗證外部呼叫,再使用另一組單獨的憑據/機制來驗證內部呼叫;
憑據始終需要加密,無論是否投入了使用;
所有API(無論內部還是外部)都需要使用 HTTPS;
不要通過 HTTP GET 接收身份驗證憑據,僅使用 HTTP 頭部或 HTTP POST/PUT;
即使在偵錯時也不能記錄憑據,通過日誌記錄的catch all來避免記錄憑據;
確保授權和身份驗證機制允許細粒度的控制和管理,即可以限制每個操作的呼叫次數、撤銷訪問、頒發額外的憑據等;
考慮使用集中式身份提供程式和公共庫;
對於高度敏感的 API/服務使用增強的安全控制(API 的雙向 TLS,服務訪問的 MFA);
使用隨機數來防止重放攻擊;
設計和構建採用最低特權原則。
需要考慮的關鍵事項:
永遠不要使用自己的加密程式,不要在這個空間內重新發明輪子;
使用行業推薦演算法:AES 256、RSA 2048+、SHA-2 512等;
使用 TLS 1.3+ 來保證傳輸安全;
在執行敏感資料的加密或雜湊操作時,必須「加鹽」和/或「加胡椒」,以防止暴力攻擊;
檢查程式語言處理敏感資訊的實踐,例如在 Java 中,在處理密碼、銀行卡號、社會保險號時,必須使用 byte[],不能使用 String。必須儘量減少資料在記憶體中停留的時間並在使用後清除物件。
如上所述,系統生命週期的標準和實踐並不一定會直接關係到安全。質量方面也有這個問題。當前的設計和方法缺陷可能會導致應用程式宕機,即便並不是由真正的安全問題引起的。
關於效能,需要考慮的關鍵事項:
對於昂貴的資源,比如資料庫、API等,應該使用連線池;
使用執行緒池;
使用快取;
操作資料時使用適當的集合;
如果可以,請使用並行程式設計;
確保你瞭解物件關係對映(ORM)如何生成查詢;
避免將大量資源載入到記憶體,請使用資料流;
建立每個(微)服務例項的效能基準,幫助你瞭解何時需要擴展;
定期執行負載測試與效能測試。
關於彈性,需要考慮的關鍵事項:
使用斷路器、重試、超時、速率限制;
當依賴的 API 不可用時有明確的回退策略;
關於彈性的資源:
https://engineering.grab.com/designing-resilient-systems-part-1
https://engineering.grab.com/designing-resilient-systems-part-2
所有 API 都必須具備冪等性;
不要在(微)服務例項中儲存狀態,可以考慮使用分散式快取。
關於可用性和可擴展性,需要考慮的關鍵事項:
設計不應當限制(微)服務的水平擴展;
做好故障計劃,建立自動化的機制,根據負載水平自動伸縮;
考慮分片,只讀副本;
使用多區域部署。
關於可觀察性和監控,需要考慮的關鍵事項:
所有(微)服務都必須公開有關應用程式以及底層容器的健康端點;
健康端點必須返回有關其所有依賴項的資訊:資料庫、加密服務、連線的 API、事件匯流排等。
利用標準化日誌創建有意義的操作儀表板。
一切都應儘可能實現自動化。自動化可以確保預測性和一致性。應該利用 CI/CD 流水線自動化所有檢查,這些檢查將從質量的角度評估(微)服務。對於不適合自動化的標準,可以考慮 Semgrep 等工具。
在本文中,我儘可能地列出了方方面面的實踐建議。雖然這不是一份詳盡的攻略,但可以作為建立安全思維模式的起點。在貫徹完成這些實踐後,你可以進一步深入的研究。這些實踐不僅可以確保系統安全,而且還具備結構化與統一性。在快速開發系統(無論是開發全新的系統還是改造遺留系統)時,這些實踐尤為重要。你不需要從第一天開始就貫徹所有實踐,特別是過多的通用標準會限制你的選擇。你可以逐步嘗試,然後看看效果。
原文連結:https://ludovicianul.github.io/2021/07/06/incomplete-list-of-security/
聲明:本文由CSDN翻譯,轉載請註明來源。
榮耀份額重回中國市場前三;特斯拉使用替代晶片重寫汽車軟體;RabbitMQ 3.9.0 釋出|極客日報
程式設計師加入新團隊必問的20道問題
只因少打一個字元 「&」,大量谷歌 Chromebook 無法解鎖
相關文章
【編者按】千里之堤毀於蟻穴。服務安全有點類似於機場安檢。工作人員會檢查每一個人,禁止他們帶某些東西上飛機等等,儘管99.9999%的人都不會劫持飛機。而機場採取的所有措施針
2021-07-28 03:13:02
前言最近一部根據真實事件改編的《中國醫生》正在火熱上映,感動了無數觀眾,更獲鍾南山院士高度評價:「真正體現了中國醫生的良心、責任、決心、行動!」影片以金銀潭醫院為核心故
2021-07-28 03:12:43
遵循一些低程式碼應用程式開發的優秀實踐,企業可以更快地構思、原型化以及創建Web或移動應用程式,並避免在開發過程的後期出現代價高昂的錯誤。調研機構指出,低程式碼是軟體開
2021-07-28 03:12:39
在AMD窮追不捨的攻勢面前,英特爾可以說是節節敗退,幾乎沒有招架之力。哪怕你架構再先進,14nm也打不過7nm。英特爾這幾年工藝製程發展得慢,關鍵是因為英特爾不對外代工,錯過了智慧
2021-07-28 03:12:29
iPhone 13 很快就要釋出了,為確保 iPhone 13滿足消費者,A15 Bionic於 5 月開始量產。與 iPhone 12 系列相比,蘋果預計即將推出的機型需求會更高,蘋果已要求供應商將產量提高 20%
2021-07-28 03:12:23
要是問選擇一款手機你最在意什麼呢?你會如何回答呢?其實就小編這樣的一個數碼愛好者來說,我可能會在意使用者的評價。因為選擇一款手機對於自己來說,更多的還是主觀上的需求,但是
2021-07-28 03:12:15