.NET Framework Security

摘要:.NET Framework Security

.NET Framework Security
Code Access Security-應用程式篇
 
 
漫談程式安全性
 
   DOS時代,應用程式的安全性並未受到重視,這是因為當時個人電腦的使用者通常只有一位,加上電腦的不普及,應用程式多半對於安全性不會有太多的著墨,充其量也只是做到帳號的分級而已。在進入Windows時代後,這個情形並未有重大的改善,直到網路時代的來臨,安全性日趨受到重視,尤其在網路入侵及病毒橫行的今天,電腦不灌上防毒軟體及防火牆的話,簡直與拿刀擱在脖子上無異。雖然Windows提供了完整的帳號管理及權限控制,但因使用習慣的關係,縱使Windows在安裝時就提醒使用者,不要以Administrator做為日常操作電腦的帳號,還是有大多數人違背此一準則,這使得大多數的軟體操作都在不受限的OS中操作,這會有什麼壞處呢?舉一個簡單的例子,你撰寫了一套軟體,該軟體使用了許多外購的組件,當你更新其中一個組件時,你如何確認該組件沒有做不該做的事?一個利用Google來搜尋網路資料的組件,必定得在擁有網路存取權限下運作,但你如何肯定,該組件不會存取Google外的網站呢?不會將使用者的資料傳給其它網站呢?這個場景披露出在安全性管理中的一大漏洞,這點在現今的中大型軟體架構中更顯得駭人,身為程式設計師的我們,該如何防堵這個漏洞呢?
 
CAS,以Code為對象的安全機制
 
 在上面的例子中,我們明顯看出問題是出在安全性管理的細度上,原有的角色與使用者管控機制已難以防堵此一漏洞,必須將安全性管理落實到更細的點上,也就是程式碼本身。.NET Framework提供了CAS(Code Access Security)機制,允許程式設計師針對程式碼進行更細膩的軟體控制,以上面的例子來說,CAS可以控制該組件只能存取Google網站,當該組件存取其它網站時將會引發安全性例外,不只如此,CAS同時也可以限制該組件不能存取Registry,IO, 環境變數、資料庫,甚至是使用Unmanaged程式碼等等,在.NET Framework 2.0更可以限制其不能存取特定的記憶體區塊。概觀來看,CAS填補了角色與使用者安全機制的缺口,提供了更細膩的安全性管控,那麼CAS是如何運作的 呢?CAS建立在安全的基礎函式庫上,也就是Secure Library概念,用存取網路為例,當應用程式需要存取網路時,會利用.NET Framework所提供的Socket、Web、TCP等網路組件來存取網路,而這些組件會要求對應的權限,如DncPermission、 WebPermission、SocketPermission等等,因此假如設計者在呼叫該組件時降低了這些權限或是拒絕存取,Socket、Web、 TCP等網路組件在要求權限時就會產生例外,藉此建立以Code為對象的安全機制。以上的說明點出了一個重點,假如不是使用.Net Framework內建的網路組件呢?那麼CAS是否就崩潰了呢?你的憂慮是正確的,CAS建立於安全的基礎函式庫上,假如組件跨越了基礎函式庫,那麼他 就不在CAS的控制範圍內了,後面針對此問題會有更深入的討論。
 
建構安全的應用程式
 
 在 軟體日漸複雜及大型化的今日,一個應用程式不再是單一軟體公司所獨力完成的,其中或多或少都使用到了其它協力廠商所提供的函式庫或是組件,這加快了軟體的 開發速度,也增加了軟體安全性管理的困難度,如上面的例子,你如何確認所使用的組件是安全的呢?又該如何防堵組件的不正常運作呢?諸如此類的安全性憂慮, 在在的提醒程式設計師,該是正視應用程式安全性問題了,我們不能再將這些問題留給OS與使用者去解決,而是要起而行的將安全性加到設計軟體的流程中了,讓應用程式主體能在安全、穩固的環境中執行。要做到這點,程式設計師在使用一個組件時,應該審慎的思考,該組件所需擁有的權限與該防堵的權限,做出適當的調整。舉個例子來說,一個SMTP組件應該擁有存取網路的權限,但不應該擁有存取網頁、寫入特定目錄以外的目錄、讀取環境變數、存取資料庫、存取非指定網路IPPort的權限。
 
