存取控制的一般議題#

在客戶端-伺服器模型中,雙方建好安全通道後,客戶端就能發出請求,由伺服器對其控制的資源執行操作——典型情境是物件伺服器:請求通常是呼叫某特定物件的方法,而只有當客戶端擁有足夠的**存取權(access rights)**時,請求才能被執行。

術語上,驗證存取權稱為存取控制(access control),授予存取權則稱為授權(authorization)。兩者關係緊密,經常混用。

理解存取控制的各種議題時,一般採用一個簡單模型:主體(subject)發出請求,要求存取物件(object)

  • 物件封裝自己的狀態並實作其上的操作,操作透過介面對外提供。
  • 主體最好想成代表使用者行事的行程,但也可以是需要其他物件服務才能完成工作的物件。

控制對物件的存取,就是保護物件不被無權的主體呼叫方法,也可能涵蓋物件管理層面(建立、更名、刪除物件)。保護通常由一支稱為**參考監視器(reference monitor)**的程式落實:它記錄哪個主體可以做什麼,並在每次物件被呼叫時(例如由底層的可信作業系統呼叫它)裁決該操作是否允許。

圖 9-25:控制物件存取的一般模型。

參考監視器本身必須防篡改(tamperproof)——攻擊者絕不能有辦法對它動手腳。

存取控制矩陣#

主體對物件存取權的常見模型是存取控制矩陣(access control matrix):每個主體一列、每個物件一行,元素 M[s,o] 精確列出主體 s 可對物件 o 請求執行的操作。主體 s 請求呼叫物件 o 的方法 m 時,參考監視器檢查 m 是否列在 M[s,o] 中,沒有就拒絕。

系統可能有數千使用者、數百萬個需要保護的物件,而單一主體通常只會存取相對少數的物件——矩陣絕大多數元素是空的,把它實作成真正的矩陣並不可行。兩種有效率的實作:

  • 存取控制清單(Access Control List, ACL):把矩陣按行分散到各物件——每個物件維護一份「哪些主體有哪些權利」的清單,空元素直接省略。
  • 能力(capability):把矩陣按列分散到各主體——每個主體攜帶一份它對各物件持有的能力清單;一筆能力就對應矩陣中的一個元素,沒有某物件的能力就等於對它沒有任何存取權。

能力好比一張入場券:持有者獲得票面所載的權利,因此票必須防止持有者竄改。特別適合分散式系統的一種做法,是用簽章保護能力(清單),這在 Amoeba 系統中被廣泛應用(Tanenbaum et al., 1990),細節留到安全管理一節。

兩者在使用上的差異:

  • 用 ACL 時,客戶端送出請求,伺服器的參考監視器檢查它是否認識這個客戶端、且該客戶端是否被允許執行所請求的操作。
  • 用能力時,客戶端把請求連同能力一起送出,伺服器不在乎客戶端是誰——能力本身就說明了一切,只需檢查能力是否有效、所請求的操作是否列在能力中。

圖 9-26:保護物件時,ACL 與能力兩種做法的比較。

保護網域#

