「你的電腦能為你做的任何事,它都可能為別人做。」——Alan Cox

「微軟本可以把有效的安全措施做成標準配備,但理智佔了上風。安全系統有反噬的惡習,而且毫無疑問它們會造成極大的麻煩。」——Rick Maybury

存取控制:安全工程與電腦科學的交會#

作者最早學程式是在一台以打孔卡為輸入、印表機為輸出的 IBM 大型主機上:你帶著一疊卡片排隊、跑作業、拿著印出來的東西離開。所有安全都是實體的。 後來出現了能同時跑多個程式的機器,於是有了保護問題——防止一個程式干擾另一個。

你不希望病毒從你的瀏覽器竊取密碼,或修改銀行應用程式來偷你的錢。而許多可靠性問題也源自應用程式彼此誤解或互相打架

但當客戶希望應用程式共享資料時,要把它們分開就很棘手。如果你的郵件客戶端與瀏覽器跑在不同機器上,讓你無法直接點郵件裡的網址,釣魚會困難得多——但那也會讓生活太難過。

從 1970 年代起,存取控制成為電腦安全的重心。它的功能是控制哪些主體(人、行程、機器…)能存取系統中的哪些資源——能讀哪些檔案、能執行哪些程式、如何與其他主體共享資料等等。

存取控制至少在四個層次上運作,由上而下越來越複雜、越來越不可靠

  1. 應用層:可能表達非常豐富、特定領域的安全政策。銀行客服中心人員通常在你回答幾個安全問題之前不能看你的帳戶明細——這不只阻止外人冒充你,也阻止銀行員工去查名人或鄰居的帳戶。某些交易還可能需要主管核准。而這與現代社群網站的存取控制複雜度相比根本不算什麼。
  2. 中介軟體層:應用可能寫在瀏覽器、銀行記帳系統或社群網路的資料庫管理系統之上。這些強制執行若干保護性質——例如記帳系統確保借記某帳戶的交易必須貸記另一帳戶,且借貸平衡,使錢不能被創造或銷毀;它們也必須允許系統狀態日後被重建。
  3. 作業系統層:作業系統從較低層元件建構檔案與通訊埠等資源時,必須提供控制它們存取的方法。你的 Android 手機把不同公司寫的 app 當成不同使用者,保護它們的資料彼此隔離。
  4. 硬體層:作業系統倚賴處理器及其相關的記憶體管理硬體,控制某個行程或執行緒能存取哪些記憶體位址。

要在這團混亂中理出頭緒,我們必須理解底層原則、常見架構,以及它們如何演化。

兩類重要的區分:

  • 任意存取控制(DAC):把保護留給機器操作者。
  • 強制存取控制(MAC):通常在廠商控制之下,保護作業系統本身不被惡意程式修改。

作業系統的存取控制#

作業系統提供的存取控制通常先用某種機制認證主體(手機用密碼或指紋,伺服器用密碼或安全協定),然後授權存取檔案、通訊埠與其他系統資源。

存取控制常可建模為存取權限矩陣,欄為檔案、列為使用者。用 r 表示讀、w 表示寫、x 表示執行程式、- 表示完全沒有存取權:

作業系統帳務程式帳務資料稽核軌跡
Samrwxrwxrwr
Alicexxrw
Bobrxrrr

在這個簡化例子中,Sam 是系統管理員、有普遍存取權(除了稽核軌跡,連他也應該只能讀);經理 Alice 需要執行作業系統與應用程式,但只能透過核准的介面,不該有竄改它們的能力;稽核員 Bob 可以讀一切。

使用者作業系統帳務程式帳務資料稽核軌跡
Samrwxrwxrr
Alicerxx
帳務程式rxrxrww
Bobrxrrr

這種政策的另一種表達方式是存取三元組(使用者, 程式, 檔案)。一般情況下,我們關心的與其說是程式,不如說是保護域(protection domain)——一組共享相同資源存取權的行程或執行緒。

一家有 5 萬名員工與 300 個應用的銀行,會有一個 1,500 萬格的矩陣——這不只造成效能負擔,也容易受管理者的錯誤影響。

我們需要更好的儲存與管理方式,兩個主要選項是壓縮使用者壓縮權限

  • 群組或角色同時管理大量使用者;
  • 儲存矩陣(存取控制清單 ACL)或依儲存(能力 capabilities,協定工程師稱之為「票證」、手機上稱為「權限」)。

群組與角色#

看大型組織時,通常會發現多數員工落入少數幾個類別中。一家銀行可能有 40 到 50 種:櫃員、客服中心接線員、放款人員等等,只有幾十個人(安全經理、外匯首席交易員…)需要個人化的存取權。

  • **群組(group)**是一份主體清單;
  • **角色(role)**是一組固定的存取權限,可由一個或多個主體在一段時間內承擔。

角色的經典例子是船上的當值官:任一時間恰有一名當值者,而換班時有正式的交接程序。在多數政府與商業應用中,重要的是角色而非個人。

群組與角色可以結合:「目前在海上所有船艦的當值官」是一個角色的群組。在銀行業,劍橋分行經理的權限可能表達為「經理」群組的成員資格加上「劍橋分行代理經理」角色的承擔——群組 manager 可能表達組織中的位階(甚至薪資級距),而角色 acting manager 可能包含在經理、副理與分行會計都請病假時代班的助理會計。

存取控制清單(ACL)#

傳統上簡化存取權管理的方式,是一次一欄地儲存存取控制矩陣,連同該欄所指的資源。這稱為 ACL。

使用者帳務資料
Samrw
Alicerw
Bobr

優點與適用場合

  • 在使用者自行管理檔案安全的環境中是自然選擇,1970 年代起在 Unix 系統中普及。它們是 Linux、macOS 以及 Android、iOS 等衍生系統的基本存取控制機制;Windows 的存取控制也基於 ACL,但隨時間變得更複雜。
  • 當存取控制政策由中央設定時,ACL 適合保護以資料為導向的環境。
  • 實作簡單。
  • 當使用者群龐大且不斷變動、或使用者想把「執行某個程式」的權限委任給另一個使用者一段時間時,ACL 較不適合。
  • 執行期安全檢查沒有效率:典型的作業系統知道哪個使用者在跑某個程式,卻不知道該程式自被喚起以來被授權存取哪些檔案。作業系統必須在每次檔案存取時檢查 ACL,或用其他方式追蹤有效的存取權。
  • 把存取規則分散進 ACL,使得「找出某使用者能存取的所有檔案」變得繁瑣。 要驗證沒有檔案被留成全世界可讀甚至可寫,可能涉及檢查數百萬個使用者檔案的 ACL——這對大型複雜公司是真實問題。

你雖然可以寫腳本檢查伺服器上是否有檔案的 ACL 違反安全政策,卻可能被技術變遷絆倒:轉向容器導致許多企業資料外洩,因為管理員忘了也去檢查容器的 ACL。(而容器本身往往很糟糕,因為那是一項新技術,被數十家毫無頭緒的新創販賣。)

撤銷剛被開除之員工的存取權,通常只能靠取消他們的密碼或認證代幣。

Unix 作業系統安全#

在傳統 Unix 系統中,檔案不能有任意的 ACL,只有簡單的 rwx 屬性。正常顯示的 ACL 有一個旗標表示是否為目錄,然後分別是擁有者、群組與全世界的 r、w、x 旗標,接著是擁有者名稱與群組名稱。

