「我自己的經驗是:擁有一套乾淨、有表達力、具體的安全需求的開發者,能造出非常緊實的機器。他們不必是安全大師,但必須理解自己想造什麼、以及它該如何運作。」——Rick Smith
「談到當時尚的奴隸,美國的經理人讓青春期少女看起來像硬派的個人主義者。」——Geoff Nunberg
「狐狸知道很多事;刺蝟知道一件大事。」——阿基羅庫斯
沒有銀彈#
如果你是個受雇打造或維護「有安全保證需求」之系統的工程師、經理或顧問,讀到這裡你會想要一套系統化的做法。這帶我們到風險分析、系統工程方法論,以及最後的秘方:你要如何管理一個團隊寫出安全的程式碼。
許多人宣稱有一個,並對當下的熱情懷抱宗教般的狂熱,從 1980 年代的橘皮書到現在的敏捷開發。但關於這件事的第一個說法就是對的。
1960 年代 Fred Brooks 領導了世界上第一個真正大型的軟體專案——IBM S/360 主機的作業系統。在其經典著作《人月神話》中,他描述了他們掙扎過的所有問題,而他的結論是「沒有銀彈」。沒有魔法公式能讓一件本質上困難的工作變容易。
還有本章開頭阿基羅庫斯的名言:狐狸知道很多事,而刺蝟知道一件大事。管理安全開發是狐狸的知識,而非刺蝟的知識。
一位有經驗的安全工程管理者必須知道成千上百件小事;這就是為什麼這本書這麼厚!而隨著軟體無所不在並開始與安全性(safety)交互作用,安全工程管理者的工作只會越來越難。
安全性與資安都是湧現性質,真的必須從一開始就烤進去。兩者都涉及系統性地思考「什麼可能出錯」,不論是出於意外還是惡意。意外能讓系統暴露於攻擊,而攻擊能讓系統退化到變得危險。
你必須問的兩個問題是:「我們在造對的系統嗎?」與「我們把它造對了嗎?」
風險管理#
安全性工程與資安工程的核心都是關於優先順序的決定:在什麼上花多少錢做保護。
新冠危機該讓所有人都明白:雖然大流行病位居包括英國在內許多國家風險登記簿之首,多數政府卻把韌性預算中大得多的部分花在名單上更後面幾位的恐怖主義上。
有近期 SARS 或 MERS 經驗的國家(如台灣與南韓)做得較好:他們準備好大規模檢驗居民與追蹤接觸者,並迅速回應。英國浪費了兩個月才意識到這個疾病很嚴重,代價是數萬條人命。
一種常見方法(作者所在大學的理事會就這麼用)是列出可能出錯的事,給每一項「嚴重性」與「發生機率」各 1 到 5 分,相乘得到 1 到 25 之間的數字。
例如若 20% 的收入來自研究合約,一所大學可能把「因經濟下行而損失研究合約收入」的嚴重性評為 5/5,機率評為 4/5,原始乘積為 20。你接著寫下你為緩解每項風險所採取的措施,並在風險委員會裡爭論每項風險被緩解得多好。
國家風險評估類似:你依「可能殺死多少人」為每個可能的壞事件評分,再依「每世紀預期發生幾次」評機率。英國國家風險登記簿把大流行性流感排在首位,嚴重性 5(可能殺死多達 75 萬人)、可能性 4。
英國大致忽視了大流行病,因為國家安全會議被安全與情報機關擄獲了;他們優先處理恐怖主義,而衛生大臣並非常態出席者。
失敗的另一面是政策過度。 當 911 教會世界「恐攻能殺死數千而非數十人」、而機關拿到韌性預算中大得多的一份後,這讓他們貪心了:他們開始渲染「恐怖分子弄到核彈」的風險,好在登記簿上有個更嚇人的威脅來正當化其預算。
年度損失期望值、產品風險與保險
標準方法是為每個可能的損失情境計算「年度損失期望值(ALE)」,即預期損失乘以平均一年預期的事件數。一家銀行 IT 系統的典型 ALE 分析可能有數百個項目:
損失類型 金額 發生率 ALE SWIFT 詐欺 $50,000,000 .005 $250,000 ATM 詐欺(大) $250,000 .2 $100,000 ATM 詐欺(小) $20,000 .5 $10,000 櫃員拿走現金 $3,240 200 $648,000 注意:常見損失(如「櫃員拿走現金」)很可能有準確數字,但大額匯款詐欺這類「低機率高風險」損失的發生率,大致是猜的。
顧問列出他們能想到的所有威脅,附上臆想的機率,算出 ALE 加總起來,然後發現銀行的 ALE 超過了它的收入。他們接著把總數調整成「能正當化其客戶資安長所說之政治上可能的最大安全預算」的數字。
重點是:ALE 可能有些價值,但你得理解哪些部分基於資料、哪些基於猜測、哪些基於辦公室政治。
你的保費過去能給出「你的事業所承擔風險」的某種訊號。但保險是循環性產業,自 2017 年左右以來,一大批新公司開始提供網路犯罪保險,把市場利潤擠掉了。結果客戶不再願意忍受侵入性的問卷,遑論評估員的實地拜訪。
撇開精算風險不談,大公司投保(以及許多其他企業行為)的一個非常重要理由,是保護高階主管而非股東。
被處理的風險表面看似營運性的,實際上是法律、法規與公關風險。 董事要求責任險;而依英美法,「專業過失」發生在專業人士未能達到其行業中「合理稱職之人」所要求的水準。所以過失索賠是按產業或專業的當前標準評判的,這給了「隨大流」的強烈誘因。
這是為什麼管理是如此追逐時尚的一門生意。 這也滲入了用來正當化安全預算的話語:1980 年代中期人人都在談駭客;從 80 年代末起病毒接管了企業的想像,人們靠賣防毒軟體致富;1990 年代中期防火牆成了明星產品;90 年代末是 PKI 的狂熱;到 2017 年是區塊鏈。
在這一片喧鬧中,安全專業人員必須保持頭腦清醒,努力理解真正的威脅是什麼。
從安全關鍵系統學到的教訓#
關鍵電腦系統是那些「某類失效必須盡可能避免」的系統。 依失效類別而定,它們可能是安全關鍵、業務關鍵、資安關鍵或環境關鍵的。
慣常程序是:辨識危害並評估風險;決定應對策略(避免、約束、冗餘……);把危害追溯到硬體與軟體元件,那些元件因而被辨識為關鍵;辨識同樣關鍵的操作程序;訂出「安全功能需求」(指明安全機制必須做什麼)與「安全完整性需求」(指明安全功能被令人滿意地執行的可能性);最後決定測試計畫。
測試的成果不只是一個你有信心上線運行的系統,也是「安全案例(safety case)」的一部分。
危害分析與消除#
考慮馬達反轉電路的例子。 在原始設計中,一個雙刀雙擲開關把電池經過馬達的電流反轉。然而這有潛在問題:若兩極中只有一極動了,電池就會短路,可能造成火災。
解法是交換電池與馬達的位置。 在修改過的電路中,開關故障只會讓馬達短路,而不是電池。安全性工程不只關於正確運作,也關於正確地失效。
我們在 SWIFT 的早期設計中看過一個例子:用來認證銀行間交易的金鑰是銀行彼此直接交換的,所以 SWIFT 沒有偽造有效交易的手段,其員工與系統因而不必被那麼信任。
一般而言,最小化可信計算基礎就是一項危害消除的練習。
同樣的道理在隱私工程中也適用。 例如若你在設計一個「監測流行病中誰可能感染了誰」的接觸追蹤 app,一種做法是有一個「每個人手機位置歷史」的中央資料庫。然而那有明顯的隱私危害,而這能靠「在每個人手機上保存藍牙接觸歷史,並只在有人通報生病時才上傳其接觸歷史」來降低。
故障樹、威脅樹、FMEA 與威脅建模
一種常見的、由上而下辨識「可能出錯之事」的方法是「故障樹分析」,建構一棵樹,其根是不想要的行為,而後續節點是其可能成因。
威脅樹被美國國防部使用。 你從每個不想要的結果出發,往回寫下每個可能的直接成因,再遞歸地加上每個前置條件。繞著樹的葉子走一圈,你就該能看到每一種「能打破你安全政策」的技術攻擊、作業失誤、實體滲透等的組合。
另一個好處是「攻擊路徑之間共通性」的視覺化,這讓你更容易推理「用最少力氣瓦解最多攻擊」。威脅樹可能相當於系統的攻擊手冊,所以可能被高度機密化。
在安全關鍵的世界中,另一種危害分析方法是 NASA 首創的「失效模式與效應分析(FMEA)」,它是由下而上而非由上而下的。
例如若你要飛越「引擎故障時無法滑翔到機場」的海洋或山區,引擎動力就是關鍵的。你於是研究其平均故障間隔時間與失效模式,從連桿斷裂到燃料用盡。
助推火箭中的 O 型環被 NASA 專案經理知道是個風險,先前的飛行中也發現過損傷;同時承包商知道低溫會增加風險;但這些關切並未匯聚起來、也未上達 NASA 高層管理。一個因低溫而變脆的 O 型環失效了,導致太空梭與七名機組員的損失。
物理學家費曼在調查委員會上著名地在電視上演示了這點:他把一塊 O 型環樣本夾在夾具中、放進冰水冷凍,然後展示在他鬆開時它仍然凹陷、沒有彈回來。
這說明:失效往往不只是技術性的,也涉及人們在組織中如何行事。當保護機制跨越制度邊界時(例如汽車),你就得同時思考法律與經濟,而不只是工程。
威脅建模:
微軟在 2003 年為讓 Windows 與 Office 更安全而大力推動之後所採用的方法論,不是純粹由上而下或由下而上,而是一種「中間會合」的取徑。
基本想法是:你不只列出你想保護的資產(做交易的能力、對機密資料的存取權等等),也列出攻擊者可用的資產(也許是訂閱你系統的能力、操縱你所供應智慧卡之輸入的能力、或在你客服中心找到工作)。
你接著追蹤穿過系統、從一個模組到另一個模組的攻擊路徑。你試圖弄清楚信任層級可能是什麼;屏障在哪裡;以及偽冒、竄改、否認、資訊揭露、服務阻斷與權限提升等技術可能被用來克服哪些屏障。
一種基本做法是建構一個「危害對安全機制」的矩陣;若安全政策是「每項嚴重危害必須被至少兩個獨立機制約束」,那你就能檢查每個相關欄位是否有兩個項目。於是你能圖形化地展示:在該危害存在的情況下,至少要兩次失效才會造成事故。
量化風險#
當任務簡單、常被執行、且有強烈的成功線索時,錯誤率可能是十萬分之一。然而當任務是在一個需要邏輯思考的混亂環境中首次執行、而操作者又承受壓力時,成功完成的機率可能很低。
三哩島與車諾比教會了核工程師:不論你做多少次設計走查,真正的紅燈亮起時才是最糟錯誤發生的時候。同樣的教訓一次又一次從空難調查中浮現。當幾十個警報同時響起時,有相當機會有人會按錯按鈕。
一項指導原則是「預設為安全狀態」:抑制核反應、讓飛機回到平直飛行、或把自駕車停到路邊。沒有原則是萬無一失的,而安全狀態也可能難以衡量。
在過去,每一路訊號送到單一儀表或錶盤,而擺放它們的空間有限。如今的誘惑是把一切都給操作者,因為你做得到。
過去設計者知道緊急狀況會給飛行員造成隧道視野,所以他們把真正需要的六個儀表放在正中央。如今可能有五十個警報而非兩個,而飛行員得掙扎著弄清楚要看電子飛行資訊系統哪一層選單的哪一個畫面。
這遠不只限於航空。 一個海軍的例子是 2017 年 USS McCain 在新加坡海峽的碰撞,其中使用者介面的混淆是主要因素:操舵控制被切換到錯誤的舵手站、一具引擎未及時減速,導致未受指令地左轉橫越繁忙航道、與一艘化學品油輪相撞,十名水兵喪生。
安全可用性不只是給終端使用者一個漂亮直覺的介面。它該以「符合關於威脅與保護之常見心智模型」的方式呈現風險,而使用者在壓力下的可能反應該導向安全的結果。
飛機設計者能倚賴任何持商用飛行執照者有可預期的技能水準。汽車則可以且確實會被年老體弱、年輕沒經驗、被乘客分心、或酒後的駕駛操作。
在專業這一端,可用性測試能與員工訓練有益地整合:飛行員去模擬機上複訓時,教官會扔給他們各種設備失效、惡劣天氣、客艙危機與航管混亂的組合。他們觀察哪些壓力組合會導致致命事故、以及這在不同駕駛艙型號間有何不同。這類資料是給駕駛艙設計者的寶貴回饋。
有數起空難源於「在某個駕駛艙系統失效的情況下駕駛客機」;雖然飛行員在理智上知道某路送到駕駛艙顯示器的資料不可靠,他們在壓力下仍可能出於反射倚賴它,而不去與其他儀表核對。
安全性處理隨機失效的效應,而在資安中我們假設一個敵對的對手,他能讓我們系統的某些元件在最不方便的時刻、以最具破壞性的方式失效。
人們在有對手存在時自然更加規避風險。 一位安全性工程師會認證一套 MTBF 為 10^9 小時的關鍵飛控系統;一位資安工程師必須擔心:對手能否強制製造出那個「十億分之一失效」的前提條件,從而隨時把飛機弄墜毀。
實際上,我們的任務是為一台「在最不方便的時刻給出微妙且惡意錯誤答案」的電腦寫程式。作者曾把這描述為「為撒旦的電腦寫程式」,以與更常見的「為墨菲的電腦寫程式」區分開來。這是安全工程之所以困難的理由之一:撒旦的電腦更難測試。
優先排序保護目標#
若你的公司營業額一千萬美元、毛利一百萬、竊盜損失 15 萬,你也許會提出一個「如何靠阻止竊盜把利潤提高 15%」的損失削減提案;但如果你能把營業額翻倍到兩千萬,股東會偏好那個——即使那讓損失變成三倍的 45 萬。利潤現在是 155 萬,上升 85%,而非 15%。
線上詐欺引擎的經驗證實了這點。 作者與若干零售商討論詐欺管理策略時注意到:成果最好的公司,是那些詐欺管理團隊向「業務」而非「財務」匯報的公司。
英國一家典型的實體+線上零售商,可能因詐欺引擎對商品、送貨地址與付款細節的組合示警,而拒絕約 4% 的購物車。所以若你能改善詐欺引擎、只拒絕 3%,那就是多 1% 的銷售——一個能點亮行銷長雙眼的前景。但若詐欺團隊改為向財務長匯報,他們就很可能被看成成本而非機會。
若本地網際網路可用性只有 99%,那麼一項 99.9% 上線的服務就夠好了;再花數百萬達到 99.99% 毫無意義,因為沒有使用者會察覺差別。
你更好的做法是刻意設定 0.1% 的錯誤預算,並把它拿來做有生產力的事——例如靠偶爾的刻意失效來演練你的韌性機制。
許多現代商業模式曾被認為太冒險,從自助超市本身開始——在那個雜貨商把所有商品放在櫃檯後面的年代。當 Richard Sears 在 1880 年代採用「保證滿意否則退款」的口號時,人人都認為他會破產,然而他發明了現代郵購生意。在商業上,利潤是承擔風險的報酬。
邏輯上,已開發國家中一條人命的價值也許是幾百萬美元(那是一般人一生的收入)。然而我們的安全行為所揭示的實際估值,從道路交叉口改善的約 5 萬美元,到列車防護系統的超過 5 億美元不等——而那只是在交通政策的脈絡中。
健康政策的變異更大,每救一命的成本從流感疫苗與某些癌症篩檢的數百美元,到最無效介入的數十億美元。
安全性偏好能被敵意行動的威脅非常急遽地改變:人們可能對「設計不良之醫療裝置害死自己」的萬分之一風險聳聳肩,直到有可能這些裝置被駭——此時連千萬分之一的風險都變得可怕。
方法論#
軟體專案通常比計畫久、比預算貴、bug 比預期多。 到 1960 年代,這已被稱為「軟體危機」。
1960 年代末,人們希望能把「大型軟體專案失敗」的比例從當時觀察到的 30% 左右降下來。但我們仍看到約 30% 的大型專案失敗——差別在於失敗的規模大得多。
現代工具讓我們在掉下複雜度之山前爬得更高,但失敗率是由公司管理者的風險胃口設定的。
- 偶然複雜度:使用不合適的工具寫程式所涉及的複雜度(例如早期機器只支援組合語言)。它主要靠技術工具處理,最重要的是「隱藏了處理機器特定細節之苦工」的高階語言。它們也帶來自己的成本:許多漏洞是 C 語言性質的結果,而若重跑一次歷史,我們肯定會改用 Rust 之類的東西。
- 內在複雜度:處理龐大複雜問題本身的複雜度。一家銀行的核心系統可能涉及數千萬行程式碼,實作了經由數個交付通道販售的數百項產品,而那對任何一個人來說都太多了。
內在複雜度需要方法論:幫我們把問題切成可管理的子問題、並限制這些子問題互動程度的方法論。基本上有兩種取徑——由上而下與迭代式。
瀑布模型、螺旋模型與敏捷
系統開發的古典模型是 1960 年代由 Win Royce 為美國空軍形式化的「瀑布模型」。
從 1970 年代到 2000 年代中期,這是美國國防部所有系統應該被開發的方式,而全球許多政府跟隨其領導(不只在國防,也在行政與醫療)。
需求以使用者語言寫成,規格以技術語言寫成,單元測試對照規格檢查各單元,而系統測試檢查需求是否被滿足。前兩步有「我們在造對的系統嗎」的回饋(驗證,validation),後兩步有「我們造對了嗎」的回饋(查核,verification)。
瀑布模型的定義特徵是:開發不可阻擋地從需求的第一次陳述向下流到系統在現場的部署。雖然每一階段對其前一階段有回饋,卻沒有從(例如)系統測試到需求的系統層級回饋。
在能讓它奏效的地方,它往往是最好的取徑。關鍵問題是:需求在任何開發或原型工作之前,是否已詳細已知?
有時確實如此,例如寫一個編譯器、或設計一個實作已知交易集的密碼處理器。有時由上而下的取徑因外部理由而必要,例如行星際太空探測器——你只有一次機會。
技術可能在變;環境可能在變;或專案的關鍵部分可能是人機介面的設計,那很可能涉及測試數個原型。設計者最重要的工作往往是幫助客戶決定他們想要什麼。
有時正式的專案就是太慢。 Reginald Jones 把英國在二戰電子戰中的相對成功,很大程度歸因於英國科學家迅速把東西湊在一起,而德國人用僵化的由上而下開發方法論,得到工程精美的設備,卻總是晚六個月。
但使用迭代式開發最常見的理由,是我們從一個既有產品出發、想改進它。 即使在計算的早期,多數程式設計師的努力也總是花在維護與強化既有程式上,而非開發新程式;調查顯示成功 IT 產品總擁有成本的 70–80% 發生在它首次上線之後。
如今,隨著軟體成為嵌入式程式碼、app 與雲端服務的事情(而這些都越來越複雜),許多公司的現實是「維護就是產品」。
迭代式設計:從螺旋到敏捷。
有許多應用中,一個初始原型是關鍵的第一步:從一家想做展示給投資人看的新創、到一家為焦點團體做新產品模型的公司,再到 DARPA 那些「旨在確立某項提議技術並非完全不可能」的種子專案。
第二種情況我們如今稱為敏捷開發,可以用一句口號總結:「解決你最糟的問題。重複。」
核心技術是回歸測試。 在固定間隔(典型是每天一次),所有在產品不同功能上工作的團隊都簽入其程式碼,被編譯成一個 build,然後對一大組輸入自動測試。回歸測試檢查「原本能用的東西是否還能用」,以及「舊 bug 有沒有跑回來」。
總是有可能某人的程式碼弄壞了 build,所以我們把「最後一個能用的 build」視為當前的「世代」。
測試技術大概是 1990 年代與 2000 年代初軟體工程中最大的實務改善。在自動回歸測試被廣泛使用之前,IBM 工程師估計 15% 的錯誤修補要嘛引入新 bug、要嘛讓舊 bug 復活。
誘因不太對,因為程式設計師能把大量有 bug 的程式碼扔過牆,希望別人會修好它。這既慢又導致臃腫的程式碼。
微軟廢除了分析師、程式設計師與測試員的區分;它只有「開發者」,他們與客戶談,也負責修自己的 bug。 這拖住了寫很多 bug 的糟糕程式設計師,好讓更多程式碼由更熟練細心的開發者產出。據 Steve Maguire 所說,這正是讓微軟贏得「統治 32 位元作業系統世界」之戰的原因。
安全開發生命週期#
最終在 2002 年 1 月,比爾·蓋茲向全體員工發出「可信計算」備忘錄,命令他們把安全置於功能之上,並在工程師接受安全訓練期間停止所有開發。
他們的內部訓練材料成了書與論文,幫助推動了更廣泛生態系的改變。其「安全開發生命週期(SDL)」於 2008 年出現,被 Windows 開發者廣泛採用。
廣泛使用的 2010 年「簡化實作」版 SDL 本質上是一個瀑布流程,有五個元件(外加「SDL 前」的安全訓練):
- 需求:這涉及風險評估,以及建立「品質關卡」或「bug 門檻」,以阻止含有某些類型瑕疵的程式碼進入下一階段。需求本身被定期檢視;在微軟,檢視間隔從不超過六個月。
- 設計:這一階段需要威脅建模與攻擊面的確立,以餵入產品的詳細設計。
- 實作:開發者必須使用核可的工具、避免或棄用不安全的函數,並對程式碼做靜態分析以檢查這一點已被做到。
- 查核:這一步涉及動態分析、模糊測試與攻擊面的檢視。
- 釋出:這以「事件回應計畫」與「最終安全檢視」為前提。
第一,安全需要一位來自開發團隊之外的主題專家(SME),以及團隊內部的一位安全或隱私「捍衛者」來檢查一切都被做到。
第二,有一個成熟度模型。 從 1989 年起,Watts Humphrey 在卡內基美隆大學軟體工程研究所發展出「能力成熟度模型(CMM)」,基於「能力是團隊而非只是個別開發者之函數」的想法。把半打稱職的樂手湊在一起不等於一個樂團,軟體也一樣。
卡內基美隆的研究顯示:新組成的團隊傾向低估專案的工作量,且所花時間的變異數也高;合作得最好的團隊在預測所需時間上(就平均開發時間而言)好得多,而且也降低了變異數。
這需要自律:犧牲一些資源分配的效率,以為個別工程師提供連續性、並維持團隊的集體專業。
閘控式開發與軟體即服務#
雖然回歸測試不充分,它仍是必要的,因為它能找出被變更影響到的功能。這在「開發衝刺加入大量可能彼此互動的功能」時特別重要。
基於這個理由,Windows 的安全修補是「閘控式開發」的一個例子:在固定間隔,產品的一個預發行版本被推過一整套額外的測試與檢視、並準備釋出。
它們可能被變化的環境、演化的威脅、對新舊平台的新依賴等等驅動。有些改變是隱含的:例如當你升級靜態分析工具時,你可能在既有程式碼庫中發現數百個「新」bug,而你得去分類處理它們。
當 Windows 中發現一個漏洞時,光是修補它並不夠;寫它的人可能寫了十幾個類似的漏洞散落在程式碼庫中,而一旦你發布修補,壞人就會研究它、理解它。
所以與其只修一個 bug,你要更新你的工具鏈,好讓你找出並消除所有產品中所有類似的 bug。 為了管理成本(對微軟與其客戶都是),該公司 2003 年開始把修補綁成每月一次的更新,即如今著名的「修補星期二」。
安全性過去在多數應用中倚賴大量的上市前測試。但一個連網裝置很難在沒有資安的情況下有安全性,而如今汽車這類裝置連上了網際網路,它們也開始有了修補週期。
然而確保「安全關鍵系統的最新版本滿足安全案例」可能需要大量且昂貴的測試。 例如一輛車可能含有來自不同零件供應商的數十個電子控制單元,而除了測試個別 ECU,你還得測試它們如何協同運作。汽車業的公司互相猜疑,即使簽了保密協議也不肯彼此分享原始碼,所以測試可能很複雜。
維護實驗車隊與真實測試車的成本,是歐盟決定要求車廠「在最後一輛車離開展示間後十年內都要修補車用軟體」時,車廠拖拖拉拉的理由之一。
這是特斯拉有顯著優勢的一個方面:作為一家以軟體為業務核心的科技公司,特斯拉能在數週內測試並出貨傳統車廠要花數年的變更。
SaaS、持續部署與 DevOps
軟體即服務(SaaS)背後的關鍵技術創新是「持續整合」與「持續部署」。
與其讓數千位客戶管理數十個不同版本的軟體,廠商能把少數客戶遷移到新版本以測試它,然後再遷移其餘的。升級變得可控得多,因為它們能在真實客戶資料快照上做預演(稱為「預備環境」)。
有些公司如今一天部署好幾次,因為他們的經驗是:頻繁的小改動可能比一次大部署更安全、更不容易弄壞東西。
一家 SaaS 公司通常在負載平衡器後方的一批 VM 上執行多個服務實例。 要做滾動部署,我們設定負載平衡器把(比方說)1% 的流量送到跑新版本的實例,那常被稱為「金絲雀」(取自礦工用來偵測一氧化碳外洩的籠中鳥)。
若金絲雀活了下來,部署就能逐步向前推展到新的服務實例。若日誌系統偵測到任何問題,開發者就會被警示。
你能極快修 bug,意味著你能用少得多的測試達到目標品質水準。你也能看見使用者做的每一件事,所以你第一次能真正理解可用性如何從安全、安全性——以及營收——的角度失敗。
當然,通常是營收在驅動對此的利用。 分析蒐集器把所有行為事件寫入日誌,餵進一條資料管線做度量、分析與查詢。這又支撐了能對可能功能做大量 A/B 測試的實驗框架。
受控實驗也被用來改善安全:例如 Google 靠量測數百萬使用者對「憑證過期」不同警告的反應,調校了其瀏覽器警告。這類改善本身通常相當小,所以你真的需要受控實驗來量測它們;但當你做很多次時,它們就累積起來了。
對這類框架的投資給了規模的遞增回報:你的使用者越多,你達到統計顯著性就越快。所以大公司能比小競爭者更快最佳化其產品;SaaS 一如許多其他數位技術,不只在短期削減成本,也在長期增加鎖定。
作者所知最好的指南是 Google 2013 年的《網站可靠性工程》。 SRE 的目標是可用性、延遲、效能、效率、變更管理、監控、緊急回應與容量規劃。核心策略是應用軟體工程技術把系統管理任務自動化,以在快速創新與可用性之間取得平衡。
多數斷線源於現行系統的變更,所以你妥善監控延遲、流量、錯誤與飽和度,並在任何事出錯時迅速回滾。你把剩餘的錯誤預算用來支撐實驗框架,並做受控的斷線以清出依賴關係。
這由 Netflix 首創,其「混沌猴」會偶爾弄掉路由器、伺服器、負載平衡器與其他元件,以檢查韌性機制如預期運作;這類「消防演習」如今是業界標準,並涉及弄掉整個資料中心。
每當我們在文件上偷工減料、用一個又快又髒的權宜之計修問題、沒有徹底測試一個修補、沒有建入安全控制、或沒有把錯誤的後果想透,我們就在囤積未來可能得連本帶利償還的問題。
技術債對新創、或對接近生命終點的系統,可能是合理的;但它更常是拙劣管理或錯位誘因的產物。
隨時間推移,系統可能債台高築到難以維護或使用,只好被重構或替換。一家銀行得替換其核心銀行系統是極其昂貴且擾動的。所以管理技術債真的很重要——DevOps 哲學的一個重要面向就是「無債經營」。
從 DevOps 到 DevSecOps#
理論上這可以意味著「一切皆程式碼」的策略;實務上它意味著不只維持既有的安全評等(與相關的安全案例),也要回應新威脅、環境變化與出人意料的漏洞。
把兩者合在一起涉及真實的工作,而有時東西得重新發明。 例如 DevOps 瓦解了銀行多年來倚賴的「開發與生產分離」;在職責分離必要的地方,我們得重新想像它。
Azure 生態系與 Google 生態系的兩種做法
Azure 生態系:
這裡的政策大致由四大會計師事務所設定,他們除了標準的內部控制功能外,還跟隨微軟要求安全開發生命週期。
用來做威脅建模、靜態分析、動態分析、模糊測試、app 與網路監控、安全編排與事件回應的數十種工具,帶來可觀的額外負擔——數十人在把資料從一個工具複製到另一個。這裡 DevSecOps 的任務,是靠把這些行政任務自動化來逐步整合這些工具。
一項策略是讓事情「快速失敗」,包括:夠早地讓安全專家介入開發流程以避免日後延誤;對每位開發者的程式碼做提交前的靜態分析以最小化失敗的 build;買或建專門工具以偵測「錯誤的認證、密碼函數使用錯誤、注入機會」這類錯誤;對新版本做自動與人工的安全測試;以及對組態與部署的自動測試。
而雖然 2010 年微軟認為作業安全與軟體安全是分開的,一家現代的 Azure 商行會靠「在部署後持續監控、人工滲透測試、最後為發現問題的第三方提供漏洞獎金」來閉合迴圈。
Google 生態系:
所以雖然一家設施管理公司可能致力於整合支援功能以省錢與減少錯誤,大型服務公司的重點是可靠性。
這又驅動了進一步的原則,例如為可復原性而設計、為可理解性而設計,以及「盡可能不讓人碰生產系統」的意願。
光有「新二進位檔的增量部署」自動化還不夠;你也想阻止系統管理員把複雜的命令列打進路由器來設定網路——多數網路斷線正是從那裡來的。
每個元件通常被實作為若干並行副本或分片,給出更小的失效域。這類域讓你能限制任何攻陷的「爆炸半徑」;理想上,你想能在不讓整個系統下線的情況下處理一次入侵。
被分隔的系統也能為韌性而工程化,但那並不直接。 當一個失效域失效時,你何時只是拉起一個新的、何時該做點別的?依賴關係是什麼?哪些元件該失效開放、哪些該失效安全?在壅塞或攻擊下,什麼樣的降級效能是可接受的?
網頁也有標準框架,它們不只該從一開始就防止 SQL 注入與跨站腳本攻擊,也該支援數十種不同語言。有一個單一前端來終止所有 http(s) 與 TLS 流量,意味著若你得更新憑證管理機制或加密套件,你只需要做一次,而不是在所有不同服務中做。
用型別封裝來強制 URL、SQL 等的性質,能減少你需要驗證的程式碼量。若你有「同時也可理解」的建構即安全 API,那是最好的。
合適的候選包括:開給開發團隊的安全工單數、安全未通過的 build 數、以及一個新應用在相關法規(不論 SOX、GDPR 或 HIPAA)下達到合規所需的時間。
但這一切都需要被有智慧地管理。 一家經營良好的公司能靠「做隱私衝擊評估、改善存取控制、擴充日誌」這類衝刺,讓安全流程對所有開發/維運人員更可見。
一家經營不良的公司則會「管理到度量指標」,那會製造張力:其安全人員最後會面對「把壞人擋在外面」與「靠達成所有用來正當化團隊自身存在之指標來餵養這頭野獸」的衝突目標。
漏洞週期#
人們曾希望「限制攻擊面的架構」加上「形式方法的應用」能讓我們逃脫。如我們所見,那並未真的奏效(除了在密碼設備這類少數邊緣案例)。
到 2000 年代初,我們得出結論:我們就是得把修補週期管理得更好。
研究者可能是客戶、學者、國家情報機關的承包商、甚至是罪犯。他們可能在市場上賣掉它。
多數大型軟體與服務公司如今提供漏洞獎金,金額能從數千到數十萬美元;另一個極端則是「收購漏洞利用以賣給網路軍火商(他們賣給軍方與情報機關)與鑑識公司(賣給執法單位)」的營運者。這類營運者如今為 Android 與 iOS 的持久性遠端漏洞利用開出數百萬美元。
Apple 為「不需要使用者任何點擊就能駭進 iOS 核心」的人提供 100 萬美元。2019 年揭露,光是透過 HackerOne 這一個漏洞獎金平台,就至少有六名駭客賺到超過 100 萬美元。
若一個漏洞利用在廠商發布修補之前就在野外被使用,它就叫「零日」,通常用於針對性攻擊。
若它被用得夠多,最終有人會注意到。攻擊被通報,然後廠商發布修補,而修補可能被逆向工程,於是許多其他行為者現在也有了漏洞利用程式碼。未能修補系統的客戶現在就對「能被犯罪集團大規模部署」的多種漏洞利用脆弱。
- 廠商會偏好 bug 根本沒被找到,以省下修補的花費。若其程式碼被用在需要修補的客戶裝置(如汽車)中,他們可能得付賠償金以支付客戶的成本;所以在這類產業中,拖延與否認的誘因更為尖銳。
- 一般客戶也可能偏好 bug 沒被找到,以避免修補的麻煩。懶惰的客戶可能不修補,並因此被感染。
- 典型的安全研究者想為其發現得到某種回報,不論是名聲、現金、還是為自己倚賴的系統取得修補。
- 情報機關想快速得知漏洞,好在修補出貨前用於零日利用。
- 資安軟體公司從未修補的漏洞中獲益,因為其防火牆與防毒軟體能尋找其淪陷指標以封鎖利用它們的攻擊。
- 大公司不喜歡修補,政府部門也是,因為「對企業關鍵系統測試新修補並推出去」的過程很昂貴。較好的公司已建立自動化來處理「修補星期二」這類常規事件,但更新或風險評估辦公室與工廠中無數的 IoT 裝置,會是未來多年的頭痛來源。
最終揭露的威脅讓廠商動起來;那段延遲給了他們足夠時間妥善測試修補再釋出;研究者得到能寫進履歷的功勞;客戶在收到 bug 報告的同時就得到修補;而大公司安排了企業客戶能據以規劃的定期更新。
喔,還有機關在其本地 CERT 有一條熱線,所以他們能提前得知自然發生的漏洞利用並加以利用。這是結束 2000 年密碼戰爭第一回合之協議的一部分。
如今被通報的漏洞有數千個、發布的修補有數百個,自動化是必要的。
福斯汽車在伯明罕與奈梅亨大學的學者發現、並負責任地揭露其遙控鑰匙進入系統的漏洞後控告了他們——而那些漏洞已被線上可得的偷車工具利用。這是福斯的錯誤;它讓人注意到那個漏洞,而且他們也輸了官司。
微軟與 Google 這類公司有二十年時間學到:跑漏洞獎金計畫與每月修補,比威脅告人有效得多。但許多傳統產業的公司至今仍沒搞懂這一點,即使其產品含有越來越多軟體。
由於這類供應鏈問題,負責任揭露已讓位給「協調揭露」。
如今很少公司自己造所有工具,即使一個兒童玩具也可能有多重軟體依賴。 若它做語音與手勢辨識,它很可能含有一顆跑著某種 Linux 或 FreeBSD 的 Arm 晶片,與一個跑著另一種 Linux 的雲端服務通訊,並能被一個跑在 Android 或 iOS 上的 app 控制。
2019 年 2 月發現 Enox「Safe-KID-One」兒童手錶與其後端伺服器之間的通訊未加密,理論上駭客能追蹤並打電話給孩子。回應是立即的全歐盟安全召回。把這類事情弄錯,可能讓你的產品(與你的公司)猝死。
安全事件與事件管理#
你需要一個「當你得知漏洞或攻擊時該怎麼做」的事件回應計畫。你的計畫需要四個成分:監控、修復、散布與安撫。
- 確保你盡早得知漏洞——最好不晚於壞人(或媒體)。這意味著建立一個威脅情報團隊。 若你是 IoT 廠商,經營自己的蜜罐以立即得知有人攻擊你的產品可能是審慎的。傾聽客戶很重要:你需要一個有效率的管道讓他們回報 bug。
- 你必須能修復問題。 二十年前那意味著每個產品團隊有一位成員「隨叫隨到」、帶著呼叫器,以防凌晨三點有東西要修。如今它意味著準備一個「從漏洞報告到重大外洩」的統籌回應。回應團隊也可能需要替代通訊手段——你可曾停下來想過你是否需要衛星電話?
- 你必須能迅速部署修補。 若所有軟體都跑在你自己的伺服器上,那可能容易;但若它涉及為數百萬台消費裝置打補丁,就需要事先規劃。 供應鏈越長,利益衝突越難管理。經驗教導:在緊急狀況中,你就是盡可能快地跑你的正常修補流程。
- 你需要事先教育你的執行長與董事,讓他們知道「迅速且誠實地處理安全外洩」對維持信心與限制損害的必要性。你需要一個能立即接通執行長並向其簡報的機制。你也需要一個應付媒體的計畫:預備好各種嚴重程度事件的新聞稿,好讓你的執行長只需挑對的一份、填上細節。
組織對風險的錯誤管理#
貝佐斯定律說:你不能用「兩個披薩餵不飽的人數」來跑一個開發專案。 八個人的團隊剛好可管理,但你不能靠六個這樣的團隊平行工作就快六倍。
若一個專案涉及多個團隊,成員不能隨機互相交談,否則你會得到混亂;而他們也不能把所有溝通都經由最低層級的共同主管,因為沒有那個頻寬。
隨著你擴大規模,協調會開始涉及中階經理、幕僚部門與委員會的激增。 一條乾淨軍事指揮鏈(N 個人、沒有橫向互動)的通訊複雜度是 log N;若每個人都得諮詢其他每個人,就是 N²;而若任何子集都能組成委員會思考問題,它可能奔向 2^N。
作者在大學部課程中舉的例子,是倫敦救護車服務新調度系統的熔毀:過於雄心勃勃的專案、不足的規格與沒有真正的測試,導致這座城市有一天沒有救護車支援。
若接受「許多大型商用與政府系統需要大量修改工作,把兩位數日期改成四位數」,而且傳統經驗是相當比例的大型開發專案遲交或根本交不出來,許多人自然假設會有相當數量的系統在 1999 年底失效,並預測廣泛的混亂。但那並未發生。
這似乎支持了「需求理解」的論點:千禧蟲修補的需求是完全已知的——「我要這個系統就照現在這樣,繼續運作到 2000 年以後。」
這是作者選 Rick Smith 那句話作為本章開頭的理由之一。
但要把這件事弄錯很容易,即使在一個相當明確定義的專案中。面對一個難題,人們常見的反應是狂熱地攻擊一個相關但較容易的問題。
在安全領域,風險管理可能更糟,因為問題是開放式的。我們真的不知道下一場風暴會從哪裡來。
1990 年代末我們以為我們有了安全的智慧卡;然後差分功耗分析來了。2010 年代中期我們以為我們的 CPU 夠安全,能讓競爭公司在 Amazon 資料中心的同一台機器上跑各自的工作負載;然後 Spectre 來了。
但檢查清單需要較少的管理注意力與心力,而品質官僚體系愛死它們了。
經認證的流程有強烈的「排擠批判思考」傾向;設計者不再持續檢視系統的保護需求,而是直接伸手拿檢查清單。結果往往是反常的:不先攻最難的問題,你就把不確定性藏起來,而它日後會更糟。而且人們很快就學會怎麼玩弄檢查清單。
(作者提到:幾乎每一家被大規模資料外洩擊中的公司都有 ISO 27001 認證,但它失敗了,因為他們的稽核員說某件事沒問題,而其實有問題。)
頂尖科技與金融科技公司有一些明星,但當資安長可能是個吃力不討好的工作。 好的工程師往往不想要它、或沒有應付人的技巧;而有野心的經理人傾向避開這份工作。
在許多組織中,晉升是資歷與人脈的問題;所以若你想當執行長,你得花 20 年爬上階層而不得罪太多人。當資安長意味著一直在對人說不,而沒有技術背景的通才根本做不來。這份工作也帶來大量壓力與過勞風險;資安長的平均任期約兩年。
無論如何,圍繞風險與安全嵌入適當的文化,是執行長與董事會的事。若他們不認為那重要,資安長就毫無機會。
另一個問題來源是:系統設計決定由「不太可能為其負責的人」所做。
這可能有許多理由:IT 人員流動率高、大量倚賴約聘人員;對裁員的恐懼把忠誠員工變成暗中的求職者。這在大型公部門 IT 專案中可能是特別的問題:涉入其中的部長或公務員,沒有一個預期自己會在七年後東西交付時還在。
所以在做大型系統專案時,別忘了環顧四周,問問自己:日後事情出錯時,誰會來背鍋?
事實上,若一家公司決心要得到一個完全安全的產品,他們就該雇用多位專家。 本書描述過這如何幫助了預付式電表的設計,而後來一項學生實驗證實:你讓越多人思考一個提議的系統設計,他們能發現的潛在危害與漏洞就越多。當然,這很少發生。
管理團隊#
要開發安全可靠的程式碼,你需要建立一個有正確文化、正確技能組合與正確誘因的團隊。
菁英工程師、多樣性與技能培養
當時的想法是:有些程式設計師的生產力遠高於其他人,所以與其把他們升為管理者而「失去」他們,不如為他們創造有管理職薪水與尊重的職位。
微軟、Google、Facebook 與 Netflix 這類更現代公司的看法是:你一開始就只想雇用超高生產力的工程師——尤其若你每年收到一百萬份履歷、卻只打算雇用兩萬名新工程師。
這兩種做法並不衝突。 現代科技公司雇用多位技術巨星,從著名設計師到圖靈獎得主。其中一家公司的看法是:如果你沒有給程式設計師的職涯結構,你就別指望寫出好軟體。
想花一輩子寫軟體、而且擅長此道的人,必須得到尊重,不論你的組織如何表達那件事。大學懂這一點;我們教授管理著這個地方。科技公司也懂。但政府一般很糟糕。
也有少數族裔:水星、雙子星與阿波羅任務的軌道計算由一位非裔美國女性 Katharine Johnson 領導。
但在美英,事情已變得由男性主導。 自作者 1990 年代成為學者以來,儘管有大量招收女學生的努力,當地資工學生中約六分之一是女性。但在前共產東歐國家,比例約三分之一。在印度接近性別平衡。所以這是文化議題。
更多元的團隊更有效,而真正的改變不是隨你雇的第一位女性而來,而是當你有足夠多人能改變團隊文化時。那也許意味著三位或更多。
微妙一點的是:若你想吸引更多女性並留住她們,管理「人」而非管理「工作」可能是個好主意。你得保護你的員工,給他們空間去做他們擅長的事。
顯然雇用厭女的霸凌者是壞主意,雖然事先要看出他們可能很難。霸凌者往往也是馬屁精;除了對下屬頤指氣使,他們也巴結上級。這種人往往不懂技術上發生了什麼,所以他們毫無概念誰有生產力,只好靠打卡時間或「有多會討好自己」來評判人。若這種管理風格擴散到整個組織,聰明人就會去別的地方。
你也需要多樣的技能。 若你在寫一個 app,你可能想要兩個人寫 Android 程式碼、兩個寫 Apple 程式碼、兩個寫伺服器。依任務而定,可能還有領導可用性測試的使用者倡議者、為安全性與資安發聲的人、保持整體設計乾淨高效的架構師、擔心 API 的語言律師、跑回歸測試機器的測試工程師,以及維護靜態與動態分析工具的工具匠。
好的科技公司讓工程師緩慢地在公司內輪調,以取得「最大化其對公司價值」的技能組合(即使那也最大化了他們對別人的價值、讓他們更容易跳槽)。
如果「找到 bug 的人(即使是自己寫的 bug)只是靜靜修好它」,那是壞實務;由於 bug 是相關的,很可能還有更多。bug 追蹤很重要,而能保有良好統計的工單系統,是提升品質的重要工具。
作為好實務的例子,在航管中,犯錯的管制員被期待不只修正它,還要立刻公開喊出來:「我誤把 Speedbird 123 放在終端管制區飛航高度八零,正在指示下降到六零。」這樣任何有潛在衝突流量的其他管制員都能注意到、喊出來並協調。軟體沒那麼戲劇化,但沒什麼不同:你得讓開發者習慣分享自己的經驗,包括自己的錯誤。
管理不良團隊的一個訊號,是程式碼庫處於混亂的風格混合中,每個人各行其是。 當一位程式設計師簽出某段程式碼來處理時,他可能花半小時把它重新格式化、調成自己的風格。除了浪費時間,重新格式化的程式碼還可能絆倒你的分析工具。
你也想要程式碼中有註解,因為人們花在讀程式碼上的時間通常多於寫。你想知道寫出漏洞的程式設計師以為自己在做什麼:那是設計錯誤,還是編碼失誤?
所以當你啟動一個專案時,讓大家坐下來、花一個下午敲定你們的家規風格。 只要它足以供日後閱讀程式碼與理解 bug,風格是什麼並不太重要;重要的是有一個一致、被大家接受、且合乎目的的風格。創造這個風格,比花一個下午打漆彈是更好的團隊建立活動。
微軟與 Google 現在都讓新手工程師參加安全「新兵訓練營」,好讓每個人都懂基礎,同時在若干層級上也有主題專家——從有碩士學位(或等效內部資格)的實務安全顧問,到有密碼學或虛擬化細節博士學位的人。
訣竅在於管理團隊中專門化的程度,以及專家(如安全架構師與測試大師)與其他開發者互動的方式。
你靠使用 Rust 這類現代語言避開緩衝區溢位;若你必須用 C 或 C++,就要有嚴格的編碼慣例,並用 SonarQube 與 Coverity 這類靜態分析工具強制執行。你靠使用維護良好的函式庫避開時序攻擊與弱亂數產生器這類密碼問題。
但你得理解工具的限制。 以 Coverity 為例,其作者說明:若你從專案一開始就用它很好,但半途採用它會帶來真實成本——你突然多出兩萬份 bug 報告要分類,而你的出貨日會延後數月。
你會不斷加入新工具。若你不關注安全新聞,你可能不知道最新的漏洞利用與攻擊,因而不會意識到自己何時得培養或買進專業。但你不能什麼都用買的;安全業有很多剝削無知客戶的不肖業者。你得理解自己需要買什麼、為什麼,然後你得把它與既有工具整合。
一個系統的架構主要由其介面定義,而它會被千刀萬剮地衰敗:由一位需要「比既有版本多兩個參數之檔案處理常式」的程式設計師(於是寫了一個新的)造成——那可能本身就危險,或只是增加複雜度、從而間接助長最終的失效。
但跨系統的依賴扇出是安全 API 的真實隱患。 我們看過密碼硬體安全模組的 API 如何被擴充以支援數百家銀行的舊 ATM 系統,直到我們突然意識到由此產生的功能互動讓它們完全不安全。
在許多其他應用領域也有類似張力,從被用在超過一百款手機的行動基頻軟體,到被用在超過一百款車的車輛元件。
小結#
隨著越來越多裝置取得 CPU 與通訊能力,我們需要打造「能做真正工作、同時把『會讓它們成為攻擊目標的漏洞』擋在外面」的東西。換句話說,你想要軟體安全——連同其他功能,以及安全性與即時效能這類其他湧現性質。
較溫和的系統演化可能涉及對需求更微妙的改變。較大的改變可能被外部強加;成功並流行的系統,可以預期會被攻擊。
然而即使有一份緊實的規格、或有來自「駭你產品之人」的持續回饋,你也還沒到家。 在雇用對的人、給他們對的工具、幫他們發展對的工作方式、以正確方式用專業支援他們,尤其是創造一個「讓他們努力改善安全能力」的環境上,都有若干挑戰。
研究問題#
安全經濟學與安全心理學近年大步前進,而我們現在知道還需要做很多工作,讓安全工具對開發者更好用。合乎邏輯的下一步,是把我們所知與安全性經濟學及安全性可用性整合起來。
若有人能蒐集一份「由組織中不良誘因所導致之安全失敗」的案例史文庫,那可能會很有用。有了像樣的經驗基礎之後,接下來會如何?