摘要:.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組件應該擁有存取網路的權限,但不應該擁有存取網頁、寫入特定目錄以外的目錄、讀取環境變數、存取資料庫、存取非指定網路IP及Port的權限。
建構安全的組件
除了由應用程式角度來思考外,組件設計者也必須從組件角度來思考,舉例來說,你寫了一個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 | 一般性的安全權限,如Reflection、Unmanaged 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就允許兩種權限,FileIOPermission及SocketPermission。 | “ |
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中的檔案,無法寫入,也無法存取其它權限所限制的資源,如Socket、Registry等等。
限制網站存取
前面提了一個網站存取的例子,那麼我們該如何利用內建的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); } |
這樣該函式就無法存取HKLM的Software下的所有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僅能夠管控網站的存取動作,不能管控FTP、SMTP等存取動作,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的權限。 |
讓安全性成為直覺思考的一部份
不可否認,在程式中加入安全性控制是繁雜的工作,但是在現今的網路環境中,程式設計師實在不能再置身事外了,將安全性觀念加到寫程式的流程中,已經是不能再拖延的工作了。