ACL 與能力清單省掉了空元素,但若不採取進一步措施,清單仍可能非常龐大。縮減的一般手法是保護網域(protection domain):形式上,保護網域是一組(物件, 存取權)對,每一對指明對某物件允許執行哪些操作(Saltzer and Schroeder, 1975)。請求一律在某個網域內發出,參考監視器先查出該請求關聯的保護網域,再依網域裁決。保護網域有幾種用法:

  • 使用者群組:例如公司內部網站的網頁應開放給所有員工。與其在 ACL 中為每位員工加一筆,不如建立 Employee 群組;參考監視器只需檢查存取者是否為員工,群組成員名單另外維護(當然也要防未授權存取)。
  • 階層式群組:組織在阿姆斯特丹、紐約、舊金山有三個分部,可把 Employee 細分為各城市子群組。全體員工都能讀內部網頁,但修改阿姆斯特丹分部的網頁只允許 Employee_AMS 的某個子集。優點是群組成員管理容易、能有效率地構造超大群組;缺點是若成員資料庫是分散式的,查找成員可能相當昂貴(例如要確認 Dick 是否為員工,得逐一查 Employee_AMS、Employee_NYC、Employee_SF)。
  • 主體攜帶憑證:與其讓參考監視器包辦查找,不如讓主體攜帶一張列出其所屬群組的憑證(certificate)——Dick 讀內部網頁時交出「我是 Employee_AMS 成員」的憑證。憑證須以數位簽章等手段防偽、防竄改。這類憑證與能力相當類似。
  • 角色(role):在角色型存取控制(role-based access control)中,使用者總是以某個特定角色登入,角色通常對應其在組織中的職務(Sandhu et al., 1996)。一人可以身兼多職——Dick 可能同時是系主任、專案經理、人事甄選委員——登入時選的角色決定他獲得的權限,也就是他將在哪個保護網域(群組)中運作。角色設計也應允許使用者必要時切換角色(例如 Dick 從系主任切換為專案經理);這種切換若只用群組來實作保護網域,將很難表達。

圖 9-27:把保護網域階層式地組織成使用者群組。

效率還能再提升:把物件也依其提供的操作(階層式地)分組,例如按介面分組,並可用子型別(subtyping,也稱介面繼承)構成階層。此時參考監視器先查出操作屬於哪個介面,再檢查主體是否可呼叫該介面的操作,而不是針對個別物件檢查。保護網域與物件分組可以並用——Gladney (1997) 便結合兩者,加上特定資料結構與受限操作,為數位圖書館的超大型物件集合實作 ACL。

防火牆#

前面的保護手段(密碼技術+存取控制矩陣的某種實作)在所有通訊方都遵守同一套規則時運作良好,例如與外界隔絕的獨立分散式系統。可是一旦允許外部人士存取系統資源(收發郵件、下載檔案、上傳報稅表單……),就需要不同的做法:以一種特殊的參考監視器——防火牆(firewall)——控制外界對分散式系統任何部分的存取(Cheswick and Bellovin, 2000; Zwicky et al., 2000)。

防火牆把分散式系統的某部分與外界隔開:所有外送、尤其是所有進入的封包,都先經過一台特殊電腦檢查,未獲授權的流量直接丟棄。

防火牆自身必須受到嚴密保護、抵禦任何安全威脅:它絕不能失效

防火牆基本上有兩種(常常合併使用):

  • 封包過濾閘道(packet-filtering gateway):以路由器的角色運作,根據封包標頭中的來源與目的位址決定放行與否。通常外側 LAN 的閘道過濾進入封包、內側 LAN 的閘道過濾外送封包。例如:要保護內部 Web 伺服器不被外部主機請求,就丟棄所有寄往該伺服器的進入封包。更細緻的應用:公司多個 LAN 經 SMDS 之類的網路相連時,各 LAN 的過濾閘道設定成只放行來自其他自家 LAN 的流量,就構成一個私有虛擬網路。
  • 應用層閘道(application-level gateway):不只看標頭,而是實際檢查進出訊息的內容。例如郵件閘道丟棄超過一定大小的進出郵件,更精緻的還能過濾垃圾郵件;又如數位圖書館的閘道允許外部存取但只提供文件摘要,想看更多就啟動電子付款協定,牆內使用者則可直接存取。

圖 9-28:防火牆的一種常見實作方式。

一種特殊的應用層閘道是代理閘道(proxy gateway):作為特定應用的前端,只放行符合特定條件的訊息。以瀏覽網頁為例,許多網頁夾帶要在瀏覽器裡執行的 script 或 applet;為了阻止這類程式碼進入內部 LAN,可以讓所有 Web 流量都經過 Web 代理閘道——它對使用者而言就像一台普通 Web 伺服器,接受牆內外的 HTTP 請求,但會過濾所有進出流量,丟棄特定請求與頁面,或改寫含可執行程式碼的頁面。

