說明:本文中所有程序均在Windows 2000 Server中文版 + SP2上編譯運行無誤
開發環境:.Net 框架1.0 Version 1.0.3705
一、ASP.NET虛擬主機存在的重大隱患
我曾經在WWW.BRINKSTER.COM申請了一個免費的ASP.NET空間,上傳了兩個程序,
其中一個查看目錄和文件的程序證明我的判斷:ASP共享空間服務器存在的一個安全問題,在 ASP+ 共享空間服務器中依然存在並且變得更加難
以防範!通過這個程序我可以瀏覽所有用戶的ASP+程序,可以查看服務器的系統日誌……,當然,如果我想刪除什麼的話也不會有什麼問題。
爲了讓大家更清楚地瞭解這一問題,我們有必要簡單介紹一下ASP中就已經存在的這一問題。
ASP中常用的標準組件:
FileSystemObject,這個組件爲 ASP 提供了強大的文件系統訪問能力,可以對服務器硬盤上的任何有權限的目錄和文件進行讀寫、刪除、改名
等操作。FSO對象來自微軟提供的腳本運行庫scrrun.dll中。
使用下面的代碼就可以在ASP中創建一個FSO對象:
Set
fso = CreateObject("Scripting.FileSystemObject")
我們使用fso對象包含的屬性和方法,如Drive、Drives、Folder、Floders
、File、Files等對服務器的磁盤、目錄和文件進行讀、寫、刪除等操作。這一強大的文件系統訪問能力給ASP共享空間提供者帶來了嚴重的安
全問題,很多ASP空間的管理員都刪除此組件或將這個組件改名以避免用戶使用這一標準組件。刪除組件或組件改名確實是一個簡單的方法並且
也很有效,但是卻使廣大用戶無法使用它的強大的功能。網絡上還有一種看起來很美的方案,它允許用戶使用 FileSystemObject 組件又不影
響服務器的安全,即對每一個用戶都設置一個獨立的服務器用戶和單個目錄的操作權限。但是這種方法是有問題的。因爲ASP和ASP.NET中在這
方面的問題十分類似,所以我們將在ASP.NET的相應解決辦法部分詳加說明。
在ASP.NET中我們發現這一問題仍然存在,並且變得
更加難以解決。這是因爲.NET中關於系統IO操作的功能變得更加強大,而使這一問題更嚴重的是ASP.NET所具有的一項新功能,這就組件不需要
象ASP那樣必須要使用regsvr32來註冊了,只需將Dll類庫文件上傳到bin目錄下就可以直接使用了。這一功能確實給開發ASP.NET帶來了很大的
方便,但是卻使我們在ASP中將此dll刪除或者改名的解決方法失去效用了,防範此問題就變得更加複雜。在討論解決方案之前,我們先來看一
下怎麼來實現上述的危險的功能。
二、文件系統操作示例
在我們編寫代碼之前,有必要了解一下我們需要用到的幾個主要的類。這幾個
類都在System.IO名稱空間下,System.IO 名稱空間包含允許在數據流和文件上進行同步和異步讀寫的類。
在整個應用程序的開始
部分我們需要了解一下服務器的系統信息,這就需要用到System.Environment類,該類提供有關當前環境和平臺的信息以及操作它們的方法。
我們通過System.Environment類可以得到系統的當前目錄和系統目錄,這可以使我們更快的發現幾個關鍵的目錄;我們還可以通過獲取運行當
前進程的用戶名來幫助我們瞭解ASP.NET程序運行所使用的用戶,進一步設置用戶權限以避免這一安全問題。
我們還要使用
System.IO名稱空間的其他幾個類是:
System.IO.Directory:提供用於創建、移動和枚舉通過目錄和子目錄的靜態方法的類
System.IO.File:提供用於創建、複製、刪除、移動和打開文件的靜態方法的類
System.IO.FileInfo:提供創建、
複製、刪除、移動和打開文件的實例方法的類
System.IO.StreamReader:實現一個 TextReader,使其以一種特定的編碼從字節流
中讀取字符。
每個我們所使用的類的屬性和方法的具體用法我們將以代碼註釋的方式在程序中加以說明。
System.IO
名稱空間在 .NET FRAMEWORK提供的mscorlib.dll中,在使用VS.Net編程之前需要將此Dll引用到此項目中。
我們所編寫的程序都
使用了Codebehind方式,即每一個aspx程序都有一個對應的aspx.cs程序,aspx程序中只是寫與頁面顯示相關的代碼,所有邏輯實現的代碼都放
在相應的aspx.cs文件中,這樣就可以更好得做到顯示與邏輯的分離。由於我們的目的不是討論Codebehind技術,所以就不在對此多加討論了。
在這篇文章裏,我們只介紹幾個主要的類及其關鍵方法的用法,詳細程序請查看附帶的源代碼。
程序一:顯示服
務器的當前信息和全部邏輯驅動器的名稱的程序listdrivers.aspx
主要方法1:我們使用 GetSysInf() 方法來得到服務器的
當前環境和平臺的信息
//獲取系統信息的方法,此方法在listdrivers.aspx.cs文件中 public void GetSysInf () { //獲取操作系統類型 qDrives = Environment.OSVersion.ToString(); //獲取系統文件夾 qSystemDir = Environment.SystemDirectory.ToString(); /*獲取映射到 進程上下文的物理內存量,通過這一內存映射量可以瞭解ASP.NET程序在運行時需要多少系統物理內存,有助於更好的規劃我們的整個應用,因 爲物理內存量是以Byte爲單位的,所以我們將此數值除以1024,可以得到單位爲KB的物理內存量*/ qMo = (Environment.WorkingSet/1024).ToString(); //獲取當前目錄(即該進程從中啓動的目錄)的完全限定路徑 qCurDir = Environment.CurrentDirectory.ToString(); //獲取主機的網絡域名 qDomName = Environment.UserDomainName.ToString(); //獲 取系統啓動後經過的毫秒數 qTick = Environment.TickCount; //計算得到系統啓動後經過的分鐘數 qTick /= 60000; //獲取 機器名 qMachine = Environment.MachineName; //獲取運行當前進程的用戶名 qUser = Environment.UserName; /*檢索此計算 機上格式爲"<驅動器號>:/"的邏輯驅動器的名稱,返回字符串數組,這是下一步操作的關鍵所在*/ achDrives = Directory.GetLogicalDrives(); //獲取此字符串數組的維數,確定有多少個邏輯驅動器 nNumOfDrives = achDrives.Length; } |
系統信息不需要進行操作,我們簡單的用asp:Label將他們顯示出來就行了。邏輯驅動器的個數在不同的服務器上是不定的,所以
用不定長數組保存邏輯驅動器的名稱,而且邏輯驅動器的名稱也是我們下一步瀏覽目錄和文件的基礎,故我們採用了數據網格DataGrid來顯示
和處理它。
顯示和處理邏輯驅動器名稱的DataGrid的代碼(代碼在listdrivers.aspx文件):
<asp:DataGrid id="DriversGrid" runat="server" AutoGenerateColumns="false"> <Columns> <asp:BoundColumn HeaderText="ID" DataField="ID" /> <asp:BoundColumn HeaderText="磁盤名" DataField="Drivers" /> < asp:HyperLinkColumn HeaderText="詳細信息" DataNavigateUrlField="Drivers" DataNavigateUrlFormatString="listdir.aspx? dir={0}" DataTextField="Detail" Target="_new" /> </Columns> </asp:DataGrid> |
前兩個BoundColumn列都是顯示序號和實際邏輯驅動器名稱的,需要說明的是第三列,我們在進入各個邏輯驅動器顯示目錄和文件之前需要 將所選擇的邏輯驅動器的名稱傳遞到顯示目錄的文件去,所以需要一個特殊的超級鏈接行HyperLinkColumn,我們將DataNavigateUrlField設置 爲數據源中要綁定到 HyperLinkColumn 中的超級鏈接的 URL 的字段,在此即邏輯驅動器名稱。然後將DataNavigateUrlFormatString設置爲當 URL 數據綁定到數據源中的字段時,此HyperLinkColumn中的超級鏈接的 URL 的顯示格式,即要鏈接到的下一級處理頁面,在此爲 listdir.aspx?dir={用戶點擊行的邏輯驅動器名稱}
創建數據源的代碼(代碼在listdrivers.aspx.cs文件中):
//通過此方法返回一個集合形式的數據視圖DataView ICollection CreateDataSource() { //定義內存中的數據表 DataTable DataTable dt = new DataTable(); //定義DataTable中的一行數據DataRow DataRow dr; /*向DataTable中增加一個 列,格式:DataColumn("Column", type) Column爲數據列的名字,type爲數據列的數據類型*/ dt.Columns.Add(new DataColumn("ID", typeof(Int32))); dt.Columns.Add(new DataColumn("drivers", typeof(string))); dt.Columns.Add(new DataColumn("detail", typeof(string))); //使用for循環將邏輯驅動器的名稱以行的形式添加到數據表DataTable中 for (int i = 0; i < nNumOfDrives; i++) { //定義新行 dr = dt.NewRow(); //對行中每列進行賦值,注意要與上邊定義的DataTable的行相對應 dr[0] = i; //循環 生成的序號 dr[1] = achDrives[i].ToString(); //邏輯驅動器的名稱 dr[2] = "查看詳情"; //向DataTable中添加行 dt.Rows.Add(dr); } //根據得到的DataTable生成自定義視圖DataView DataView dv = new DataView(dt); //返回得到的視 圖DataView return dv; } |
我們通過這個方法得到了一個包含所有我們需要的數據的數據視圖 DataView,我們只需要在此aspx頁的Page_Load方法中將此數據視圖綁定到DataGrid上就可以了。
數據綁定代碼(代碼在 listdrivers.aspx.cs文件中):
/* 設置DataGrid的數據源DataSource爲我們從CreateDataSource()方法得到的數據視圖DataView */ DriversGrid.DataSource = CreateDataSource(); //將此DataGrid進行數據綁定 DriversGrid.DataBind(); |
通過上邊介紹的 幾種主要方法我們就實現了獲取系統信息和顯示所有邏輯驅動器名稱的功能,並且可以通過相應的鏈接進入下一個顯示目錄和文件名的程序 listdir.aspx顯示該邏輯驅動器下的所有目錄和文件。
程序二:顯示目錄中所有子目錄和文件的程序listdir.aspx
目錄下有子目錄和文件兩種形式,必須分別對待。我
們調用此程序本身對子目錄進行列表顯示,而文件我們需要調用showfile.aspx程序對文件的屬性和內容進行顯示。並且兩者還有不同的刪除方
法,所以我們在這裏設置了兩個DataGrid,兩個DataTable,兩個DataView,分別處理和顯示目錄和文件。
顯示和處理目錄和文件
的DataGrid的代碼(代碼在listdir.aspx文件):
顯示目錄或文件的序號和名稱的數據列類似於listdrivers.aspx程序中的相應
代碼,這裏就不再重複了。對於子目錄和文件分別有各自的處理頁面,所以需要導航到兩個不同的頁面,對於子目錄,我們繼續使用
listdir.aspx程序對其下的子目錄和文件進行列表顯示:
<asp:HyperLinkColumn DataNavigateUrlField="DirName" DataNavigateUrlFormatString="listdir.aspx?dir= {0}" DataTextField="DirDetail" HeaderText="詳細信息" Target="_new" /> 對於文件,我們使用showfile.aspx程序顯 示其屬性和內容: <asp:HyperLinkColumn DataNavigateUrlField="FileName" DataNavigateUrlFormatString="showfile.aspx? file={0}" DataTextField="FileDetail" HeaderText="詳細信息" Target="_new" /> |
在兩個DataGrid(DirGrid,FileGrid)中我們分別設置了兩個HyperLinkColumn列來導航到不同的處理頁面。
在兩個DataGrid 中我們都使用了一個刪除的按鈕列:
<asp:ButtonColumn HeaderText="刪除" Text="刪除" CommandName="Delete" /> |
由 於添加、更新、刪除功能列都是DataGrid的默認模板列,所以可以在Vs.net中通過DataGrid的屬性生成器自動添加此列。
獲取上 一頁面所傳遞來的參數的代碼:
因爲在下面產生數據源的方法中需要使用由上一個頁面傳遞過來的參數來確定目錄和文件的名稱 ,所以在頁面的Page_Load方法裏使用了下列代碼:
strDir2List = Request.QueryString["dir"]; |
字符串strDir2List即傳過來的目錄名或文件名。
因爲我們使用了兩個DateGrid,就需要進行兩次數據綁定,就有兩個不同的生成數據源的方法。
生成目錄數據網格( DirGrid)數據源的方法:
//通過此方法返回一個集合形式的數據視圖DataView,用來初始化子目錄的DataGrid ICollection CreateDataSourceDir() { dtDir = new DataTable(); DataRow dr; //向DataTable中添加新的數據列,共四列 dtDir.Columns.Add(new DataColumn ("DirID", typeof(Int32))); dtDir.Columns.Add(new DataColumn("DirName", typeof(string))); dtDir.Columns.Add(new DataColumn("DelDir", typeof(string))); dtDir.Columns.Add(new DataColumn("DirDetail", typeof(string))); //根據傳入的參 數(目錄名)得到此目錄下所有子目錄名的字符串數組 string [] DirEntries = Directory.GetDirectories(strDir2List); //使用 foreach循環可以對未知長度的數組進行遍歷循環 foreach(string DirName in DirEntries){ dr = dtDir.NewRow(); dr[0] = i;// 序號 dr[1] = DirName;//文件夾名稱 dr[3] = "刪除"; dr[3] = "查看詳情"; dtDir.Rows.Add(dr); i++; } DataView dvDir = new DataView(dtDir); //返回得到的數據視圖 return dvDir; } 生成文件數據網格(FileGrid)數據源 的方法: //通過此方法返回一個集合形式的數據視圖DataView,用來初始化文件的DataGrid ICollection CreateDataSourceFile() { dtFile = new DataTable(); DataRow dr; dtFile.Columns.Add(new DataColumn("FileID", typeof (Int32))); dtFile.Columns.Add(new DataColumn("FileName", typeof(string))); dtFile.Columns.Add(new DataColumn("DelFile", typeof(string))); dtFile.Columns.Add(new DataColumn("FileDetail", typeof(string))); //根據傳入的參數(目錄名)得到此目 錄下所有文件名的字符串數組 string [] FileEntries = Directory.GetFiles(strDir2List); foreach(string FileName in FileEntries){ dr = dtFile.NewRow(); dr[0] = i; dr[1] = FileName; dr[2] = "刪除"; dr[3] = "查看詳 情"; dtFile.Rows.Add(dr); i++; } dvFile = new DataView(dtFile); return dvFile; } |
我們編程實現了兩個DataSource只需在頁面的Page_Load方法裏對兩個DataGrid進行數據綁定即可將得到 的DataTable中的數據顯示在aspx頁面的DataGrid上。
數據綁定代碼:
//對子目錄數據列表DirGrid進行數據源定義和數據綁定 DirGrid.DataSource = CreateDataSourceDir(); DirGrid.DataBind(); //對文件數據列表FileGrid進行數據源定義和數據綁定 FileGrid.DataSource = CreateDataSourceFile(); FileGrid.DataBind(); |
通過我們上邊介紹的主要方法,我們實現了對某個邏輯驅動器或目錄中的所有子目錄和文件進行了列表 顯示,並且可以根據顯示結果更進一步的瀏覽子目錄或者查看文件的屬性和內容提要。瀏覽子目錄仍然是通過listdir.aspx這個程序,沒有任 何子目錄級別要求,沒有目錄深度限制。
刪除子目錄和文件的主要方法和代碼:
在刪除子目錄時,我們需要用到Directory.Delete (string,bool)方法,此方法有兩
種:
1.public static void Delete(string);
從指定路徑刪除空目錄。
2.public static void
Delete(string, boolean);
刪除指定的目錄並(如果指示)刪除該目錄中的任何子目錄,將boolean設置爲true的話,則刪除此目
錄下的所有子目錄和文件,否則將boolean設置爲false。
在這裏我們使用了第二種方法,如果選擇刪除的話,將刪除此目錄下的
所有子目錄和文件。
注意:Directory 類的所有方法都是靜態的,因而無需具有目錄Directory的實例就可被調用。
/*實現刪除子目錄的方法,此方法爲VS.NET自動添加,注意DataGridCommandEventArgs e爲DirGrid中 CommandName="Delete" 的
ButtonColumn的事件,通過此事件,我們可以得到是那一行的ButtonColumn按鈕列被點擊,進而確定我們需要刪除的子目錄的名稱
*/ private void DirGrid_DeleteCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e){ /*定義一個 單元格,e.Item爲此事件所發生行的所有項目,e.Item.Cells[1]爲整個行的第二個單元格的內容,在此DataGrid中爲子目錄的名稱 */ TableCell ItemCell = e.Item.Cells[1]; //得到此子目錄的名稱的字符串 string item = ItemCell.Text; //刪除此子 目錄 Directory.Delete(item,true); //刪除後進行數據綁定以更新數據列表 DirGrid.DataBind(); } |
在刪除文件時,我們需要用到File.Delete(string path);
注意:File 類的所有方法都是 靜態的,因而無需具有目錄的實例就可被調用。
private void FileGrid_DeleteCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e) { TableCell ItemCell = e.Item.Cells[1]; //得到此文件名稱的字符串 string item = ItemCell.Text; //刪除此文件 File.Delete(item); //刪除後進行數據綁定以更新數據列表 DirGrid.DataBind(); } |
通 過上邊的主要方法我們在頁面上實現了一個刪除某一個子目錄或者文件的功能,此功能在測試時需要慎重使用,一旦刪除無法通過常規方法恢 復。其他如目錄或文件改名、修改內容等方法都可以在此程序基礎上添加相應的功能,實現方法也很簡單。各位愛好者可以通過添加相應功能 ,使之擴充爲一個基於Web的服務器文件管理系統。我們也可以由此看到這個程序的危害性,一個沒有對此安全隱患採取防範措施的服務器的文 件系統就都暴露在了使用此程序的用戶面前。
程序三:顯示文件屬性和內容的程序showfile.aspx
在顯示屬性和內容時需要用到的兩個主要的類:
System.IO.FileInfo:提供創建、複製、刪除、移動和打開文件的實例方法,並且幫助創建 FileStream 對象。
System.IO.StreamReader:實現一個 TextReader,使其以一種特定的編碼從字節流中讀取字符。除非另外指定,StreamReader的默認編碼爲
UTF-8,而不是當前系統的 ANSI 代碼頁。UTF-8 可以正確處理 Unicode 字符並在操作系統的本地化版本上提供一致的結果。
Showfile.aspx頁面主要代碼:
<asp:Label id="FileDetail" runat="server"/> |
我們只是將文件的屬性信息和部分內容顯示在此 Label上。所以沒有其他複雜的代碼。
獲取文件信息和內容的主要代碼都在Page_Load方法中(代碼在showfile.aspx.cs文件中) :
//接收傳入的參數,確定需要操作的文件名稱 strFile2Show = Request.QueryString["file"]; //根據文件名實例化一個FileInfo 對象 FileInfo fi = new FileInfo(strFile2Show); FileDetail.Text = "文件名:"; FileDetail.Text += strFile2Show+"<br> "; FileDetail.Text += "文件大小"; //獲得文件的大小,然後變換單位爲KB FileDetail.Text += (fi.Length/1024).ToString() +"K<br>"; FileDetail.Text += "創建文件時間:"; //獲得文件的創建日期 FileDetail.Text += fi.CreationTime.ToString (); FileDetail.Text += "上次訪問時間:"; //獲得文件的上次訪問日期 FileDetail.Text += fi.LastAccessTime.ToString()+" <br>"; FileDetail.Text += "上次寫入時間:"; //獲得文件的上次寫入日期 FileDetail.Text += fi.LastWriteTime.ToString ()+"<br>"; //實例化一個StreamReader對象,用於讀取此FileInfo的內容 StreamReader FileReader = fi.OpenText(); //定義 一個長度爲1000的字符數組作爲緩衝區 char[] theBuffer = new char[1000]; /*ReadBlock方法:從當前流中讀取最大數量的字符並從 索引開始將該數據寫入緩衝區。 參數: char[] buffer:方法返回時,包含指定的字符數組 int index:buffer 中開始寫入的位置 int count:最多讀取的字符數 */ int nRead = FileReader.ReadBlock(theBuffer,0,1000); FileDetail.Text += new String(theBuffer,0,nRead); //關閉此 StreamReader 並釋放與之關聯的所有系統資源 FileReader.Close (); |
到目前爲止,我們實現了一個簡單的web頁面的服務器磁盤管理應用程序,可以查看、刪除目錄和文 件。如果需要修改文件、新建文件和文件夾等功能,只需稍作修改,添加上相應的代碼就可以。由於我們只是通過這個程序說明服務器中存在 的安全隱患,所以在這裏就不再實現這些功能了。
通過這三個簡單的程序,我想大家已經能夠清楚的認識到這一漏洞的危害性了 ,如果我們不加防範的話,其他用戶的程序就能被惡意使用此功能的用戶查看、刪除,服務器的系統日誌、系統文件也沒有任何安全可言了。
解決方案
將FSO組件和刪除或改名的方式我們不再過多的加以說明了,這一類的解決方法網
絡上已經有很多文章介紹了。
另外還有一種關於ASP的FSO組件漏洞的相應解決方案,即根據用戶設置權限。在IIS裏,可以設置每
個站點的匿名訪問所使用的帳號,默認爲IUSR_ HostName,這一方法的原理就是針對每一個共享主機用戶分別設置一個Windows帳號,如
IUSR_HostName1,IUSR_ HostName 2等,然後將每一個用戶限制在各自的Web目錄下。
我們仔細的研究一下這種方案,可以發現這個方案無法真正實現安全。因爲系統運行ASP時並不是使用的IUSR_ HostName帳號,而是
IWAM_ HostName帳號,就象在ASP.NET中使用的用戶ASPNET一樣。也就是說每個ASP程序所擁有的權限並不是IUSR_ HostName的權限,而是
IWAM_HostName用戶的權限。這樣的方法無法真正的將每個共享主機用戶的文件系統訪問權限限制在各自的虛擬站點中,每個用戶仍然可以訪問
別人的代碼。所以這種方法在ASP.NET中無法真正實現用戶之間的安全性。
在ASP.NET中相應的運行ASP.NET程序的帳號爲ASPNET,
和上面所說的ASP中的解決方案類似,我們只能限制此用戶不能訪問系統目錄等其他目錄,但是無法防止用戶訪問其他共享主機用戶的程序代碼
,無法從根本上杜絕這種問題。
那麼,有沒有真正的解決方案了呢?
有!這就是.NET Framework 的新特性――代碼
訪問安全性
爲了更好的理解這一問題的解決方法,我們需要先介紹一下.NET Framework的安全機制。然後再結合我們的實際問題來
討論解決方案。
爲了解決安全問題,.NET Framework提供了一種稱爲代碼訪問安全性的安全機制。代碼訪問安全性允許根據代碼
的來源和代碼的標識等屬性將代碼設置爲不同級別的信任代碼,同時還詳細定義了不同級別的對代碼的信任,從而可以詳細的對代碼設置各自的
權限而不是將最大權限賦給所有的代碼。使用代碼訪問安全性,可以減小惡意代碼或各種錯誤的代碼帶來的嚴重的系統安全性問題的可能性。
您可以設置允許代碼執行的一組操作,同樣可以設置永遠不允許代碼執行的一組操作。
實現代碼訪問安全性的基礎就是JIT(運行
時編譯)和IL(中間代碼)。所以所有以公共語言運行庫爲目標的託管代碼都會受益於代碼訪問安全性。非託管代碼則無法完全使用代碼訪問
安全性。
下面我們將介紹一下代碼訪問安全性實現的各種功能:
代碼訪問安全性是控制代碼對受保護資源和操作的訪問權限的一種
機制。在 .NET Framework中,代碼訪問安全性執行下列功能:
· 定義權限和權限集,它們表示訪問各種系統資源的權限。
· 使管理員能夠通過將權限集與代碼組關聯來配置安全策略。
· 使代碼能夠請求運行所需權限以及其他一些有用
的權限,以及指定代碼絕對不能擁有哪些權限。
· 根據代碼請求的權限和安全策略允許的操作,向加載的每個程序集授予權限
。
· 使代碼能夠要求其調用方擁有特定的權限。
· 使代碼能夠要求其調用方擁有數字簽名,從而只允許特定組
織或特定站點的調用方來調用受保護的代碼。
· 通過將調用堆棧上每個調用方所授予的權限與調用方必須擁有的權限相比較,加
強運行時對代碼的限制。
爲了確定是否已授予代碼相應的權限,.NET運行庫的安全系統將遍歷整個調用堆棧,將每個調用方所授
予的權限與目前要求的權限相比較。如果調用堆棧中的任何調用方沒有要求的權限,則會引發安全性異常,並會拒絕訪問和相應的操作。堆棧
步旨在防止引誘攻擊;在這種攻擊中,受信程度較低的代碼調用高度信任的代碼,並使用高度信任的代碼執行未經授權的操作。在運行時要求
所有調用方都擁有權限將影響性能,但對防止代碼遭受攻擊至關重要。若要優化性能,可以使代碼執行較少的堆棧步;但是,任何時候這樣做
時均必須確保不會暴露安全缺陷。
還存在另外一種代碼訪問安全性的常見用途,即應用程序將控件從網絡 Web 站點直接下載到客
戶端,這種方式的代碼安全性也是可以在客戶端進行設置的,根據簽名等數據權限證書來確定是不是可以允許下載的控件運行。這種方法類似
於ActiveX的安全性設置,但是比之在設置權限更加詳細和強大。同JAVA APPLET的沙箱安全機制相比,.NET 的客戶端控件可以在本地簡單設置
後訪問客戶端的各種資源。由於這一方面的用途不是我們的重點,所以我們在這裏就不再更詳細的討論其用途及其實現原理了。
下面我們就談談如何應用這一安全特性來解決ASP.NET中存在的系統安全漏洞。由於我們介紹的系統是共享主機,所以有其特殊性,即系統管理
員無法事先給所有的代碼賦予相應的權限,因爲每個用戶都可能有各種權限要求,並且這些要求特殊權力的代碼在使用中都可能出現的,所以
在權限管理上隨時都有各種要求。
因此在權限設置方面,不僅僅是管理員設置,也包括了各個共享主機用戶的權限請求,這也正是安全代
碼機制的一個重要部分。
請求權限是您讓運行庫知道代碼執行有哪些操作權限的方法。通過將屬性(聲明式語法)放到代碼的程
序級範圍來爲程序集請求權限。
請求內置權限的代碼示例:
//The attribute is placed on the assembly level. using System.Security.Permissions; [assembly:PermissionSetAttribute(SecurityAction.RequestMinimum, Name = "FullTrust")] |
將此段代碼放在程序的開始部分(namespace聲明之前),在編譯時就會將請求的權限存儲在程序集清單中。加載時,運行庫檢查權限
請求,並應用安全策略規則來確定授予程序集哪些權限。
雖然我們編寫的大部分代碼都沒有請求權限,其實不管是共享主機形式
還是獨立服務器形式都應該請求權限,這是因爲請求權限有助於確保只將代碼需要的權限授予代碼。如果沒有授予代碼額外權限,即使某些惡
意代碼想利用您的代碼來進行安全性破壞,它也無法操作沒有賦給您自己代碼相應權限的額外系統資源。您只應該請求代碼需要的那些權限,
而不應請求更多權限。
代碼請求權限之後,系統管理員可以使用"權限查看"工具 (Permview.exe,位於您的.NET Framework的目
錄的bin目錄下) 來檢查您的程序集並根據其他條件來設置安全策略以決定是否給您的代碼所請求的相應權限。如果您不顯式地在代碼中請求應
用程序需要的權限,那麼管理員將很難管理您的應用程序。在權限管理嚴格的主機上,將無法實現您的代碼所要求的功能。
請求
權限會通知運行庫應用程序正常運行需要哪些權限,或具體不需要哪些權限。在.NET Framework安裝後的默認狀態下,所有代碼都是FullTrust
(完全信任)的。這時是不需要申請任何權限的,但是管理員一旦修改了代碼安全,我們使用的磁盤訪問就要受到限制了,這是就需要申請相
應的權限了。我們上邊介紹的文件管理代碼就需要具有本地硬盤讀寫操作的能力,則應用程序必須擁有 FileIOPermission。如果代碼不請求
FileIOPermission,在本地安全設置不允許應用程序擁有此權限的主機上,在應用程序嘗試磁盤操作時就會引發安全性異常。即使應用程序能
夠處理此異常,也不會允許它操作磁盤。當然,如果您的代碼不訪問受保護的資源或執行受保護的操作,則不必請求任何權限。例如,如果代
碼只根據向它傳遞的輸入來計算結果而不使用任何資源,則不必請求權限。如果您的代碼訪問受保護的資源但未請求必要的權限,則仍可能允
許它執行,但如果它嘗試訪問某種資源而它又沒有必要的權限,則可能在執行過程中失敗。
系統管理員在得到了用戶的權限申請後,可以根據情況考慮是否賦予用戶相應的權限,在這裏我們來看一下相應代碼權限設置的具體方法
。
在我們安裝成功.NET Framework之後,在Windows 2000 Server的管理工具裏多了兩項管理工具: Microsoft .NET Framework
Configration和Microsoft .NET Framework Wizards。這兩種管理工具要實現的功能差不多,只不過Microsoft .NET Framework Wizards是通
過嚮導方式設置,如果您對於.NET Framewrk的安全性操作不是很熟悉的話,可以使用嚮導根據系統提示一步步的來設置相關的權限。
Microsoft .NET Framework Wizards界面 |
Microsoft .NET Framework Wizards界面 |
使用Microsoft .NET Framework Wizards可以簡單的設置.NET Framework的權限。但是我們爲了更好的管理各個代碼的權限,還是使用
Microsoft .NET Framework Configuration來詳細的設置我們所需的權限。
(Microsoft .NET Framework Configuration主界面) |
在Microsoft .NET Framework Configuration中可以設置所有關於.NET Framework的屬性。
點擊我的電腦,打開下拉菜單,我們可
以看到程序集緩存、已配置程序集、遠程處理服務、運行庫安全策略、應用程序等五項。運行庫安全策略設置是我們這篇文章的重點。
我們可以先查看一下程序集緩存,在這裏我們可以看到所有的全局程序集緩存,全局程序集緩存中存儲了專門指定給由計算機中
若干應用程序共享的程序集。在這裏我們可以發現我們可以使用的所有的程序集,同時也可以添加和刪除某些程序集。詳細操作請參見.NET
Framework SDK文檔。
我們在這裏主要討論的是運行庫安全策略。在此策略中,按層次結構由高到低分爲四個級別,即:企業、計
算機、用戶、應用程序。在計算權限授予時,運行庫從該層次結構的頂部開始,然後向下進行計算。較低的策略級別不能對在較高級別上授予
的權限進行增加,但是可以使權限減少。這就是說如果我們將計算機策略設置爲較小的權限時,可以不必更改企業策略就可以使設置的權限生
效,也就是說權限檢查的順序是從低級別到高級別,只有在低級別中不存在的設置纔會檢查上一級的設置。默認情況下,用戶策略和應用程序
域策略的限制性小於計算機策略和企業級策略。大部分默認策略存在於計算機級別。所以我們需要將默認安裝的主機的權限在計算機級別上進
行修改,修改的內容根據主機是不是共享主機,主機應用的其他不明代碼的可能性來設置。如果是我們討論的共享主機的話,在計算機級別上
就儘量將權限設的小一些,爲了避免我們討論的文件系統安全問題,一定要注意權限中的本地磁盤訪問權限。
我們打開計算機策
略設置可以發現幾個默認的代碼組、權限集和策略程序集。
根據需要,我們可以添加代碼組和自定義的權限集。
在
添加代碼組的時候可以選擇幾種條件,主要的條件類型:默認爲All Code、應用程序目錄、哈希、強名稱、作者、站點等。
對於
我們所要討論的共享主機,我們需要將My_Computer_Zone下的All Code的權限更改爲不能進行磁盤讀寫,在更改之前,我們需要先定義一個權
限集。這一權限集的作用就是將我們需要點擊權限集,右鍵快捷菜單中選擇新建,會出現一個創建權限集的窗口,這裏需要給我們新建的權限
集命名。下一步就是將單個權限分配給權限集。如下圖所示。
在這裏我們可以給這個新建的權限集賦予一個的系統權限,如上圖所示,可用的權限包括:目錄服務、DNS、事件日誌、環境變量、文
件IO、OLEDB數據庫操作、註冊表等等。我們主要要說明的是文件IO操作,其他的權限操作可以根據自己的需求來設置。這裏我們就不再說明了
。
在文件IO的權限設置中我們可以自定義針對每一個目錄的權限,這裏包括讀、寫、追加、路徑盤等操作,在這裏我們可以將我
們需要的目錄權限添加到列表中。因爲我們是利用這一權限使所有沒有配置權限的代碼不可以進行文件IO操作,所以我們不強文件IO添加到分
配的權限中。
新建了這一權限集後,我們更改一下默認設置,即將All Code的權限設置爲此新建的權限集,也就是說所有沒有在
此定義代碼都不能訪問文件IO系統。
這裏需要注意一件事情,因爲Microsoft .NET Framework Configuration本身也需要文件IO
權限,如果沒有單獨分配給Configuration一個文件IO操作權限的話,那麼您就不能再次使用Configuration來設置權限了,只能重新安裝.NET
Framework了。所以我們需要將FullTrust權限分配給Configuration所使用的Dll,即mscorcfg.dll。在添加時,成員條件可以選擇強名稱,使
用"導入",到winnt/window .net/framework/versionnumber/下選擇mscorcfg.dll。如果需要運行其他配置程序,還需要設置相應的權限,這些
系統程序一般都在系統程序集緩存中。
這樣我們就完成了一個簡單的設置,可以防止任何未經驗證的代碼訪問文件IO系統。這樣
就從根本上防止了磁盤惡意操作。
如果您今後需要利用這一功能或者有共享主機用戶需要使用文件IO功能,那麼您可以在
Microsoft .NET Framework Configuration中將其加入代碼,如果不能使其使用其他功能,可以僅僅設置一個只具有文件IO權限的權限集。如
果是共享主機用戶您還可以給他分配直接到其所使用的目錄的全部讀寫權限,對於他的日誌文檔,您可以將讀功能分配給用戶。通過上邊新建
權限集時我們可以發現:權限集可以規定到每一個目錄的讀寫權限,所以可以將用戶鎖定於其可以使用的目錄中。當然對於共享主機提供商來
說,最好的方法就是自己實現這些功能,然後配置權限系統使用戶使用共享主機提供商的程序來實現他們的正常操作,而避免了惡意文件操作
。
需要注意的是如果分配給每一個單獨的程序相應的權限時,我們最好使用強名稱這一方式或者其他的可驗證方式,強名稱由程
序集的標識--其簡單文本名稱、版本號和區域性信息(如果提供)--加上公鑰和數字簽名組成。這就需要我們使用Sn.exe 來設置密鑰、簽名和
簽名驗證。強名稱保證了程序是開發人員開發的並且沒有被改動。
在進行上面的設置之後,管理員可以根據用戶的各種需求來設
置不同的代碼集和權限集。
我們已經簡單的介紹了一下ASP.NET中關於文件IO系統的漏洞的防治方法,這一方法有些繁瑣,但是卻
可以從根本上杜絕一些漏洞,由於.NET的JIT(運行時編譯)和IL(中間語言),.NET可以在程序編譯時檢查程序的安全性設置,所以能從根本
上防止一些非法訪問。.NET的代碼安全性的內容很全面,我們討論的只是很少的一部分,更多的功能需要大家共同來探索、學習。