.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
同時也可以限制該組件不能存取
Registr
y
,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
程式碼的權限,身為一個組件設計者,你得建立一個新的
Permissio
n
,並要求呼叫者必須擁有此一權限才能呼叫,避免惡意的呼叫者藉由你的組件來攻擊別人。
識別,是安全控制的第一道防線
識別,是安全控制的第一道防線,不管是建構安全的應用程式或是組件,辨識組件或應用程式的來源是首要任務,
.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
內建了這麼多的
Permissio
n
,我該在何時使用,又該如何使用她們來建構安全的應用程式呢?這個答案其實很簡單,只要你能接受撰寫應用程式時順便將安全性列入考量,那麼使用她們就只是直覺性的反應罷了,舉個例子來說,當你拿到了一個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
中新增了幾個
Permissio
n
,下表列出她們,日後有機會筆者再詳細介紹她們。
名稱
|
用途
|
SqlNotificationPermission
|
管理使用
Sql Server 2005
所新增的
Notification
功能權限。
|
SmtpPermission
|
SMTP
權限控制。
|
NetworkInformationPermission
|
管理使用
.NET B2
中新增的
NetworkInformation
中組件的權限。
|
GacIdentityPermission
|
以
Gac
為識別的權限管理。
|
DataProtectedPermission
|
使用
DDAPI
的權限。
|
讓安全性成為直覺思考的一部份
不可否認,在程式中加入安全性控制是繁雜的工作,但是在現今的網路環境中,程式設計師實在不能再置身事外了,將安全性觀念加到寫程式的流程中,已經是不能再拖延的工作了。
v