安全的行動程式碼#

第 3 章談過,現代分散式系統的重要發展之一是能在主機間遷移程式碼而不只是被動資料。但行動程式碼帶來嚴重的安全威脅,方向有二:

  • 保護代理程式:把代理程式(agent)送上網際網路時,擁有者要防範惡意主機竊取或竄改代理程式攜帶的資訊。
  • 保護主機:主機要防範惡意代理程式。多數使用者不是系統專家,無從判斷抓來的程式是否可信;很多時候連專家都難以察覺程式正在被下載。惡意程式一旦落腳,就能輕易危害主機。這是一個存取控制問題——重點不是阻止下載,而是讓行動程式碼能以彈性但完全受控的方式存取本地資源

保護代理程式#

考慮一個代理使用者尋找奈洛比到馬林迪最便宜機票、獲授權一找到就訂位、身上帶著電子信用卡的行動代理程式。它每到一台主機,該主機都不該能偷走信用卡資訊;也要防止竄改——例如 Chuck 廉價包機公司若看出代理程式還沒拜訪過更便宜的競爭對手 Alice 航空,不該有辦法改掉代理程式讓它跳過 Alice 航空。其他需要防範的攻擊還包括惡意銷毀代理程式,或把它改造成回家後反咬主人一口。

Ajanta 系統(Karnik and Tripathi, 2001)採取這條路線,提供三種機制讓擁有者偵測竄改:

  • 唯讀狀態(read-only state):一組由擁有者簽章的資料項。代理程式建構與初始化時(送出去之前),擁有者先算出訊息摘要、再用私鑰加密。代理程式抵達某主機時,該主機拿狀態對照簽章過的摘要,即可輕易偵測唯讀狀態是否被動過。
  • 只可附加的日誌(append-only log):讓代理程式在移動途中收集資訊——資料只能附加,任何移除或修改都逃不過擁有者的偵測。初始時日誌為空,關聯校驗和 C_init = K+_owner(N)K+_owner 是擁有者的公鑰,N 是只有擁有者知道的秘密 nonce)。伺服器 S 要交付資料 X 給代理程式時,把 X 附加到日誌、以簽章 sig(S,X) 簽署,並計算新校驗和 C_new = K+_owner(C_old, sig(S,X), S)。代理程式回家後,擁有者從日誌尾端開始,反覆以私鑰解開校驗和,每次迭代取得下一個校驗和、sig(S,X) 與 S,驗證日誌末端元素是否與簽章相符;相符就移除該元素、處理後繼續,直到回到初始校驗和,或發現某個簽章不符——即日誌已被竄改。
  • 選擇性揭露狀態(selective revealing of state):提供一個資料項陣列,每個項目指定給某台伺服器、以該伺服器的公鑰加密以確保機密性;整個陣列由擁有者簽章以確保整體完整性。任何項目被惡意主機改動,指定的伺服器都會發現並能採取行動。

Ajanta 也提供多種保護主機不受惡意代理程式攻擊的機制(詳見 Tripathi et al., 1999),其中許多與其他支援行動程式碼的系統相同,以下即討論這一面。

保護目標主機#

比起保護行動程式碼,更多的注意力放在保護主機。若把代理程式送出去太危險,使用者通常還有別的辦法完成工作;但要讓代理程式進來,除了完全拒之門外,往往別無選擇——所以一旦決定放行,使用者就需要完全掌控代理程式能做什麼。(面對惡意的外來代理程式,事後才「偵測到資源被蹂躪」為時已晚,必須事前保護所有資源不被下載程式碼未授權存取。)

沙箱(sandbox)是一種讓下載程式的每一條指令都能被完全控制的執行技術:程式企圖執行主機禁止的指令、或存取主機未允許的暫存器與記憶體區域時,執行立即停止。對原生執行檔實作沙箱並不容易——一種做法是下載時檢查執行碼,並為只能在執行期檢查的情況插入額外指令(Wahbe et al., 1993);對直譯式程式碼則簡單得多。

