CLR中程式碼訪問安全檢測實現原理(轉)

RegisterForBlog發表於2007-09-19
CLR中程式碼訪問安全檢測實現原理(轉)[@more@]

  在傳統的作業系統級安全模型中,安全管理的粒度都是 Principal-based 層面的。使用者從認證登陸成功開始,就獲得此帳號的所有許可權,而其執行的程式,也自動被授予帳號及其所在組的所有許可權。例如我在《DACL, NULL or not NULL》一文中介紹的,NT 使用者從登陸到系統建立 Session 開始,就預設使用相同許可權,新建程式自動獲得父程式的許可權,除非程式本身手動進行限制。而 *nix 系統下面的思路也是類似,只不過從 Token 程式設計了各種 uid/gid 等等。

  這種 Principal-based 的安全模型,在以主機為中心的孤立環境中是非常合適的,而且足夠簡單和高效。但隨著網路的普遍使用,這種安全模型開始受到挑戰,最直接的就是如何處理從網路上執行程式的策略問題。按照現有模型,所有程式都會自動獲得最大許可權集,但這顯然是不現實的,安全管理粒度過於粗放。

  因此在 Java 和 IE 等涉及網路的應用程式中,提出了新的基於位置的安全模型。一個程式執行時獲得的許可權,並不由其父程式或者說宿主來決定(這些程式往往用於較高許可權),而是由其程式所在位置覺得具有多少許可權。Java 中將之簡化為對程式碼源的安全策略限定,如在策略檔案中指定所有來自 www.nsfocus.com 的程式都有寫 c: emp 目錄的許可權,而來自其他網址的程式只能讀取 c: emp 目錄內容。而 IE 中則更進一步,將這些來源分類為本機(my computer)、內網(intranet)、外網(internet)、可信站點(trusted)和不可信站點(untrusted)。

  如果說 Principal-based 的安全模型中,關鍵因素是:我是誰(當前帳號)、我要訪問什麼(目標資源)、我要怎麼訪問(操作型別);則在 基於位置的安全模型中變成了:我來自哪裡(程式碼來源)、我要訪問什麼(目標資源)、我要怎麼訪問(操作型別)。關鍵因素雖然只有一個我是誰到我來自哪裡的轉變,但其控制粒度能夠大大提升。

  但是這樣的基於位置的安全模型還是存在其問題,因為元件之間的可信程度是不同的。例如一個本機控制元件因為來源於本機,受到系統的信任,被賦予很高的許可權。同時一個惡意程式碼來源於不受信任的位置,無法執行某項操作。如果許可權限定完整的話,本來不會出現問題,但因為某些許可權依賴關係的管理混亂,造成惡意程式碼可以透過受信任程式碼執行本不應允許他執行的功能。這也是眾多 IE 相關漏洞的問題根本所在,其受到 IE 現有安全模型的單級信任機制的限制,註定無法徹底解決。

  要徹底解決此類問題,歸根結底必須建立一個可信鏈驗證機制,也就是說執行某個操作的時候,必須檢查此操作所有的上級操作元件是否擁有安全許可權,而不僅僅只檢測最後一級的元件。這一思路正是 CLR 中程式碼訪問安全檢測的設計思路,其關鍵因素增加了一個:呼叫我的人都有哪些,他們是否有相應許可權的檢測。

  例如我在建立一個檔案的時候,可以強制性檢測呼叫鏈上的所有元件是否都擁有操作此檔案的許可權:

  以下內容為程式程式碼:

  public void CreateFile()

  {

  // Create a new FileIO permission object

  FileIOPermission perm = new FileIOPermission(FileIOPermissionAccess.Write, @"C:SomeFile.txt");

  try {

  // Demand the FileIOPermission

  perm.Demand( );

  } catch (SecurityException se) {

  // Callers do not have necessary permission

  }

  // Method implementation...

  }

  FileIOPermission 類描述了我需要檢測的許可權,對 C:SomeFile.txt 檔案可寫;FileIOPermission.Demand() 則執行這一許可權的檢測工作,遍歷此方法呼叫鏈上的所有元件,檢測他們是否有次許可權。這樣一來就可以從理論上避免惡意程式碼透過呼叫可信元件執行越權操作的問題。具體的許可權定義和使用方法,這裡就不詳細介紹了。下面就這種檢測如何實現做一個結構上的簡要分析。

  System.Security.CodeAccessPermission

  System.Security.Permissions.EnvironmentPermission

  System.Security.Permissions.FileDialogPermission

  System.Security.Permissions.FileIOPermission

  System.Security.Permissions.ReflectionPermission

  System.Security.Permissions.RegistryPermission

  System.Security.Permissions.SecurityPermission

  System.Security.Permissions.UIPermission

  System.Security.Permissions.IsolatedStoragePermission

  System.Security.Permissions.IsolatedStorageFilePermission

  System.Security.Permissions.StrongNameIdentityPermission

  System.Security.Permissions.PublisherIdentityPermission

  System.Security.Permissions.SiteIdentityPermission

  System.Security.Permissions.UrlIdentityPermission

  System.Security.Permissions.ZoneIdentityPermission

  為進行程式碼訪問許可權檢測,CLR 預設定義了以上這些許可權型別。首先他們都是從 CodeAccessPermission 型別繼承出來,其次就其意義可進一步分為程式碼訪問許可權和程式碼身份許可權。程式碼訪問許可權定義程式碼將如何去訪問資源,如讀寫檔案、彈出對話方塊等等;程式碼身份許可權則定義訪問此資源的元件必須符合什麼樣的身份,如只能是本地檔案、或者只能是 nsfocus.com 釋出的元件等等。

  雖然許可權種類眾多,但各種子類只負責定義自身許可權的特性以及如何對自身許可權驗證,而所有的呼叫鏈遍歷和驗證工作,都是由 CodeAccessPermission.Demand() 方法完成的:

  以下內容為程式程式碼:

  public void CodeAccessPermission.Demand()

  {

  CodeAccessSecurityEngine engine = SecurityManager.GetCodeAccessSecurityEngine();

  if ((engine != null) && !this.IsSubsetOf(null))

  {

  StackCrawlMark mark = StackCrawlMark.LookForMyCallersCaller;

  engine.Check(this, ref mark);

  }

  }

  可以看到 CodeAccessPermission.Demand 方法,實際上是將驗證操作轉發給安全管理器 SecurityManager 的程式碼訪問安全引擎 CodeAccessSecurityEngine 型別的 Check 方法完成的。

  以下內容為程式程式碼:

  internal class CodeAccessSecurityEngine

  {

  internal virtual void Check(CodeAccessPermission cap, ref StackCrawlMark stackMark)

  {

  if (!PreCheck(cap, null, 1, ref stackMark, PermissionType.DefaultFlag))

  {

  Check(PermissionToken.GetToken(cap), cap, ref stackMark, -1, ((cap is IUnrestrictedPermission) ? 1 : 0));

  }

  }

  internal virtual void Check(CodeAccessPermission cap, ref StackCrawlMark stackMark, PermissionType permType)

  {

  int num1 = 0;

  if (CodeAccessSecurityEngine.GetResult(permType, out num1))

  {

  return;

  }

  if (this.PreCheck(cap, null, 1, ref stackMark, permType))

  {

  CodeAccessSecurityEngine.SetResult(permType, num1);

  return;

  }

  this.Check(PermissionToken.GetToken(cap), cap, ref stackMark, -1, ((cap is IUnrestrictedPermission) ? 1 : 0));

  }

  [MethodImpl(MethodImplOptions.InternalCall)]

  private void Check(PermissionToken permToken, CodeAccessPermission demand, ref StackCrawlMark stackMark, int checkFrames, int unrestrictedOverride);

  }

  CodeAccessSecurityEngine 內部類的 Check 方法,將最終呼叫透過 Unmanaged 程式碼實現的內部方法進行安全檢測。rotor 中的 COMCodeAccessSecurityEngine 型別 (ComCodeAccessSecurityEngine.cpp) 實現了這個檢測邏輯。

  COMCodeAccessSecurityEngine::Check 函式 (ComCodeAccessSecurityEngine.cpp:683) 透過呼叫 COMCodeAccessSecurityEngine::CheckInternal 函式 (ComCodeAccessSecurityEngine.cpp:697) 填充一個堆疊遍歷請求結構 CasCheckWalkData 的內容,最終將請求轉發給 StandardCodeAccessCheck 函式 (ComCodeAccessSecurityEngine.cpp:563) 完成檢測。此結構的指標將作為堆疊遍歷回撥函式的引數傳遞給回撥函式進行實際許可權驗證,而 StandardCodeAccessCheck 只是負責呼叫全域性堆疊遍歷支援 StackWalkFunctions 函式(StackWalk.cpp:512),以 CodeAccessCheckStackWalkCB 函式 (ComCodeAccessSecurityEngine.cpp:449) 為回撥函式,以 CheckInternal 函式填充的 CasCheckWalkData 結構為引數,透過現成的堆疊遍歷支援 Thread::StackWalkFrames 完成堆疊遍歷。

  透過堆疊遍歷實現程式碼訪問安全檢測呼叫流程如下:

  以下為引用:

  CodeAccessSecurityEngine::Check 內部呼叫定義,由下面的函式實現

  COMCodeAccessSecurityEngine::Check 轉發檢測請求 (ComCodeAccessSecurityEngine.cpp:683)

  COMCodeAccessSecurityEngine::CheckInternal 填充 CasCheckWalkData 結構 (ComCodeAccessSecurityEngine.cpp:697)

  StandardCodeAccessCheck 執行堆疊遍歷

  Thread::StackWalkFrames 遍歷當前執行緒堆疊

  CodeAccessCheckStackWalkCB 檢測當前元件許可權 (ComCodeAccessSecurityEngine.cpp:449)

  因此現在 CAS 檢測的問題被分為兩個部分:如何遍歷呼叫堆疊;如何檢測某個元件是否擁有許可權。

  過於如何遍歷呼叫物件,因為涉及到比較複雜的堆疊幀型別處理,這裡

  

·上一篇:

·下一篇:
 
     最新更新
·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·

·


| | | | | | |

Copyright © 2004 - 2007 All Rights Reserved

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10763080/viewspace-970148/,如需轉載,請註明出處,否則將追究法律責任。

CLR中程式碼訪問安全檢測實現原理(轉)
請登入後發表評論 登入
全部評論

相關文章