.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
|