建構安全的組件
 
 除了由應用程式角度來思考外,組件設計者也必須從組件角度來思考,舉例來說,你寫了一個Email的組件,其中呼叫了Windows API來傳送電子郵件,因此該組件必須要擁有執行Unmanaged程式碼的權限,身為一個組件設計者,你得建立一個新的Permission,並要求呼叫者必須擁有此一權限才能呼叫,避免惡意的呼叫者藉由你的組件來攻擊別人。
 
識別,是安全控制的第一道防線
 
 識別,是安全控制的第一道防線,不管是建構安全的應用程式或是組件,辨識組件或應用程式的來源是首要任務,.NET Framework提供幾種識別組件來源的方式。
名稱
說明
Security.Permissions.PublisherIdentityPermission
以發行者為識別的權限管理。
Security.Permissions.SiteIdentityPermission
Site為識別的權限管理。
Security.Permissions.StrongNameIdentityPermission
StrongName為識別的權限管理。
Security.Permissions.UrlIdentityPermission
Url為識別的權限管理。
Security.Permissions.ZoneIdentityPermission
Zone為識別的權限管理。
不管使用何種識別方式,Strong Name(強式名稱)是最基本的要求,安全的應用程式應該避免使用未擁有Strong Name的組件。
 
內建Permission一覽
 
 .NET Framework內建了許多Permission供設計師使用,見表一。
表一   .Net Framework 內建的Permission物件
名稱
說明
Data.Odbc.OdbcPermission
使用ADO.NET ODBC Provider的權限。
Data.OleDb.OleDbPermission
使用ADO.NET OLE DB Provider的權限。
Data.SqlClient.SqlClientPermission
使用ADO.NET SQL Client Provider的權限。
Data.OracleClient.OraclePermission
使用ADO.NET Oracle Provider的權限。
Drawing.Printing.PrintingPermission
列印功能的權限控制。
Messaging.MessageQueuePermission
MSMQ 的權限控制。
Net.DnsPermission
存取DNS的權限。
Net.SocketPermission
使用Socket的權限。
Net.WebPermission
存取Web的權限。
Security.Permissions.EnvironmentPermission
存取環境變數的權限。
Security.Permissions.FileDialogPermission
使用檔案對話框的權限。
Security.Permissions.FileIOPermission
存取檔案的權限。
Security.Permissions.IsolatedStoragePermission
Isolated Storage存取的權限。
Security.Permissions.ReflectionPermission
使用Reflection的權限。
Security.Permissions.RegistryPermission
存取Registry的權限。
Diagnostics.EventLogPermission
存取事件記錄的權限。
Diagnostics.PerformanceCounterPermission
存取效能計數器的權限。
DirectoryServices.DirectoryServicesPermission
存取Activate Directory服務的權限。
ServiceProcess.ServiceControllerPermission
控制服務(Services)的權限。
Security.Permissions.SecurityPermission
一般性的安全權限,如ReflectionUnmanaged Code等等。
Security.Permissions.UIPermission
UI的存取權限。
Web.AspNetHostingPermission
ASP.NET Host 的權限。
PrincipalPermission
Windows帳號為識別的權限管理。
PrintingPermission
印表機的存取權限。
由於篇幅的關係,筆者無法於此篇文章中一一介紹這些Permission物件的用途及用法,僅選出其中幾個較常用的來介紹。這些Permission至少接受一個SecurityAction的參數,用來指定該Permission所允許的範圍,其定義如表2
2
說明
可套用的地方
LinkDemand
驗證僅發生於JIT編譯時,因此能得到較高的效率,但此一模式不適用於擁有介面的類別,因為直接由介面呼叫可以跨越此一限制。
類別、方法、屬性
InheritanceDemand
要求繼承者必需擁有此權限。
Demand
CAS要求此權限。
Deny
拒絕此權限。
PermitOnly
除此權限外,拒絕其它權限的要求。
Assert
這是一個特殊的權限,能在呼叫者未擁有對應權限時執行該權限所限制的動作,使用此一權限必須擁有Assertion權限。
RequestMinimum
只要求此一權限,其它的權限將被拒絕。
Assembly
RequestOptional
RequestMinimum搭配使用,也就是說,RequestMinmum要求FileIOPermission,除了FileIOPermission之外的權限要求都會被拒絕,假如以RequestOptional要求一個SocketPermission的話,那麼這個Assembly就允許兩種權限,FileIOPermissionSocketPermission
RequestRefuse
拒絕此一權限的要求。
 