drwxrwxrwx Alice Accounts     ← 所有旗標都設定的目錄
-rw-r----- Alice Accounts     ← 前例中檔案 3 的 ACL

後者記錄了:這是檔案而非目錄;擁有者可讀寫;群組成員(含 Bob)可讀不可寫;非群組成員完全無存取權;擁有者是 Alice;群組是 Accounts。

機器開機時取得控制權的程式(作業系統核心)以 supervisor 身分執行,對整台機器有不受限的存取權。所有其他程式以使用者身分執行、由 supervisor 仲裁存取。存取決定基於與程式關聯的 userid;但若它是零(root),存取控制決定就是「可以」。

這帶來四個具體問題:

  1. 系統管理員能做任何事,所以我們難以把稽核軌跡實作成他無法修改的檔案。上例中 Sam 可以動手腳改帳,而若被誣告動手腳也難以自辯;更嚴重的是,設法取得管理員身分的駭客可以移除他入侵的所有證據。

    傳統且至今仍最常見的日誌防護方式是把它們分開存放。過去這意味著把系統日誌送到上鎖房間裡的印表機;如今意味著送到另一台機器、甚至送到第三方服務。強制存取控制的角色也越來越重要。

  2. ACL 只含使用者名稱,不含程式名稱,所以沒有直接的方法實作(使用者, 程式, 檔案)存取三元組。Unix 提供了一個間接方法:**set-user-id(suid)**檔案屬性——程式的擁有者可以把代表該程式的檔案標為 suid,使它以擁有者的權限而非喚起者的權限執行。

    程式設計師往往懶惰或面臨緊迫期限,於是乾脆把應用程式設成 suid root,讓它什麼都能做。這種做法導致了一些駭人的安全漏洞。 做存取控制決定的責任從作業系統環境移到了應用程式,而多數程式設計師的經驗不足以檢查所有該檢查的東西。(而且很難知道該檢查什麼,因為喚起 suid root 程式的人控制其環境,可能以意料之外的方式操弄它。)

  3. ACL 不太擅長表達可變狀態。假設我們想要一筆交易在被執行前先由一位經理與一位會計核准,我們只能在應用層做(例如設一個等待第二個簽名的交易佇列)、或用 suid 玩花樣。管理有狀態的存取規則很困難;它們會讓剛被開除之使用者的撤銷變複雜,因為很難追蹤他們開啟過的檔案,東西也可能卡住。

  4. Unix ACL 只指名一個使用者。 若某資源會被多人使用而你想在 OS 層做存取控制,舊系統只能用群組;新系統實作了 Posix 的擴充 ACL,可含任意數量的具名使用者與具名群組。

    理論上 ACL 與 suid 機制常能達成想要的效果。實務上程式設計師往往太趕而懶得弄清楚怎麼做,而安全介面通常又太瑣碎難用。 於是人們把程式碼設計成需要遠比它該有的更多權限,因為那似乎是唯一能把事情做完的方式。

能力(capabilities)#

管理存取控制矩陣的下一種方式是依列儲存,這稱為能力:

使用者作業系統帳務程式帳務資料稽核軌跡
Bobrxrrr
  • 執行期安全檢查更有效率
  • 委任權限不難:Bob 可以做一張憑證說「這是我的能力,我在此把上午 9 點到下午 1 點讀取檔案 4 的權利委任給 David,Bob 簽」。
  • 但改變檔案的狀態變得棘手,因為很難查出哪些使用者有存取權。這在我們必須調查事故或準備證據時很麻煩。

事實上,可規模化的系統最終在內部都使用事實上的能力,因為即時的全系統撤銷實在太貴。在 Unix 中,檔案描述符其實就是能力,即使 ACL 權限甚至檔案擁有者改變後,它們仍會繼續授予存取權一段時間;在分散式 Unix 中,存取可能持續整個 Kerberos 票證的生命期。

能力的歷史,以及它最適合的場合

能不能完全捨棄 ACL? 1970 年代有人建造了全程使用能力的實驗性機器;第一個商業產品是電話交換機控制器 Plessey System 250。IBM AS/400 系列系統在 1988 年把能力式保護帶進主流計算市場,並取得一些商業成功。密碼學中使用的公鑰憑證實際上就是能力,1990 年代中期起成為主流。能力已開始在作業系統中補充 ACL,包括較新版的 Windows、FreeBSD 與 iOS。

在某些應用中,能力是表達安全政策的自然方式。

例如醫院可能有「護理師應能存取其病房中、或過去 90 天內曾在其病房的所有病人」這樣的存取規則。

在基於傳統 ACL 的早期系統中,每一次存取控制決定都需要參照行政系統,去查出哪些護理師與哪些病人在何時位於哪個病房——但這讓人資系統與病患行政系統都變成攸關安全的關鍵系統,重創了可靠度。

修正方式是給護理師發帶有憑證的識別證,讓她們有權存取與若干病房或醫院部門相關的檔案。

順著紋理走,能在堆疊的所有層次帶來好處:讓東西更好用、支援更安全的預設值、減少錯誤、降低工程投入,也省錢。

DAC 與 MAC#

過去,任何能實體接觸電腦的人就控制了它的全部:你可以載入任何喜歡的軟體、檢視記憶體或磁碟中的一切、改變任何想改的東西。這是**任意存取控制(DAC)**背後的模型。

但這在規模上難以管理。1970 年代美國軍方啟動了一項龐大的電腦安全研究計畫,目標是保護機密資訊:確保標記為「最高機密」的檔案絕不會被只有「機密」權限的使用者取得,不論任何普通使用者、甚至 supervisor 做了什麼。

在這種多層次安全(MLS)系統中,系統管理員不再是老大:最終控制權落在設定安全政策的遠端政府機關手上。這些機制開始被稱為強制存取控制(MAC)——supervisor(或者說 root 存取權)處於遠端控制之下。

MAC 的想法後來從多個方向匯流:

  • 1980 年代起,安全工程師也在研究安全完整性等級的概念——粗略地說,較可靠的系統不得倚賴較不可靠的系統。他們開始意識到自己需要類似多層次安全的東西,但用於 safety。
  • 軍事系統的人也認識到保護機制本身的防篡改性至關重要。
  • 1990 年代,隨著電腦與網路快到能處理音訊與視訊,創意產業遊說要求數位版權管理(DRM)。這也是一種強制存取控制——阻止訂閱者把歌分享給非訂閱者,在許多方面就像阻止最高機密使用者把情報報告分享給機密使用者。
TPM 與可信開機:真正的 supervisor 換人了

2000 年代初,這些想法匯合,若干作業系統廠商開始把 MAC 研究計畫的想法與機制納入產品。

催化劑是微軟與 Intel 把密碼學引入 PC 平台以支援 DRM 的倡議。Intel 相信 PC 的商業市場已飽和,成長將來自家用;而他們相信家用市場會要求 DRM。微軟從 DRM 出發,接著意識到為文件提供權利管理,也許能把客戶牢牢鎖進 Windows 與 Office。他們成立了一個產業聯盟(現稱可信計算群組),把密碼學與 MAC 機制引進 PC 平台。

要做到這件事,作業系統必須被做成防篡改的,而這是靠一顆獨立的處理器達成——可信平台模組(TPM),基本上是裝在 PC 主機板上的智慧卡晶片,支援可信開機與硬碟加密。