以 Java 為例(MacGregor et al., 1998):Java 程式由類別組成、沒有全域變數與函式,編譯成由 **Java 虛擬機器(Java Virtual Machine, JVM)**直譯的指令集,從 main 方法開始執行。Java 沙箱有三道防線:

  1. 類別載入器(class loader):負責從伺服器抓取指定類別並安裝到客戶端位址空間。因為類別載入器本身也是 Java 類別,下載的程式可能夾帶自己的載入器——沙箱的第一件事就是只使用可信的類別載入器,禁止下載程式建立自己的載入器來繞過正常的載入流程。
  2. 位元組碼驗證器(byte code verifier):檢查下載的類別是否遵守沙箱的安全規則——不含非法指令、不含可能破壞堆疊或記憶體的指令。只有從外部伺服器下載的類別才檢查;客戶端本機的類別一般直接信任(雖然其完整性也很容易驗證)。
  3. 安全管理器(security manager):類別安全下載並驗證後,JVM 開始執行其物件的方法,由安全管理器在執行期做各種檢查。要被下載的 Java 程式被強制使用安全管理器,無法繞過——例如所有 I/O 操作都要過審,安全管理器說不行就不執行。它扮演的正是前述參考監視器的角色。典型的安全管理器會禁止很多操作:幾乎都拒絕存取本地檔案、只允許程式連回它的來源伺服器;操弄 JVM 當然也不行;但允許使用繪圖函式庫、接收滑鼠移動與點擊等事件。

圖 9-29:Java 沙箱的組織方式。

原始的 Java 安全管理器實施相當嚴格的政策,不區分不同的下載程式、甚至不區分不同來源的伺服器。初期的沙箱模型在許多情況下過於受限,需要更多彈性——以下便是後來的替代做法。

與沙箱同路線但更有彈性的是遊樂場(playground)(Malkhi and Reiter, 2000):一台專門保留來執行行動程式碼的獨立機器。遊樂場本地的資源(檔案、對外部伺服器的網路連線)對其中執行的程式開放(仍受一般保護機制約束);但其他機器的本地資源與遊樂場實體隔離,下載的程式碼碰不到。其他機器的使用者以傳統方式(例如 RPC)存取遊樂場,而行動程式碼永遠不會被下載到遊樂場以外的機器。

圖 9-30:(a) 沙箱。(b) 遊樂場。

再進一步的彈性,是要求每個下載程式都能被驗證來源,再依來源施行特定的安全政策。驗證相對容易:程式碼跟其他文件一樣可以簽章(code signing)——這也常被當作沙箱的替代方案,效果是只接受來自可信伺服器的程式碼。難的是施行安全政策。Wallach et al. (1997) 針對 Java 程式提出三種機制:

  • 物件參考當作能力:程式要存取本地資源(如檔案),必須在下載時就被交付一個處理檔案操作的特定物件之參考;沒拿到參考就無法存取檔案。實作檔案系統的物件介面,靠「不發參考」對程式完全隱藏;Java 的強型別檢查保證執行期無法憑空構造這些介面的參考。此外可利用 Java 把變數與方法完全封在類別內部的特性——把建構子(constructor)設為 private——防止程式自行實體化檔案處理物件。
  • (擴充的)堆疊內省(stack introspection):對本地資源方法 m 的任何呼叫,之前都插入對特殊程序 enable_privilege 的呼叫,檢查呼叫者是否獲授權呼叫 m;獲准則給予呼叫期間的暫時特權,m 結束返回前再呼叫 disable_privilege 撤銷。與其要求本地資源介面的開發者自己插入這些呼叫,不如讓 Java 直譯器自動處理(Web 瀏覽器處理 applet 的標準做法):每次呼叫本地資源時直譯器自動呼叫 enable_privilege 檢查,獲准後把一個 disable_privilege 呼叫推上堆疊,確保方法返回時特權必被撤銷——惡意程式設計者無從繞過。
  • 名稱空間管理(name space management):程式要用本地資源,得先引入實作該資源的類別——給直譯器一個名稱,解析成類別後於執行期載入。針對不同來源的下載程式,同一個名稱可以解析成不同的類別,藉此施行來源相依的安全政策。名稱解析通常由類別載入器處理,需要改造它來實作這個做法。