小試身手,一個簡單的範例
 
 .NET Framework內建了這麼多的Permission, 我該在何時使用,又該如何使用她們來建構安全的應用程式呢?這個答案其實很簡單,只要你能接受撰寫應用程式時順便將安全性列入考量,那麼使用她們就只是直 覺性的反應罷了,舉個例子來說,當你拿到了一個Assembly,該Assembly擁有一個OpenFile的函式,其會讀取一個檔案,分析其內容,傳 回一個有意義的結果,照理來說,這個函式不應該有刪除檔案、或是存取其它目錄的權利,但不幸的是這個函式內容如程式1。
程式1
public void OpenFile(string fileName)
{
     System.IO.File.Delete(fileName);
}
不管是惡意還是疏忽,這個結果絕不是設計師所想要的結果,那麼該如何避免此結果呢?程式2使用的FileIOPermission可以避免這個結果的發生。
程式2
[System.Security.Permissions.FileIOPermission(System.Security.Permissions.SecurityAction.PermitOnly, Read = @"D:\Temp")]
private void OpenFile()
{
     ClassLibrary6.Class1 c = new ClassLibrary6.Class1();
     c.OpenFile(@"D:\Temp\0050.gif");               
}
當應用程式呼叫程式2的函式時,該函式將被限制在只能讀取D:\Temp目錄中的檔案,刪除、或是讀取其它目錄的動作都會被拒絕,這達到了我們的目的。
 
使用.NET Framework Configuration工具
 
 除了直接於程式中管控外,使用者也可以直接於本機上的.NET Framework設定工具中管理權限,兩者的不同之處在於程式內管控是主動防禦,不會因為使用者的疏忽導致安全性漏洞,但缺點則是彈性較低。反之.NET Framework設定工具採取的是被動式防禦,優點是彈性高,缺點就是容易因使用者疏忽導致破洞,當然!兩者並存是最好的方式。該工具位於系統管理工具中,執行畫面如圖1
1
.NET Framework內建三個原則:企業、電腦及使用者,每個原則下管理著一群Code Group(程式碼群組),每個程式碼群組繫結一個權限集合,整個展開如圖2。
圖2
以前一節的例子來說,假設OpenFile函式是位於Class1類別中,而Class1是位於ClassLibrary6這個Assembly中,由於ClassLibrary6是位於本機電腦中,因此要調整此Assembly權限的方式是在電腦中的程式碼群組內的My_Computer_Zone中新增一個程式碼群組,如圖3
3
輸入程式碼群組名稱後點選下一步選擇過濾條件。
4
ClassLibrary6是一個具備Strong Name(強式名稱)Assembly,因此此處就可以選擇以強式名稱做為過濾條件。
5
點選下一步後,開始指派權限集合給這一個程式碼群組,.Net Framework內建了數個權限集合供使用者套用,不過此處我們選擇建立一個新的權限集合。
6
輸入集合名稱後,接著要選擇要指派的權限,此處只允許ClassLibrary6讀取D:\Temp目錄下的檔案,如圖7
7
接著還得賦與可執行的權限給這個集合,否則將無法執行此一Assembly,如圖8
8
完成後還得做另一個動作讓此程式碼群組正常運作,點選ClassLibrary6這個Code Group,選擇內容,勾選如圖9的選項。
9
這個動作是將此Code Group限制於本身,不繼承My_Computer_Zone的完全信任權限。完成後ClassLibrary6就只能夠讀取D:\Temp中的檔案,無法寫入,也無法存取其它權限所限制的資源,如SocketRegistry等等。
 
限制網站存取
 
   前面提了一個網站存取的例子,那麼我們該如何利用內建的Permission來達到限制網站存取的目的呢?程式3利用了WebPermission將該函式限制於只能存取http://www.hinet.net網站。
