概述#
建構和維護網站是一個迭代過程,而非一次性的目標。隨著產品演進和程式碼庫增長,開發者需要不斷新增功能、修復 bug 並重構程式碼。大多數安全漏洞的產生並非因為開發知識不足,而是因為在截止日期壓力下走捷徑、缺乏對細節的注意。
本章聚焦於如何遵循 Software Development Life Cycle (SDLC) 來撰寫安全的程式碼。SDLC 是開發團隊在設計新功能、撰寫程式碼、測試和部署變更時所遵循的流程。混亂的 SDLC 會導致無法追蹤執行中的程式碼及其漏洞;而良好的 SDLC 能讓你在流程中及早發現並消除 bug 和漏洞。
SDLC 包含五個階段:
- Design and Analysis(設計與分析)
- Writing Code(撰寫程式碼)
- Pre-Release Testing(發布前測試)
- The Release Process(發布流程)
- Post-Release Testing and Observation(發布後測試與觀察)
Phase 1: Design and Analysis#
SDLC 不是從撰寫程式碼開始,而是從思考應該撰寫什麼程式碼開始。
需求識別#
- 分析需要新增的功能並設計實作方式
- 專案初期可能只需簡要的設計目標概述
- 網站上線後,需要更謹慎地考慮變更,避免破壞現有使用者的功能
- 最重要的目標是識別需求——開發完成後,所有人都應能判斷新程式碼是否正確達成需求
Issue-Tracking Software#
Issue-tracking software(也稱為 bug trackers)對設計和分析階段有極大幫助:
- 將開發目標描述為個別的 issues(如「建構客戶結帳頁面」或「修正首頁的拼字錯誤」)
- Issues 分配給個別開發者,可按優先級排序、標記為完成
- 開發者可將特定的程式碼變更與 issue 連結
- 大型團隊可使用專案管理軟體來排程 issues
撰寫程式碼前花在紙上規劃的時間因情況而異。撰寫韌體或關鍵系統(如核子反應爐)的團隊會花大量時間在設計階段,因為部署後很少有修正機會。Web 開發者則傾向於更快速地行動。
Phase 2: Writing Code#
Source Control#
完成設計和分析後,進入撰寫程式碼階段。所有非一次性腳本的程式碼都應保存在 source control software(版本控制)中:
- 儲存程式碼庫的備份副本
- 瀏覽程式碼庫的歷史版本
- 追蹤並註釋程式碼變更
- 透過 push 將變更分享給團隊
- Release 意味著將變更部署到 production 網站
Source control 是所有開發團隊(即使只有一個人)都需要的首要工具。它讓你能瀏覽目前在 production 上運行的程式碼版本,這對於診斷和修復發布後發現的安全問題至關重要。
Distributed vs. Centralized Version Control#
- Git 是目前最流行的版本控制工具,由 Linus Torvalds 創建
- Git 是 distributed version control system:每份程式碼副本都是完整的 repository,包含完整的變更歷史
- 與 centralized 系統不同——集中式系統從中央伺服器下載和上傳完整檔案
- GitHub 讓設定線上 Git repository 變得簡單,並提供 issue tracker 和程式碼管理工具
Branching and Merging Code#
- Branch 是程式碼庫的邏輯副本,開發者可在自己的 branch 上進行變更而不影響 master codebase
- 完成功能或修復後,將 branch merge 回 master codebase
- 多人同時編輯相同檔案時可能產生 merge conflict,需要手動解決
Code Reviews#
Merge 時是進行 code reviews 的絕佳機會:
- 一位或多位團隊成員審閱程式碼變更並提供回饋
- Four eyes principle(四眼原則):每個程式碼變更在發布前需要兩個人看過
- 新鮮的視角常能發現原作者未預料到的問題
- Git 工具透過 pull request 來正式化 code review 流程
Pull request 是開發者請求將程式碼 merge 到 master codebase 的機制。GitHub 等工具可以要求其他開發者在 merge 前批准變更,且常將 pull request 的批准與所有測試通過的 continuous integration 結果掛鉤。
Phase 3: Pre-Release Testing#
SDLC 的第三階段是測試。只有在徹底測試、確認程式碼正確且無潛在 bug 後才應發布程式碼。
Unit Tests#
- Unit tests 是程式碼庫內的小型腳本,對程式碼行為做出基本斷言
- 應作為 build 過程的一部分執行
- 為特別敏感或頻繁變更的程式碼撰寫 unit tests
撰寫良好 unit tests 的原則:
- 保持簡單,測試程式碼的隔離功能
- 過度複雜的 unit tests 容易脆弱 (brittle),程式碼變更時容易斷裂
- 好的 unit test 也是一種文件,展示程式碼應如何正確運作
發現 bug 時的好規則:先撰寫一個斷言正確行為的 unit test,然後再修復 bug。這能防止問題再次發生。
Coverage and Continuous Integration#
- Coverage:執行所有 unit tests 時涵蓋的程式碼百分比
- 追求 100% coverage 雖然值得讚賞但通常不切實際——完整的 coverage 也不保證程式碼正確
- Continuous integration server 連接到 source control repository,每次程式碼變更時自動 checkout、build 並執行 unit tests
- 如果 build 失敗或 unit tests 不通過,開發團隊會收到警報
Test Environments#
- Test environment(也稱為 staging、pre-production 或 quality assurance environment)應是網站的完整運作副本
- 在專用伺服器上運行,用於在發布前偵測軟體缺陷和安全漏洞
- 應盡可能模擬 production 環境(相同的伺服器和資料庫技術)
Test 和 production 環境需要在網路層面適當隔離 (segregated)。不能讓攻擊者從不安全的 test 環境跳入 production 環境。
Test 環境通常有自己的資料庫,常從 production 複製資料來產生逼真的測試資料。如果這樣做,務必 scrub(清洗)敏感資訊——包括姓名、付款資料和密碼。近年來多起重大資料洩漏事件就是因為 test 環境中的資料清洗不徹底所致。
Phase 4: The Release Process#
Release process 涉及從 source control 取出程式碼、複製到 web server、並(通常)重啟 web server 程序。
發布流程的三個關鍵特性#
- Reliable(可靠):能保證部署的程式碼、dependencies、資源和設定檔正確無誤。發布腳本通常使用 checksums(數位「指紋」)確保伺服器上的檔案與 source control 中的一致
- Reproducible(可重現):可以在不同環境、不同版本的程式碼上重複執行,減少人為錯誤的空間。應盡可能自動化
- Revertible(可回復):能夠 roll back(回退)到之前的版本。部分回退的程式碼可能留下不安全的設定或已知漏洞的 dependencies
Options for Standardized Deployment#
Platform as a Service (PaaS)#
- PaaS 讓開發者用單一命令列呼叫就能發布程式碼
- 平台處理虛擬化伺服器設置、作業系統安裝、dependencies 載入、程式碼部署等一切事務
- 常見的 PaaS 提供者:Microsoft Azure、AWS Elastic Beanstalk、Google App Engine、Heroku
- 優點:最小化停機時間、乾淨的部署、完整的 audit trail
- 限制:僅支援特定程式語言和作業系統、有限的伺服器設定、不支援複雜的網路佈局
Infrastructure as a Service (IaaS) 和 DevOps#
- 如果 PaaS 不適用,通常將程式碼部署到獨立伺服器(自架、資料中心或虛擬化伺服器如 Amazon EC2)
- DevOps(developer operations)工具(如 Puppet、Chef、Ansible)模糊了開發和運維的界線
- DevOps 工具使描述標準部署場景、模組化發布腳本變得容易
Containerization#
- Containerization 技術(如 Docker)允許建立描述作業系統、磁碟佈局、第三方軟體和 web application 的設定腳本(images)
- Image 部署為 container——抽象底層作業系統功能以實現一致的部署
- Docker images 可以可重現方式部署到實體或虛擬伺服器
- Docker Swarm 和 Kubernetes 等技術允許在機器可讀的設定檔中描述多伺服器網路配置
The Build Process#
大多數程式碼庫都有 build process,將原始碼準備為可部署的格式:
- Java 和 C# 等語言在 build 過程中將原始碼編譯為可部署的 binary
- 使用 package manager 的語言在 build 時下載並驗證第三方程式碼(dependencies)
- Client-side 資源的預處理:TypeScript/CoffeeScript 編譯為 JavaScript
- JavaScript 的 minify(壓縮)或 obfuscate(混淆)以加快瀏覽器載入速度
- CSS pre-processors(如 Sass、SCSS)在 build 時預處理為 CSS 檔案
應在本地 build 後再 check in 到 source control,確保 build 過程正常。使用 continuous integration server 來確保這一點。
Database Migration Scripts#
新增功能常需要新的 database tables 或更新現有 tables:
- 不能每次發布都清除重建資料庫,因為資料需要在版本間保留
- 使用 migration scripts 在發布過程中更新資料庫結構
- Migration scripts 應是可逆的——回退程式碼時也要能 undo 資料庫變更
- 某些技術(如 Ruby on Rails)允許在 build 過程中執行 migration scripts
- 大型公司常有專門的 database administrators (DBAs) 管理此流程
如果員工能在正式發布流程之外更改資料庫結構,這是一個安全風險。應鎖定資料庫權限(詳見 Chapter 11)。
Phase 5: Post-Release Testing and Observation#
部署程式碼後,應進行 post-release testing(也稱為 smoke testing)以確認部署正確且程式碼在 production 中如預期運作。
Penetration Testing#
- Penetration testing 透過外部探測網站來測試安全漏洞
- 可用於 pre-release 和 post-release 測試
- 自動化 penetration testing 工具可分析 URL 並嘗試構造惡意 HTTP 請求
- 費用雖高但遠比被入侵便宜
強烈建議將 penetration testing 加入你的測試流程。
Monitoring, Logging, and Error Reporting#
Production 環境需要在執行時可觀察,以幫助管理員發現異常和潛在的惡意行為。
Logging#
- 將每個 HTTP request(包含 timestamp、URL、HTTP response code)記錄到日誌檔
- 記錄使用者的重要操作(如認證、密碼重設、發送 email、呼叫 API)
- 日誌應可供管理員在 command line 或 web console 存取,並存檔備查
注意不要在日誌中記錄敏感資訊(如密碼和信用卡資訊),以防攻擊者取得日誌存取權。
Monitoring#
- 測量 response time 和其他 runtime 指標
- 當網路速度變慢或資料庫查詢耗時過長時發出警報
- 許多 cloud platform 內建 monitoring 功能
Error Reporting#
- 捕獲並記錄程式碼中的非預期錯誤
- 從 log 中提取或在程式碼中直接記錄 error conditions
- 將 error conditions 彙總到管理員可存取的 datastore
- 第三方服務如 Rollbar 和 Airbrake 可快速整合 error reporting
- Log-scraping 工具如 Splunk 可從日誌檔中提取和分析錯誤
許多安全入侵都是利用處理不當的 error conditions。務必注意非預期的錯誤。
Dependency Management#
現代 web 開發中,你自己撰寫的程式碼可能只佔網站運行程式碼的一小部分。你的網站通常依賴:
- 作業系統程式碼
- Programming language runtime 和相關程式庫
- 可能的虛擬機
- Web server 執行的第三方程式庫
這些統稱為 dependencies。
使用他人的程式碼需要盡職調查。安全的 SDLC 應包含審查第三方程式庫和決定何時套用修補的流程。這通常需要在正常開發週期之外進行,因為駭客不會等到你的下一個排程發布日才開始嘗試利用安全漏洞。關注安全公告並及時部署修補與保護自己撰寫的程式碼同樣重要。
總結#
- 良好的 SDLC 能避免 bug 和安全漏洞
- 使用 issue-tracking software 記錄設計目標
- 將程式碼保存在 source control 中,方便檢查歷史版本和組織 code reviews
- 在發布前於獨立且隔離的 test environment 中測試,並謹慎處理測試資料
- 發布流程應可靠、可重現、可回復;使用腳本化的 build process 和 continuous integration 及早發現問題
- 發布後使用 penetration testing 偵測漏洞,使用 monitoring、logging 和 error reporting 診斷問題
- 關注第三方程式碼的安全公告,必要時在正常發布週期外部署修補