概述#

建構和維護網站是一個迭代過程,而非一次性的目標。隨著產品演進和程式碼庫增長,開發者需要不斷新增功能、修復 bug 並重構程式碼。大多數安全漏洞的產生並非因為開發知識不足,而是因為在截止日期壓力下走捷徑、缺乏對細節的注意

本章聚焦於如何遵循 Software Development Life Cycle (SDLC) 來撰寫安全的程式碼。SDLC 是開發團隊在設計新功能、撰寫程式碼、測試和部署變更時所遵循的流程。混亂的 SDLC 會導致無法追蹤執行中的程式碼及其漏洞;而良好的 SDLC 能讓你在流程中及早發現並消除 bug 和漏洞。

SDLC 包含五個階段:

  1. Design and Analysis(設計與分析)
  2. Writing Code(撰寫程式碼)
  3. Pre-Release Testing(發布前測試)
  4. The Release Process(發布流程)
  5. 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(也稱為 stagingpre-productionquality 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 AzureAWS Elastic BeanstalkGoogle App EngineHeroku
  • 優點:最小化停機時間、乾淨的部署、完整的 audit trail
  • 限制:僅支援特定程式語言和作業系統、有限的伺服器設定、不支援複雜的網路佈局

Infrastructure as a Service (IaaS) 和 DevOps#

  • 如果 PaaS 不適用,通常將程式碼部署到獨立伺服器(自架、資料中心或虛擬化伺服器如 Amazon EC2
  • DevOps(developer operations)工具(如 PuppetChefAnsible)模糊了開發和運維的界線
  • DevOps 工具使描述標準部署場景、模組化發布腳本變得容易

Containerization#

  • Containerization 技術(如 Docker)允許建立描述作業系統、磁碟佈局、第三方軟體和 web application 的設定腳本(images
  • Image 部署為 container——抽象底層作業系統功能以實現一致的部署
  • Docker images 可以可重現方式部署到實體或虛擬伺服器
  • Docker SwarmKubernetes 等技術允許在機器可讀的設定檔中描述多伺服器網路配置

The Build Process#

大多數程式碼庫都有 build process,將原始碼準備為可部署的格式:

  • Java 和 C# 等語言在 build 過程中將原始碼編譯為可部署的 binary
  • 使用 package manager 的語言在 build 時下載並驗證第三方程式碼(dependencies
  • Client-side 資源的預處理:TypeScript/CoffeeScript 編譯為 JavaScript
  • JavaScript 的 minify(壓縮)或 obfuscate(混淆)以加快瀏覽器載入速度
  • CSS pre-processors(如 SassSCSS)在 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
  • 第三方服務如 RollbarAirbrake 可快速整合 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 診斷問題
  • 關注第三方程式碼的安全公告,必要時在正常發布週期外部署修補