程式3
[System.Net.WebPermission(SecurityAction.PermitOnly, ConnectPattern=@"http://www\.hinet\.net/")]
private void TestWeb(string url)
{
           System.Net.HttpWebRequest req =
 (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
}
想當然爾,所有存取Hinet網站以外的動作都會引發SecurityException
 
限制存取Registry
 
    使用RegistryPermission物件可以限制對於Registry的存取,如程式4
程式4
[RegistryPermission(SecurityAction.Deny,Read = @"HKEY_LOCAL_MACHINE\Software")]
private string TestRegistryPermission(string regPath,string key)
{
           RegistryKey r = Registry.LocalMachine.OpenSubKey(regPath);
           return (string)r.GetValue(key);                        
}
這樣該函式就無法存取HKLMSoftware下的所有Registry資訊。
 
限制使用Reflection
 
 使用ReflectionPermission可以控制Reflection的使用範圍,如程式5
程式5
[System.Security.Permissions.ReflectionPermission(SecurityAction.Deny, Flags = ReflectionPermissionFlag.TypeInformation)]            
private void TestReflectionPermission(object target)
{
           MethodInfo m =
target.GetType().GetMethod("GetHello",
BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.IgnoreCase);
           m.Invoke(target,null);
}
TypeInformation旗標代表著此函式無法使用Reflection來取得、或呼叫非公開函式。
 
限制Socket存取
 
   WebPermission僅能夠管控網站的存取動作,不能管控FTPSMTP等存取動作,SocketPermission可以達到此需求,如程式6
程式6
[System.Net.SocketPermission(SecurityAction.PermitOnly, Access = "Connect", Host = "61.219.38.89",Port = "80", Transport = "All")]
private void TestSocket(string ip)
{
           System.Net.Sockets.Socket socket =
new System.Net.Sockets.Socket(
System.Net.Sockets.AddressFamily.InterNetwork,                                   System.Net.Sockets.SocketType.Stream,
System.Net.Sockets.ProtocolType.Tcp);
           socket.Connect(new System.Net.IPEndPoint(System.Net.IPAddress.Parse(ip),80));
}
此函式無法存取61.129.38.89 80 Port以外的網路。
 
真的安全嗎?
 
 在前面提過,CAS建立在安全的函式庫上,這代表著不安全的函式庫仍然有破壞應用程式的可能性,用程式2的例子來看,之所以刪除檔案會被拒絕的原因是File物件在刪除檔案前會先要求FileIOPermission來確認是否有足夠的權限刪除檔案,那麼假如該函式庫不是使用File物件,而是直接以Unmanaged Code呼叫Windows API來刪除檔案呢?答案是FileIOPermission無法防堵此行為,你必須加上另一個權限物件來避免這種情況。
程式7
[System.Security.Permissions.FileIOPermission(System.Security.Permissions.SecurityAction.PermitOnly, Read = @"D:\Temp")]
[SecurityPermissionAttribute(SecurityAction.Deny, UnmanagedCode = false)]
private void OpenFile()
{
           ClassLibrary6.Class1 c = new ClassLibrary6.Class1();
           c.OpenFile(@"D:\Temp\0050.gif");                           
}
不過這個手法有一個漏洞,那就是當對象Assembly套用了SuppressUnmanagedCodeSecurityAttribute時,此限制將被忽略,要確實防堵此一漏洞的方式是在AssemblyInfo.cs中套用以下的Attribute。
[assembly: SecurityPermission(SecurityAction.RequestRefuse, UnmanagedCode = true)]      
你可能與我一樣好奇,在這種限制下,.NET Framework函式庫中的Unmanaged呼叫還能正常運作嗎?答案是可以的。
 
Assert
 
 前面曾提過,Assert可以讓被呼叫者執行呼叫者所沒有的權限,簡單的說就是呼叫者雖然不允許Unmanaged Code的執行,但被呼叫者使用了Assert來允許Unmanaged Code執行,那麼這個動作將會正常的運作,防堵此一漏洞的方法是在呼叫者的AssemblyInfo.cs中宣稱此一呼叫者禁止使用Assert
[assembly: SecurityPermission(SecurityAction.RequestRefuse, UnmanagedCode = true , Assertion = true)]
 
.NET 2.0 Beta 2
 
   .NET 2.0 B2中新增了幾個Permission,下表列出她們,日後有機會筆者再詳細介紹她們。
名稱
用途
SqlNotificationPermission
管理使用Sql Server 2005所新增的Notification功能權限。
SmtpPermission
SMTP權限控制。
NetworkInformationPermission
管理使用.NET B2中新增的NetworkInformation中組件的權限。
GacIdentityPermission
Gac為識別的權限管理。
DataProtectedPermission
使用DDAPI的權限。
 
讓安全性成為直覺思考的一部份
 
   不可否認,在程式中加入安全性控制是繁雜的工作,但是在現今的網路環境中,程式設計師實在不能再置身事外了,將安全性觀念加到寫程式的流程中,已經是不能再拖延的工作了。