圖 9-31:把 Java 物件參考當作能力使用的原理。

延伸:堆疊內省為何更強,以及語言相依的代價

用堆疊來檢查特權還有一個重要優點:能檢查呼叫鏈。假設程式呼叫本地物件 O1,O1 再呼叫 O2——即使 O1 有權呼叫 O2,若 O1 的呼叫者不被信任去呼叫 O2 的特定方法,這條呼叫鏈就不該放行。堆疊內省讓這種檢查很容易:直譯器只需從堆疊頂端逐框檢視,看是否存在一個已啟用適當特權的框(放行),或存在一個明確禁止存取當前資源的框(立即終止)。實質上,堆疊內省允許把特權附加在類別或方法上、並對每個呼叫者分別檢查,從而實作出以類別為基礎的保護網域(詳見 Gong and Schemers, 1998)。

圖 9-32:堆疊內省的原理。

不過,這一整套做法都靠 Java 直譯器施行政策,安全架構因此高度依賴語言,換一種語言就得重新開發。語言無關的解法(如 Jaeger et al., 1999)需要更一般化的安全施行手段,也更難實作:需要一個能察覺下載行動程式碼的安全作業系統,強制所有對本地資源的呼叫都經過核心,在那裡進行後續檢查。

阻斷服務#

存取控制通常在確保資源只被授權的行程存取;與之相關的一種特別惱人的攻擊,則是惡意阻止授權行程存取資源——阻斷服務(denial of service, DoS)攻擊。隨著分散式系統經由網際網路開放,防禦 DoS 日益重要。來自單一或少數來源的 DoS 通常能有效處理;難的是分散式阻斷服務(Distributed Denial of Service, DDoS):龐大數量的行程聯手打垮一個網路服務,攻擊者往往已劫持一大群機器,讓它們在不知情中參與攻擊。

Specht and Lee (2004) 區分兩類攻擊:

  • 頻寬耗竭(bandwidth depletion):對單一機器狂送訊息,讓正常訊息幾乎到不了接收者。
  • 資源耗竭(resource depletion):讓接收者把資源耗在無用的訊息上。著名例子是 TCP SYN-flooding:攻擊者發起大量連線(送出三向交握的 SYN 封包),之後卻永不回應接收者的確認。

沒有單一方法能防住 DDoS。攻擊者利用的是被偷裝軟體的無辜受害者——要根絕只能靠機器持續自我監控、檢查檔案是否被污染;但考慮到病毒在網際網路上傳播之容易,只靠這一招並不可行。

較好的做法是持續監控網路流量:

  • 從組織網路的**出口路由器(egress router)**做起:經驗顯示,丟棄來源位址不屬於本組織網路的外送封包,就能避免大量禍害。一般而言,越靠近來源過濾封包越好。
  • 也可以在**入口路由器(ingress router)**監控,但在入口才偵測到攻擊已太遲——網路多半已經被塞爆。更好的做法是讓網際網路更深處的路由器(例如 ISP 的網路)在懷疑攻擊發生時就開始丟包。Gil and Poletto (2001) 便採此路線:路由器發現「送往某節點的封包數」與「來自該節點的封包數」不成比例時即開始丟棄封包。

總之需要部署一整套技術,而新的攻擊仍持續出現。實務上的最新概況見 Mirkovic et al. (2005),詳細分類見 Mirkovic and Reiher (2004)。