TPM 監控開機過程,每一階段都需要「迄今載入之一切的雜湊」才能取出解密下一階段所需的金鑰。

於是系統上真正的 supervisor 不再是你這個機器擁有者——而是作業系統廠商。

基於 TPM 與可信開機的 MAC,自 2006 年起被用於 Windows 6(Vista)作為對抗持久性惡意程式的防禦。TPM 標準與架構被其他作業系統廠商與裝置 OEM 採用,現在甚至有基於 Google 產品的開源 TPM 晶片計畫 OpenTitan。

(原書註記:微軟原本有更宏大的計畫。它的 Palladium 專案本會為權利管理 app 提供一個全新、更可信的世界,與跑舊軟體的正常世界並存。他們在 2003 年推出「資訊權利管理」——文件的 DRM——但企業不買單,視之為鎖定手法。雙世界實作對 Vista 來說太複雜,經兩次獨立開發後被放棄;但這個願景自 2004 年起在 Arm 的 TrustZone 中延續了下來。

各大作業系統的取徑#

macOS、iOS、Android、Windows 的存取控制簡史

macOS:基於跑在 Mach 核心之上的 FreeBSD 版 Unix。BSD 層提供記憶體保護;應用程式除非以進階權限執行,否則無法存取系統記憶體(或彼此的記憶體)——這意味著你可以用「強制結束」殺掉卡住的應用而不必重開機。

檔案系統層次上,macOS 幾乎是標準 Unix。預設安裝停用 root 帳號,但可管理系統的使用者被放在 wheel 群組中,允許他們 su 到 root。自 10.5(Leopard)起,它基於 TrustedBSD——一個納入強制存取控制機制的 BSD 變體,用來保護核心系統元件不被惡意程式竄改。

iOS:Apple 取用 CMU 的 Mach 核心並與 FreeBSD 版 Unix 融合,為效能與穩健性做了若干改動。例如在原味 Unix 中,一個檔名可以有多條路徑名指向代表檔案物件的 inode;在 iOS 中這被簡化為檔案有唯一路徑名,而路徑名正是檔案層級存取控制的對象。同樣有 MAC 元件,使用來自**域與型別強制(DTE)**的機制來防篡改核心系統元件——Apple 導入它是因為擔心 app 會把 iPhone 變磚,導致保固索賠。

app 也有權限(即能力):它們在首次嘗試使用時請求存取行動網路、電話、簡訊、相機等裝置服務的能力,若使用者同意就授予。

眾多裝置服務開啟了可能的旁通道攻擊:例如被拒絕存取鍵盤的 app,可以用加速度計與陀螺儀推論按鍵。

Apple 生態系是封閉的:iPhone 只跑 Apple 簽署過的 app(少數例外:企業可取得內部 app 的簽章金鑰,但濫用時會被列入黑名單)。這讓公司能抽取 app 營收的一份,也能篩檢 app 是否有惡意程式或其他不良行為。

iPhone 5S 導入指紋生物辨識與支付,在 A7 處理器上加入**安全飛地(SE)**給它們獨立保護。

雖然 iOS 能存取系統分割區,使用者的個人資料是加密的,金鑰由 SE 管理。金鑰管理由一把燒進系統單晶片熔絲連結的獨一無二 256 位元 AES 金鑰啟動。裝置上電時,使用者有十次輸入密碼的機會;只有通過後,檔案金鑰才會從主金鑰導出並可用。 裝置上鎖時,某些金鑰仍可使用,好讓 iOS 能判斷誰寄來了新訊息並通知你——這種方便的代價是鑑識設備能取得部分使用者資料。

Android:世界上使用最廣的作業系統(Google 數據:2019 年 5 月有 25 億台活躍裝置)。它基於 Linux,不同廠商的 app 以不同 userid 執行。Linux 機制在檔案層級控制存取,防止一個 app 讀另一個的資料、耗盡記憶體與 CPU 等共享資源。如同 iOS,app 有實質上是能力的權限。

app 以簽署過的 .apk 套件形式提供;iOS app 由 Apple 簽署,而 Android 的驗證金鑰以自簽憑證形式提供,作為開發者的名字——這在維持開放生態系的同時支援更新的完整性。

每個套件含一份要求一組權限的清單,使用者必須核准其中「危險的」那些。在早期版本的 Android 中,使用者必須在安裝時全部核准、否則不能跑該 app。

但經驗顯示多數使用者會為了通過安裝流程而亂點一通,於是你甚至看到手電筒 app 索取你的通訊錄存取權——因為它們可以賣錢。所以 Android 6 轉向 Apple 的「首次使用即信任」模型。

自 Android 5 起,SELinux 被用來以強制存取控制強化作業系統,不只保護核心系統功能免於攻擊,也強力分隔行程並記錄違規。SELinux 由 NSA 開發以支援政府系統中的 MAC。

其哲學是:行動需要三方的同意——使用者、開發者與平台。

Windows:目前版本(Windows 10)看來是第三受歡迎的作業系統,2020 年 3 月達成十億月活躍裝置(2016 年之前 Windows 一直是領先者)。Windows 有一套嚇人地複雜的存取控制系統。

早期版本沒有存取控制。Windows 4(NT) 是個轉捩點,它很像 Unix 並受其啟發,但有若干擴充:

  • 除了讀、寫、執行,還有取得擁有權、變更權限與刪除的獨立屬性,以支援更彈性的委任。這些屬性也適用於群組,而群組權限讓你能達成類似 Unix suid 程式的效果。
  • 屬性不是像 Unix 那樣單純的開或關,而是有多個值:AccessDeniedAccessAllowedSystemAudit,並依此順序解析——若在相關使用者或群組的 ACL 中遇到 AccessDenied,則不論有多少衝突的 AccessAllowed 旗標,都不允許存取。 更豐富的語法讓你能安排成「安裝印表機這類日常組態任務不必要求完整管理員權限」。
  • 使用者與資源可被分割成有各自管理員的域(domains),信任可在域之間單向或雙向繼承。典型大公司可能把所有使用者放進由人資管理的人員域,而伺服器與印表機等資產放在部門控制的資源域中,個別工作站甚至由其使用者管理。可以安排成部門資源域信任使用者域、但反之不然——於是被駭或粗心的部門管理員造成的外部損害不會太大。

管理這一切、並對使用者介面隱藏 ACL 細節的資料結構稱為登錄檔(Registry)。其核心過去是 Active Directory,管理遠端認證(用 Kerberos 變體或 TLS,封裝在安全支援提供者介面 SSPI 之後,讓管理員能插入其他認證服務)。

Windows 以兩種方式加入了能力:

  1. 使用者或群組可透過**設定檔(profiles)**被允許或拒絕存取。安全政策由群組而非整個系統設定;群組政策覆蓋個別設定檔,並可與站點、域或組織單位關聯。
  2. 在許多應用中人們用 TLS 做認證,而 TLS 憑證提供了另一層 Active Directory 管不到的、能力導向的存取控制。

Vista 之後的三項保護機制(除了可信開機):

  1. 核心對開發者關閉;
  2. 圖形子系統與多數驅動程式被移出核心;
  3. **使用者帳戶控制(UAC)**以使用者預設取代先前的預設管理員權限。

先前有太多例行任務需要管理權限,以致許多企業把所有使用者都設成管理員,這使得遏制惡意程式非常困難;而許多開發者寫軟體時就假設它會擁有一切存取權。據微軟工程師說法,這是 Windows 缺乏穩健性的主要原因:應用程式以不相容的方式擺弄系統資源。

於是他們加入了應用程式資訊服務,啟動需要提升權限的應用並用虛擬化把它們裝起來:若它們修改登錄檔,改的不是「真的」登錄檔,而只是它們看得到的那個版本。

Vista 起,桌面成為後續使用者行程的父行程,所以即使管理員也以一般使用者身分瀏覽網頁,下載的惡意程式在未獲後續授權前無法覆寫系統檔。Vista 還以檔案完整性等級的形式加入了強制存取控制:低完整性行程(例如你從網際網路下載的程式碼)不應能修改高完整性資料(例如系統檔),除非有可信行程介入(例如微軟對該程式碼簽章的驗證)。

  • Windows 8(2012):加入動態存取控制,讓你依情境(工作 PC vs 家用 PC vs 手機)控制使用者存取。
  • Windows 8.1(2016):加入更乾淨的抽象——主體(principals)(可以是使用者、電腦、在安全情境中執行的行程或執行緒,或這類主體所屬的群組)與安全識別符(SID)。也加入微軟帳戶為轉向雲端運算做準備。
  • Windows 10:加入若干支援雲端運算與多樣客戶端裝置的功能,從憑證釘選到廢除舊的安全注意序列 Ctrl-Alt-Del(在觸控螢幕裝置上難以做到,而且使用者本來就不懂)。

總結:Windows 演化出了比先前任何在大眾市場上銷售的系統都更豐富、更有彈性的存取控制工具集。

驅動它的是企業客戶——他們需要管理跨數百個據點、擔任數百種不同職務的數萬名員工,並提供內部控制以限制少數不誠實員工或受感染機器所能造成的損害。微軟超過一半的營收來自授權超過 25,000 個席次的公司。

但企業客戶所要求之彈性的代價是複雜性:為一家大型 Windows 商店設定存取控制是高度專業的工作。

中介軟體#

在檔案與程式的層級做存取控制,在運算的早期(那時這些就是重要的資源)沒有問題。但自 1980 年代起,規模與複雜度的成長導致存取控制改在其他層次做——而非(或除了)作業系統層次。

資料庫存取控制#

在人們開始用網站購物之前,資料庫安全大體是後勤部門的事。但企業現在有攸關存亡的資料庫來處理庫存、出貨與電子商務,前面架著把交易直接傳給資料庫的網頁伺服器。 這些資料庫現在含有攸關我們生活的許多資料——銀行帳戶、車輛登記與就業紀錄——而失誤有時會把它們暴露給隨機的線上使用者。

資料庫產品有自己的存取控制機制,模仿作業系統機制,權限通常對使用者與物件都可用(所以機制是 ACL 與能力的混合)。

有些產品讓開發者能繞過作業系統控制。例如 Oracle 同時有作業系統帳號(使用者須由平台外部認證)與資料庫帳號(使用者直接由 Oracle 軟體認證)。用後者往往比較方便,可以省去與其他部門同步的工夫。在許多安裝中,資料庫可從外部直接存取;即使有網頁服務前端遮蔽,那前端也常含有讓 SQL 程式碼能被插入資料庫的漏洞。

瀏覽器#

網頁瀏覽器是另一個我們倚賴其做存取控制、而其複雜度常讓我們失望的中介軟體平台。主要存取控制規則是同源政策(same-origin policy):網頁上的 JavaScript 或其他動態內容只被允許與它最初來自的 IP 位址通訊;這類程式碼在沙箱中執行以防它改變主機系統。

但很多事情會出錯。

從 CSRF 到「洗來源」:瀏覽器安全的演變

在本書前幾版中,我們把網頁安全視為「伺服器如何被設定、以及這是否導致跨站漏洞」的問題。例如惡意網站可以包含旨在製造特定副作用的連結或表單按鈕:

https://mybank.com/transfer.cgi?amount=10000USD&recipient=thief

想法是:若一位已登入 mybank.com 的使用者點了它,該交易可能被執行,因為有有效的工作階段 cookie。所以支付網站部署了短生命期工作階段、反 CSRF token(工作階段 cookie 的隱形 MAC)並檢查 Referer: 標頭等反制措施。

畢竟瀏覽器是你筆電上執行「由你不想信任、且偶爾會有惡意的人所寫之程式碼」的地方;手機作業系統把不同 app 當成不同使用者以提供更穩健的保護。 即使沒有惡意,你也不希望因為某個分頁裡的腳本而必須重開瀏覽器。(Chrome 試圖靠讓每個分頁跑在獨立的作業系統行程中來確保這件事。)

瀏覽器的臭蟲被用於偷渡式下載攻擊——造訪一個攻擊網頁就能感染你的機器。而即使沒有這個,現代網頁環境也極難控制:

許多網頁充滿由多個廣告聯播網與資料仲介提供的追蹤器與其他壞東西,讓同源政策背後的意圖形同笑話

惡意行為者甚至能用網頁服務洗來源:攻擊者把目標網站與自己的邪惡腳本做成 mash-up,然後讓受害者透過 Google 翻譯這類代理去檢視它。

謹慎的人會靠輸入網址或用書籤去銀行網站;不幸的是,行銷業訓練每個人去點郵件裡的連結。

一個 2020 年的例子:Amazon Alexa 被發現有錯誤設定的跨來源資源共享政策,意味著任何攻陷另一個 Amazon 子網域的人,都能把目標 Alexa 上的技能換成惡意的。

沙箱#

1990 年代末出現了另一種存取控制:軟體沙箱,由 Sun 隨其 Java 程式語言導入。

模型是:使用者想跑一段以 applet 形式下載的程式碼,但擔心它可能做壞事——例如竊取她的通訊錄寄給行銷公司,或只是霸佔 CPU 把電池耗光。

Java 設計者以提供「沙箱」來處理這個問題——一個受限環境,程式碼在其中無法存取本地硬碟(頂多暫時存取受限目錄),且只被允許與它來自的主機通訊(同源政策)。這靠讓程式碼由只有有限存取權的直譯器(Java 虛擬機)執行來強制。

這個想法被沿用到網頁中主要的腳本語言 JavaScript(雖然它其實是不同的語言)與其他動態內容。Java 的一個版本也被用於智慧卡,好讓它們能支援不同廠商寫的 applet。

虛擬化與容器#

虛擬化是雲端運算的動力來源:它讓單一機器能獨立模擬多台機器,於是你可以在資料中心每月花幾十美元租一台虛擬機(VM),而不必為整台伺服器付上百美元。

虛擬化由 IBM 在 1960 年代發明;單一機器可用 VM/370 分割成多台虛擬機。起初這是為了讓新的大型主機能跑數種舊機器架構的舊應用;很快地,買兩台電腦的公司把一台用於正式環境、另一台當成一系列邏輯上分離的機器用於開發、測試與次要應用就成了常態。

光在主機作業系統上跑虛擬機監視器(VMM)再跑其他作業系統是不夠的;你必須處理會揭露處理器狀態(絕對位址、處理器時鐘)的敏感指令

可運作的 VMM 在 2003 年隨 VMware ESX Server 與(尤其是)Xen 出現在 Intel 平台上;Xen 對資源使用計算得夠好,足以促成 AWS 與雲端運算革命。有處理器支援時可以做得更乾淨——Intel 自 2006 年起以 VT-x 提供。

VM 的安全宣稱某種程度上建立在「VMM hypervisor 的程式碼可以遠小於作業系統、因而更容易做程式碼審查與加固」的論證上;至於是否真的漏洞較少,當然是個經驗問題。

在客戶端,虛擬化讓人們能在主機作業系統上跑客體作業系統,這不只提供彈性,也帶來更好隔離的前景。例如員工可以在筆電上跑兩份 Windows——一份鎖定的辦公環境、另一份家用。三星的 Knox 在手機上創造一台雇主可鎖定並遠端管理的虛擬機,同時使用者在同一裝置上享有正常的 Android。

人們需要在多個 VM 之間分享資料,而如果他們用 USB 隨身碟與網頁郵件帳號這類臨時機制,就破壞了分隔。安全的資料分享遠非小事。

一個具體例子:Bromium 的每應用 VM 有多麻煩

Bromium(現屬 HP)在企業 PC 上提供為特定 app 量身打造的 VM:Office 一個 VM、Acrobat reader 一個、瀏覽器一個。這讓公司能相當安全地使用老舊、不再支援的軟體。

那麼你怎麼下載一份 Office 文件?

  1. 瀏覽器把檔案從它的 VM 匯出到主機硬碟,標記為「不可信」;
  2. 使用者試圖開啟它時,會拿到一台只有該文件加上 Office、別無他物的新 VM;
  3. 當他們接著用郵件寄出這份不可信文件時,有一個 Outlook 外掛阻止它在「寄件備份」窗格中被算繪。

當網路服務整合進 app 時事情更可怕:關於「哪些站點能存取哪些 cookie」的規則很複雜,而處理單一登入與跨多個域的工作流程很困難。剪貼簿也需要多得多的規則來控制。

許多規則不時改變,而且是啟發式而非硬性、可驗證的存取邏輯。

簡言之,在客戶端用 VM 做分隔,若要對使用者顯得透明,就需要與作業系統及各 app 深度整合,而且安全與可用性之間有大量取捨。 實際上,你是在把虛擬化事後加裝到並非為它而建的作業系統與 app 上。

容器是 2010 年代末的熱門新話題,常被(尤其是行銷人員)與虛擬化混淆。

容器實作藉由虛擬化作業系統機制的一個子集(行程識別碼、行程間通訊與命名空間)來分隔行程群組,也使用沙箱與系統呼叫過濾等技術。

商業誘因是把客體的大小、互動複雜度與管理成本降到最低,所以它們與編排工具一起部署。

如同任何其他新技術,有許多熱情多於經驗的新創。2019 年 Jerry Gamblin 的一份調查揭露:Docker Hub 上提供給開發者的前 1000 個容器中,有 194 個把 root 密碼設成空白。

如果你要用雲端系統,你必須認真注意工具的選擇,並學會另一套存取控制機制——服務供應商提供的那套,例如 AWS 的身分與存取管理(IAM)。這又多了一層複雜性,而人們會搞錯。

例如 2019 年一家為銀行與警方提供生物辨識服務的資安公司,把整個資料庫毫無保護地留在外面;兩名研究者用 Elasticsearch 找到它,發現數百萬人的照片、指紋、密碼與安全查核等級——而且他們不只能讀,還能寫。

但即使你把雲端系統鎖得再緊,分隔機制能達成的也有硬體上的限制。2018 年發表了兩類強大的旁通道攻擊:MeltdownSpectre

那些用容器部署支付處理的銀行,至少隱含地倚賴「在 Amazon 或 Google 那種規模的雲中,它們的容器難以被鎖定」。

硬體保護#

多數存取控制系統不只想控制使用者能做什麼,也想限制程式能做什麼。

  • 保護問題(protection problem):防止一個行程干擾另一個。
  • 拘限問題(confinement problem):防止程式透過授權通道以外的方式對外通訊。

兩者都有幾種風味。目標可能是防止主動干擾(例如覆寫記憶體),或阻止一個行程直接讀另一個的記憶體——這是商業作業系統設定的目標。軍事系統可能還想保護中繼資料,例如讓使用者無法查出還有哪些使用者登入系統、或他們在跑什麼行程。

除非使用沙箱技術(對一般程式設計環境而言太受限),在單一處理器上解決保護問題至少意味著要有一個能阻止一個程式覆寫另一個程式之程式碼或資料的機制

這通常意味著硬體存取控制必須與處理器的記憶體管理功能整合

一個經典機制是分段定址(segment addressing):記憶體由兩個暫存器定址——指向記憶體某段的段暫存器,與指向該段內某位置的位址暫存器。段暫存器由作業系統控制,通常由一個稱為**參考監視器(reference monitor)**的元件負責,它把存取控制機制與硬體連結起來。

實作隨處理器本身變複雜而變複雜:

  • 早期 IBM 大型主機有雙態 CPU:機器要嘛在授權狀態、要嘛不在。不在時程式被限制在作業系統配置的記憶體段中;在的時候它可以隨意寫入段暫存器。授權程式就是從授權函式庫載入的程式。

    給定合適的授權函式庫,任何想要的存取控制政策都能建在這之上,但這不總是有效率;而系統安全取決於把壞程式碼(不論惡意還是有臭蟲)擋在授權函式庫外。

  • Multics(1960 年代 MIT 開發、啟發了 Unix 的作業系統)導入表達不同權限層級的保護環(rings of protection):ring 0 程式對磁碟有完整存取權,supervisor 狀態跑在 ring 2,使用者程式碼跑在各種較低權限層級。它的許多特性被較新的處理器採用。

Intel 處理器#

早期 PC 用的 Intel 8088/8086 處理器沒有系統模式與使用者模式的區分,因此任何執行中的程式都控制整台機器。

  • 80286 加入了受保護的分段定址與環,PC 首次能跑正經的作業系統。
  • 80386 內建虛擬記憶體,記憶體段大到(4 GB)可以忽略,把機器當成 32 位元平坦位址機器。
  • 486 與 Pentium 系列加入更多效能(快取、亂序執行與 MMX 等額外指令)。

保護環由若干機制支援:目前權限層級只能由 ring 0(核心)的行程改變;程序不能直接存取較低層級環中的物件,但有**閘(gates)**允許在不同權限層級執行程式碼並管理支撐基礎設施(例如多個堆疊段)。

Intel VT(2006 起的 x86 虛擬化硬體支援)

某些處理器架構(如 S/370 與 PowerPC)容易虛擬化,其理論要求 1974 年就由 Popek 與 Goldberg 確立:所有揭露原始處理器狀態的敏感指令都必須是特權指令。

然而原生 Intel 指令集有敏感的使用者模式指令,需要應用程式碼改寫與對受託管作業系統打補丁這類髒兮兮的變通做法。

在硬體中加入 VMM 支援意味著你可以讓作業系統照設計跑在 ring 0;VMM 在底下有自己的一份記憶體架構。你仍然要攔截敏感的 opcode,但系統呼叫不會自動需要 VMM 介入,你可以跑未修改的作業系統,速度更快、系統通常也更穩健。

SGX(Software Guard eXtensions,2015):讓可信程式碼跑在**飛地(enclave)**中——記憶體的一個加密區段——而其餘程式碼照常執行。

Intel 在可信計算倡議的早年就研究過這類架構,但讓事情擱置,直到它需要一個飛地架構來與 TrustZone 競爭。加密由**記憶體加密引擎(MEE)**執行,SGX 也導入新指令與記憶體存取檢查,以確保非飛地行程無法存取飛地記憶體(連 root 行程也不行)。

SGX 被推廣用於 DRM 與保護雲端 VM,特別是含密碼金鑰、憑證或敏感個資的那些;這受到 Spectre 與類似攻擊的威脅。

由於 SGX 的安全邊界是 CPU,其軟體在主記憶體中是加密的,這在時間與空間上都課以真實的代價。另一個缺點過去是 SGX 程式碼必須由 Intel 簽署——現在該公司已下放簽署(所以壞人也能取得程式碼簽章),而自 SGXv2 起將把信任根開放給他人。

於是人們正在實驗 SGX 惡意程式,它能維持不被防毒軟體偵測。 由於 SGX app 不能發系統呼叫,人們原本希望飛地惡意程式做不了太多壞事,然而 Schwarz、Weiser 與 Gruss 已想出如何從飛地對主機 app 發動隱蔽的返回導向程式設計(ROP)攻擊;他們主張問題在於「飛地應該做什麼」缺乏清晰界定,而任何合理的威脅模型都必須包含不可信的飛地。

Arm 處理器#

Arm 是手機、平板與 IoT 裝置中最常用的處理器核心;光在行動電話中就用了數十億顆,高階裝置的晶片組中有數十顆各種大小的 Arm 核心。

原始的 Arm(意為 Acorn Risc Machine)是第一個商業 RISC 設計,1985 年發布,就在 MIPS 之前。1991 年 Arm 成為獨立公司,與 Intel 不同,它不擁有也不營運任何晶圓廠:它授權一系列處理器核心,由晶片設計者納入其產品中。

  • 早期核心有 32 位元資料路徑與 15 個暫存器,其中 7 個被系統行程的備份暫存器影射,以降低中斷時切換情境的成本。有多個 supervisor 模式,處理快速與正常中斷、重置時進入的系統模式,以及各種例外處理。
  • 核心起初不含記憶體管理,所以基於 Arm 的設計可以大幅客製化其硬體保護;現在有帶記憶體保護單元(MPU)的變體,也有帶記憶體管理單元(MMU)、能處理虛擬記憶體的。
  • 2011 年 Arm 推出第 8 版,支援 64 位元處理並讓多個 32 位元作業系統可被虛擬化。Hypervisor 支援又加了一個 supervisor 模式。

TrustZone:支援前述「兩個世界」模型的安全擴充,2004 年提供給手機製造商。

TrustZone 支援一個給正常作業系統與一般用途應用的開放世界,加上一個處理密碼學與關鍵 I/O(在手機上這可包括 SIM 卡與指紋讀取器)等敏感操作的封閉飛地。處理器處於安全或非安全狀態,與它處於使用者模式或某個 supervisor 模式是正交的。封閉世界託管單一可信執行環境(TEE),有獨立的堆疊、簡化的作業系統,通常只跑由 OEM 簽署的可信程式碼。

雖然 TrustZone 2004 年就發布了,它一直封閉到 2015 年;OEM 用它保護自己的利益,除偶爾在保密協議下之外並不對 app 開發者開放。

而如同 Intel SGX,目前似乎還沒有辦法處理惡意的飛地 app——它們可能以 DRM 形式與遊戲 app 綑綁,或被威權國家強制要求;而且用 TrustZone 建立的飛地 app 會引發透明度與控制權的問題,這可能外溢到可稽核性、隱私與更多方面。

CHERI 是 Arm 最新的產品,為 Arm CPU 加入細粒度的能力支援。

目前 Chrome 這類瀏覽器把分頁放在不同行程中,好讓一個網頁的腳本跑得慢時不會拖慢其他分頁。如果每個網頁中的每個物件都能被個別沙箱化就太好了,但因為每次行程間情境切換的 CPU 週期成本很高,這做不到。

CHERI 讓一個孵出子執行緒的行程能分配給它對特定記憶體範圍的讀寫存取權,於是多個沙箱能跑在同一個行程中。

它 2018 年被宣布為產品,預期 2021 年見到首批矽晶片。這項技術的長期承諾是:如果它在 Windows、Android 或 iOS 這類作業系統中被徹底使用,本可以防止近年來多數的零時差漏洞利用。 大規模納入新的保護技術要花真錢,就像從 32 位元 CPU 換到 64 位元一樣,但它可以省下大量修補的成本。

會出什麼錯#

Android、Linux 與 Windows 這類熱門作業系統極為龐大複雜,其功能每天被數十億使用者在極多樣的情況下測試。許多臭蟲被找到,其中一些產生漏洞,而漏洞有典型的生命週期:

  1. 被發現後回報給 CERT 或廠商;
  2. 修補被發布;
  3. 修補被逆向工程,可能產生 exploit;
  4. 沒有及時套用修補的人可能發現自己的機器被攻陷。

少數情況下漏洞被立刻利用而非回報——稱為零時差利用(zero-day exploit),因為攻擊從漏洞已知存在的第零天就發生。

攻擊者目標的轉變#

傳統上攻擊者的目標是在系統上取得一個普通帳號、然後變成系統管理員,好完全接管系統。

  1. Windows PC 是最常見的線上裝置(直到 2017 年被 Android 超越),所以是最常見的攻擊目標;而由於它們把許多應用當成管理員跑,一個能被攻陷的應用通常就給出管理員存取權
  2. 攻擊者有兩種基本類型針對性攻擊者想監視特定個人,目標通常是取得該人帳號的存取權;規模攻擊者的目標通常是攻陷大量 PC 並組織成殭屍網路。這兩者都不需要管理員存取權。

然而殭屍網路牧人確實偏好安裝 rootkit(如其名所示以 root 執行),又稱遠端存取木馬(RAT)

在企業環境中 user/root 的區分仍然重要:你不希望這種工具組被敵對情報機關、企業間諜公司、或正在為一場大型詐欺做偵察的犯罪集團安裝為進階持續性威脅

另一個區分是漏洞是否可蠕蟲化(wormable)——它能否被用來在沒有人為介入的情況下把惡意程式在線上從一台機器快速散播到另一台。

Morris 蠕蟲是第一個大規模案例,此後還有許多。第 2 章提過的 WannaCry 與 NotPetya 使用了 NSA 開發後洩漏給其他國家行為者的漏洞。作業系統廠商對可蠕蟲化的漏洞反應迅速,因為它們能造成的損害規模巨大,通常會發布計畫外的修補。

撰稿時最麻煩的可蠕蟲化漏洞是 Mirai 的變體——一個用來接管使用已知 root 密碼之 IoT 裝置的蠕蟲。它 2016 年 10 月出現以攻陷監視攝影機,此後產生了數百個版本。

粉碎堆疊#

經典的軟體漏洞利用是記憶體覆寫攻擊,俗稱「粉碎堆疊(smashing the stack)」,1988 年的 Morris 蠕蟲就用了它——它感染了如此多 Unix 機器,以致擾亂了網際網路並強力地把惡意程式帶到大眾媒體的注意中。

涉及違反記憶體安全的攻擊,在 1990 年代末與 2000 年代初佔了針對作業系統之漏洞利用的遠超過一半,但此後比例一直緩慢下降。

原理:程式設計師常對檢查參數大小很草率,所以把長參數傳給程式的攻擊者可能發現其中一部分被當成程式碼而非資料處理。

Morris 蠕蟲的 finger 漏洞與「著陸墊」技巧

Morris 蠕蟲使用的經典例子是 Unix finger 指令的漏洞。一個常見實作會接受任意長度的參數,儘管程式只為該參數配置了 256 位元組。當攻擊者用更長的參數呼叫該指令時,參數尾端的位元組最後覆寫了堆疊並被系統執行。

通常的利用技巧是讓參數的尾端位元組成為一個著陸墊(landing pad)——一長段的無操作(NOP)指令,或其他不改變控制流的暫存器指令,其任務是在處理器執行到其中任何一個時把它接住。著陸墊把處理器送達攻擊程式碼,後者會做像是直接建立一個具管理權限的 shell 這樣的事。(原書圖 6.5 圖示此攻擊。)

這種攻擊比 1988 年早得多。

  • 1960 年代早期多數分時系統都有這個漏洞並修好了它。
  • 1970 年代初的滲透測試顯示,最常被使用的攻擊策略之一仍是「非預期的參數」。
  • Intel 的 80286 處理器在 1982 年導入了明確的參數檢查指令——verify read、verify write 與 verify length——但多數軟體設計者為避免架構依賴而迴避了它們。

堆疊覆寫攻擊在各種可程式化裝置上都被找到過——甚至包括智慧卡與硬體安全模組這類設計者真該更懂事的東西。

其他技術攻擊#

堆疊溢位是資料被當作輸入接受(例如一個 URL)、最後被當成機器碼執行。這些是型別安全的失敗。事實上堆疊溢位既可視為記憶體安全失敗,也可視為未能淨化使用者輸入,但兩類都有更純粹的例子。

  • 釋放後使用(use after free):現在是遠端執行漏洞最常見的成因,近年提供了大量對瀏覽器的攻擊。它可能發生在一塊記憶體被釋放後仍被使用時,或許是因為對「程式哪一部分負責釋放它」有混淆。若一個惡意區塊現在被配置,它可能在堆積上取代原位;而當一個舊的無害函數被呼叫時,一個新的惡意函數可能被喚起。

  • SQL 注入:最常見的「未能淨化輸入」攻擊,發生在粗心的網頁開發者把使用者輸入傳給後端資料庫而未檢查它是否含 SQL 程式碼。錯誤訊息往往洩漏天機,有能力且有動機的使用者可從中推論出足以發動攻擊的資訊。

    通常的補救是把所有使用者輸入都視為可疑並加以驗證。但這比看起來難,因為很難預料所有可能的攻擊,而為某個 shell 寫的過濾器可能不知道另一個 shell 中存在的擴充。可能的話,應該只在安全的情境中對使用者輸入採取行動,用設計把這類攻擊排除掉。

  • 競爭條件(race conditions):發生在交易分兩個或更多階段執行,第一階段驗證存取權、第二階段做某件敏感的事。若有人能在兩階段之間改變狀態,就可能導致攻擊。

    從 Unix mkdir 到容器 TOCTTOU

    一個經典例子出現在早期版本的 Unix,建立目錄的指令 mkdir 分兩步運作:先配置儲存空間,再把擁有權轉移給使用者。由於這兩步是分開的,使用者可以在背景啟動一個 mkdir,若它只完成第一步就被暫停,第二個行程就能用一個指向密碼檔的連結取代新建的目錄。然後原行程恢復,把密碼檔的擁有權改成該使用者。

    一個更現代的例子出現在容器中用來攔截應用程式對作業系統之系統呼叫的包裝器(wrappers)。這些包裝器在核心的位址空間中執行,檢視所有系統呼叫的進出狀態,只封裝安全邏輯。

    它們通常假設系統呼叫是不可分割的(atomic),但現代作業系統核心高度並行。系統呼叫彼此之間並非不可分割;兩個系統呼叫有許多可能競逐共享記憶體的存取,這產生了檢查時到使用時(TOCTTOU)攻擊

    一個早期(2007)的例子呼叫一個名稱剛好溢出頁面邊界一個位元組的路徑,導致核心在取頁時睡眠;然後它就把記憶體中的路徑換掉。

    此後還有更多。隨著每顆 CPU 晶片出貨的處理器越來越多、容器成為越來越常見的應用部署方式,這類攻擊可能會變成越來越大的問題。

  • 備份與復原系統的計時攻擊:讓使用者能自行復原檔案很方便,勝過必須打電話給系統管理員——但你要如何保護資訊資產不受一個時間旅行者傷害? 人們可以重新取得已被撤銷的存取權,還能玩更微妙的把戲。

  • 返回導向程式設計(ROP):許多現代系統試圖靠**資料執行防護(DEP)**來防止型別安全攻擊——把記憶體標記為程式碼或資料(這個措施可回溯到 Burroughs 5000);而如果所有程式碼都簽了名,你大概會以為未授權的程式碼無法被執行。

    這由使用常見共享函式庫 libc 提供已知小工具的 return-to-libc 攻擊演化而來;此後發展出許多變體,包括讓 SGX 飛地中的惡意程式對主機 app 發動隱蔽攻擊的攻擊。最新的變體區塊導向程式設計(BOP),往往能從程式模糊測試發現的當機中自動產生攻擊,擊敗目前的控制流完整性控制。

  • 旁通道:攻擊技術最近的重大創新以 CPU 管線行為為目標。2018 年初兩個改變賽局的攻擊開創了這個類型:Meltdown(利用 Intel 處理器上亂序執行創造的旁通道)與 Spectre(利用 Intel、AMD 與 Arm 處理器上的推測執行)。

    基本想法是:大型現代 CPU 的管線又長又複雜,會往前看並預期接下來十幾條指令,即使那些指令是目前行程不被允許執行的(想像存取檢查在兩條指令之後,而它將會禁止的讀取操作又在兩條之後)。

    未被走的那條路徑仍可能把資訊載入快取,從而以延遲的形式洩漏資訊。 用點巧思,一個行程可以安排讀取另一個行程的記憶體。

    雖然緩解措施已發布,同類型的進一步攻擊持續被發現,可能要好幾年與新一代處理器才能完全控制住它們

    這讓人想起 Roger Needham 的一句話:最佳化就是把一個能用的東西換成一個幾乎能用、但比較便宜的東西。 現代 CPU 被如此重度最佳化,我們必然會看到更多 Spectre 主題的變奏。

    這類攻擊限制了容器與 VM 所能提供的保護,也限制了 TrustZone 與 SGX 這類飛地機制。特別是,它們可能讓謹慎的公司不敢把高價值密碼金鑰託付給飛地,並延長老派硬體密碼設備的服役壽命。

使用者介面失敗#

攻打堡壘的常見方式是騙守衛幫你,作業系統也不例外。

最早的攻擊之一是特洛伊木馬——一個管理員被邀請執行、但含有討厭驚喜的程式。人們會寫遊戲檢查玩家是否為系統管理員,若是就建立另一個密碼已知的管理員帳號。

一個變體是寫一個與常見系統工具同名的程式(例如列出 Unix 目錄中所有檔案的 ls),設計成在喚起真正的工具之前濫用管理權限(如果有的話)。然後你向管理員抱怨那個目錄有問題;當他們進入該目錄打 ls 看看有什麼時,損害就造成了。

這是**混淆代理人問題(confused deputy problem)**的例子:若 A 代表 B 做某項任務,而 A 的權限同時來自 A 與 B,且 A 的權限超過 B,事情就可能出錯。

  1. Windows 永遠在跳出確認對話框,訓練人們把框點掉好把工作做完;
  2. 直到 2006 年,使用者必須是管理員才能安裝任何東西。

想法是把軟體安裝限制給管理員,能讓微軟的大型企業客戶(銀行與政府部門)鎖住系統,讓員工不能跑遊戲或其他未授權軟體。但在多數環境中,一般人需要安裝軟體才能把工作做完。

於是數億人擁有他們本不該需要的管理員權限,並在網站只是跳出一個框叫他們做點什麼時安裝了惡意程式碼。而這又被許多堅持自己的程式碼要以 root 執行的應用開發者加劇——不是出於懶惰,就是因為他們想蒐集不該蒐集的資料。

補救#

軟體安全並非全是壞消息;2000 年代情況大幅改善。 世紀之交時 90% 的漏洞是緩衝區溢位;到 2008 年第二版出版時只剩不到一半,現在更少。有幾件事造成了差別:

  1. 特定防禦

    • 堆疊金絲雀(stack canaries):編譯器在堆疊上返回位址旁插入的隨機數。若堆疊被覆寫,金絲雀有很高機率會改變。
    • 資料執行防護(DEP):把所有記憶體標記為資料或程式碼,防止前者被執行。2003 年隨 Windows XP 出現。
    • 位址空間佈局隨機化(ASLR):同時出現。藉由讓系統每個實例的記憶體佈局都不同,讓攻擊者更難預測目標位址。這在有 ROP 攻擊工具組(可繞過 DEP)的今天特別重要。
    • 控制流完整性機制:在編譯期分析可能的控制流圖,並在執行期驗證間接控制流轉移來強制它。2005 年出現。(然而分析並不精確,區塊導向程式設計攻擊正是為利用這些縫隙而演化出來的伎倆之一。
  2. 更好的通用工具:Coverity 這類靜態分析程式能找到大量潛在的軟體臭蟲並凸顯程式碼偏離最佳實務之處;若從專案一開始就使用,能造成很大的差別。(若後來才加入,可能拋出數千個難以處理的警示。)

  3. 更好的訓練:2002 年微軟宣布了一項安全倡議,讓每位程式設計師都接受如何寫安全程式碼的訓練。(他們為此製作的書《Writing Secure Code》至今仍值得一讀。)其他公司跟進。

  4. 最新的取徑是 DevSecOps:敏捷開發方法論被擴充以允許非常快速的修補部署與事故回應,可能讓設計、編碼與測試的投入被瞄準到最緊迫的問題上。

程式應該只擁有它需要的權限——最小權限原則(principle of least privilege)。 軟體也應被設計成預設組態、以及一般而言最容易的做法,就是安全的做法。健全的架構是達成安全預設值與最小權限的關鍵。

然而許多系統出貨時帶著危險的預設值與髒亂的程式碼,把各式各樣的介面暴露給根本不該發生的 SQL 注入等攻擊。這涉及個人與企業誘因的失敗,也涉及教育不足與資安工具的糟糕可用性。

環境潛移#

許多安全失敗發生在環境變化破壞了安全模型時。 在初始環境中運作得當的機制,在更廣的環境中往往失敗。

Unix 五十年的「成功災難」

Unix 原本被設計為「單使用者的 Multics」(名字由此而來)。它接著成為實驗室裡一群有技術且可信賴的人共用單一機器的作業系統。在這個環境中,安全機制的功能主要是遏制錯誤——防止一位使用者的打字錯誤或程式當機刪除或覆寫另一位使用者的檔案。原始的安全機制對此目的相當足夠。

自 Ken Thompson 1969 年在貝爾實驗室開始這項工作以來的五十年間,Unix 被反覆擴充,卻未妥善考慮保護機制也需要如何擴充:

  • Berkeley 版本假設從單一機器擴充到「全在一個區網上、全在同一管理之下」的機器網路。
  • 網際網路機制(telnet、ftp、DNS、SMTP)原本是為安全網路上的大型主機而寫。主機是自主的,網路在安全協定之外,也沒有授權的轉移。所以 Berkeley 模型真正需要的遠端認證,根本沒有被支援。
  • Sun 的擴充(如 NFS)也來湊一腳,假設的是「一家公司有多個可信區網」。

我們只好把 Kerberos、TLS 與 SSH 這類協定當強力膠帶事後貼上去,把世界勉強黏在一起。

數十億支手機的到來——有時用 wifi、有時用行動網路通訊,跑著數百萬位作者寫的 app(多數自私,有些主動惡意)——讓安全工程師跑得越來越快才追得上。

網際網路現在有數十億支手機、數十億個 IoT 裝置、也許十億台 PC,以及數百萬個「管理者不只不合作、甚至可能彼此衝突」的組織:有競爭的公司、彼此鄙視的政治團體,以及互相交戰的民族國家。

使用者不再是「可信賴但偶爾無能」,而是大體上沒有技術——但有些既有能力又懷敵意。程式碼過去只是有臭蟲,現在外面有大量惡意程式碼。對通訊的攻擊過去是情報機關的權責範圍——現在青少年從網路下載攻擊工具就能發動它們,而且對它們如何運作毫無真正的概念。

小結#

存取控制機制在系統中的多個層次運作,從硬體往上經過作業系統與瀏覽器這類中介軟體到應用程式。

存取控制的主要功能是限制特定群組、使用者與程式所能造成的損害,不論那是出於錯誤還是惡意。

  • 部署最廣的例子是客戶端的 Android 與 Windows、伺服器端的 Linux;它們有共同的血統與許多架構上的相似之處。
  • 基本機制(以及它們的問題)無所不在。
  • 多數攻擊涉及對臭蟲的機會性利用;複雜、廣泛使用、或兩者兼具的產品,特別可能被找到漏洞並被轉化為 exploit。

許多技術被發展出來以減少實作錯誤的數量、讓產生的臭蟲較不可能導致漏洞、並讓漏洞較難被轉成 exploit;但大型軟體系統的整體可靠度只是緩慢地改善。

研究問題#

存取控制中多數議題在 1960 年代或 1970 年代初就被指認出來,並在 Multics 與 CAP 這類實驗系統上被解決了。此後存取控制系統的研究,許多涉及在手機這類新情境中重做那些基本主題。

近期的研究線索包括飛地,以及為加入更細粒度存取控制的 CHERI 機制。另一個問題是:開發者將如何有效地使用這類工具?

作者在第二版中預測「未來幾年一個有用的研究題目,會是如何設計出不只穩健、而且對程式設計師與終端使用者都好用的存取控制機制」。近期 Yasemin Acar 等人的研究接下了這個題目,並把它發展成資安研究中成長最快的領域之一。

許多(若非多數)技術性資安失敗,至少部分肇因於開發者被期待使用之保護機制的糟糕可用性。 密碼學那一章已提過密碼 API 常誘使人們使用真正不安全的預設值(例如用 ECB 模式加密長訊息);存取控制同樣糟糕——任何冷不防面對 Windows 系統、或 Intel 與 Arm CPU 之存取控制機制的人都會發現這點。

作為一道開胃題,這裡有個新問題:我們能否把在技術層次(硬體、OS 或 app)所知的存取控制,擴展到組織層次?

二十世紀提出過若干安全政策,從 Bell-LaPadula 到 Clark-Wilson。既然我們現在有了有趣的技術類比,在這個深度外包與虛擬組織的世界裡,是不是該重新檢視這件事了?