說明:本文中所需環境為2003Server+iis6.0+ms sql2000
曾經很早就在網上看到一篇關於<asp.net虛擬機器主機的重大隱患>的文章,當時並不在意,做過asp虛擬機器主機的朋友可能都知道,即對每一個使用者都設定一個獨立的伺服器使用者和單個目錄的操作許可權,能夠基本上解決asp的fso問題。
在網上無意中發現了一個叫做webadmin的asp.net-webshell,對自己的伺服器進行測試的時候,讓我大吃一驚,居然對我伺服器的c盤有讀取的許可權。以及對整個硬碟的修改刪除許可權。這樣的話,那麼我的伺服器的安全……
為了進一步證實,本人曾在國內一些著名的虛擬機器主機供應商上作過測試,均有和我一樣的問題。
有必要先介紹一下漏洞的原因。
ASP中常用的標準組件:FileSystemObject,這個組件為 ASP 提供了強大的檔案系統訪問能力,可以對伺服器硬碟上的任何有許可權的目錄和檔案進行讀寫、刪除、改名等操作。FSO對象來自微軟提供的指令碼運行庫scrrun.dll中。
在ASP.NET中我們發現這一問題仍然存在,並且變得更加難以解決。這是因為.NET中關於系統IO操作的功能變得更加強大,而使這一問題更嚴重的是ASP.NET所具有的一項新功能,這就組件不需要象ASP那樣必須要使用regsvr32來註冊了,只需將Dll類庫檔案上傳到bin目錄下就可以直接使用了。這一功能確實給開發ASP.NET帶來了很大的方便,但是卻使我們在ASP中將此dll刪除或者改名的解決方案失去效用了,防範此問題就變得更加複雜。需要進一步瞭解的朋友可以看<asp.net虛擬機器主機的重大隱患>一文,本文就不再重複。只針對此問題引出虛擬機器主機的安全設定。
網上提出針對此問題用Microsoft .NET Framework Configration設定System.io的對目錄讀取的許可權,經過我們長時間的測試沒有成功,可能是.net framework1.1機制改革了?
廢話不說。先說說解決的思路:在 IIS 6 中,Web 應用程式的背景工作處理序設定為以進程標識“Network Service”運行。在 IIS 5 中,進程外 Web 應用程式則設定為以 IWAM_<伺服器名> 帳戶運行,這個帳戶是普通的本機使用者帳戶。
Network Service 是 Windows Server 2003 中的內建帳戶。瞭解 IIS 5 上的本機使用者帳戶(IUSR 和 IWAM)與這個內建帳戶之間的區別是非常重要的。Windows 作業系統中的所有帳戶都分配了一個 SID(安全標識,Security ID)。伺服器是根據 SID,而不是與 SID 相關的名稱來識別伺服器上所有帳戶的,而我們在與使用者介面進行互動時,則是使用名稱進行互動的。伺服器上建立的絕大部分帳戶都是本地帳戶,都具有一個唯一的 SID,用於標識此帳戶隸屬於該伺服器使用者資料庫的成員。由於 SID 只是相對於伺服器是唯一的,因此它在任何其他系統上無效。所以,如果您為本地帳戶分配了針對某檔案或檔案夾的 NTFS 許可權,然後將該檔案及其許可權複製到另一台電腦上時,目標電腦上並沒有針對這個遷移 SID 的使用者帳戶,即使其上有一個同名帳戶也是如此。這使得包含 NTFS 許可權的內容複寫可能出現問題。
內建帳戶是由作業系統建立的、一類較為特別的帳戶或組,例如 System 帳戶、Network Service 和 Everyone 組。這些對象的重要特徵之一就是,它們在所有系統上都擁有一個相同的、眾所周知的 SID。當將分配了 NTFS 許可權的檔案複製到內建帳戶時,許可權在伺服器之間是有效,因為內建帳戶的 SID 在所有伺服器上都是相同的。Windows Server 2003 服務中的 Network Service 帳戶是特別設計的,專用於為應用程式提供訪問網路的足夠許可權,而且在 IIS 6 中,無需提升許可權即可運行 Web 應用程式。這對於 IIS 安全性來說,是一個特大的訊息,因為不存在緩衝溢出,懷有惡意的應用程式無法破譯進程標識,或是對應用程式的攻擊不能進入 System 使用者環境。更為重要的一點是,再也不能形成針對 System 帳戶的“後門”,例如,再也無法通過 InProcessIsapiApps 中繼資料庫項利用載入到 Inetinfo 的應用程式。
Network Service 帳戶在建立時不僅僅考慮了在 IIS 6 中的應用。它還具有進程標識 W3WP.exe 的絕大部分(並不是全部)許可權。如同 ASPNET 使用者為了運行 ASP.net 應用程式,需要具有 IIS 5 伺服器上某些位置的存取權限,進程標識 W3WP.exe 也需要具有類似位置的存取權限,而且還需要一些預設情況下沒有指派給內建群組的許可權。
為了管理的方便,在安裝 IIS 6 時建立了 IIS_WPG 組(也稱為 IIS 背景工作處理序組,IIS Worker Process Group),而且它的成員包括 Local System(本地系統)、Local Service(本地服務)、Network Service(網路服務)和 IWAM 帳戶。IIS_WPG 的成員具有適當的 NTFS 許可權和必要的使用者權限,可以充當 IIS 6 中背景工作處理序的進程標識。
因此,Network Service 帳戶提供了訪問上述位置的許可權,具有充當 IIS 6 背景工作處理序的進程標識的充足許可權,以及具有訪問網路的許可權。
Msdn上說:在 Windows Server 2003 中,使用者上下文稱為 NETWORK SERVICE。這些使用者帳戶是在 .NET Framework 安裝過程中建立的,它具有唯一的不易破解的密碼,並僅被授予有限的許可權。ASPNET 或 NETWORK SERVICE 使用者只能訪問運行 Web 應用程式所需的特定檔案夾,如 Web 應用程式儲存已編譯檔案的 /bin 目錄。
要將進程標識設定為特定使用者名稱,以取代 ASPNET 或 NETWORK SERVICE 使用者標識,您提供的使用者名稱和密碼都必須儲存在 machine.config 檔案中。
但是根據實際情況,asp.net的system.io可以無限制訪問不設防的伺服器路徑。不知道這算不算一個ms的重大漏洞。而且根本不能使iis以machine.config的使用者執行asp.net程式。J
如何解決呢?答案就是—應用程式集區。
IIS 6.0 在被稱為應用程式隔離模式(隔離模式)的兩種不同操作模式下運行,它們是:背景工作處理序隔離模式和 IIS 5.0 隔離模式。這兩種模式都要依賴於 HTTP.sys 作為超文字傳輸通訊協定 (HTTP) (HTTP) 偵聽程式;然而,它們內部的工作原理是截然不同的。
背景工作處理序隔離模式利用 IIS 6.0 的重新設計的體繫結構並且使用背景工作處理序的核心組件。IIS 5.0 隔離模式用於依賴 IIS 5.0 的特定功能和行為的應用程式。該隔離模式由 IIs5IsolationModeEnabled 設定資料庫屬性指定。
您所選擇的 IIS 應用程式隔離模式對效能、可靠性、安全性和功能可用性都會產生影響。背景工作處理序隔離模式是 IIS 6.0 操作的推薦模式,因為它為應用程式提供了更可靠的平台。背景工作處理序隔離模式也提供了更進階別的安全性,因為運行在背景工作處理序中的應用程式的預設標識為 NetworkService。
以 IIS 5.0 隔離模式啟動並執行應用程式的預設標識為 LocalSystem,該標識允許訪問並具有更改電腦上幾乎所有資源的能力。
| IIS 功能 |
IIS 5.0隔離模式宿主/組件 |
背景工作處理序隔離模式宿主/組件 |
| 背景工作處理序管理 |
N/A |
Svchost.exe/WWW 服務 |
| 背景工作處理序 |
N/A |
W3wp.exe/背景工作處理序 |
| 運行進程內ISAPI 擴充 |
Inetinfo.exe |
W3wp.exe |
| 運行進程外ISAPI 擴充 |
DLLHost.exe |
N/A(所有的 ISAPI 擴充都在進程內) |
| 運行ISAPI篩選器 |
Inetinfo.exe |
W3wp.exe |
| HTTP.sys 配置 Svchost.exe/WWW 服務 |
Svchost.exe/WWW |
服務 |
| HTTP 協議支援 |
Windows核心/HTTP.sys |
Windows 核心/HTTP.sys |
| IIS設定資料庫 |
Inetinfo.exe |
Inetinfo.exe |
| FTP |
Inetinfo.exe |
Inetinfo.exe |
| NNTP |
Inetinfo.exe |
Inetinfo.exe |
| SMTP |
Inetinfo.exe |
Inetinfo.exe |
由此可見,我們只能使用背景工作處理序隔離模式解決.net的安全問題。
預設情況下,IIS 6.0在背景工作處理序隔離模式下運行,五所示。在這種模式中,對於每一個Web應用,IIS 6.0都用一個獨立的w3wp.exe的執行個體來運行它。w3wp.exe也稱為背景工作處理序(Worker Process),或W3Core。
可靠性和安全性。可靠性的提高是因為一個Web應用的故障不會影響到其他Web應用,也不會影響http.sys,每一個Web應用由W3SVC單獨地監視其健康情況。安全性的提高是由於應用程式不再象IIS 5.0和IIS 4.0的進程內應用那樣用System帳戶運行,預設情況下,w3wp.exe的所有執行個體都在一個許可權有限的“網路服務”帳戶下運行,六所示,必要時,還可以將背景工作處理序配置成用其他使用者帳戶運行。
對,這裡,這裡就是我們解決的核心。
我們把每一個網站都分配一個獨立的應用程式集區,並賦予不同的許可權。不就能解決這個問題了嗎?
具體如何做呢,下面我就針對建立一個網站來做一個示範:
首先,我們為網站建立兩個使用者(一個是app_test_user、密碼為appuser,一個是iis_test_user、密碼為iisuser)
1. 開啟 電腦管理器
2. 單擊主控台樹狀目錄中的使用者→電腦管理→系統工具→本機使用者和組→使用者
3. 單擊“操作”菜單上的“新使用者”輸入使用者名稱為。app_test_user、密碼為appuser
4. 在對話方塊中鍵入適當的資訊。
5. 選中複選框:
使用者不能更改密碼
密碼永不到期
6. 單擊“建立”,然後單擊“關閉”。
按照此方法在建立iis_test_user賬戶
然後分別把app_test_user添加到iis_wpg組,把iis_test_user添加到Guests組。刪除其他組。
然後,建立相應的應用程式集區。
依次開啟Internet 資訊服務→本機電腦→應用程式集區→建立→應用程式集區
建立一個名字為test的應用程式集區
編輯test應用程式集區的屬性→標示→配置→使用者名稱→瀏覽→把使用者名稱改為我們剛才建立的app_test_user並輸入相應的密碼
其次建立相應的網站。
依次開啟Internet 資訊服務→本機電腦→網站→建立→test的網站,目錄為d:/test →編輯test網站的屬性→主目錄→應用程式集區→app_test_user →目錄安全性→身分識別驗證和存取控制→編輯,選擇我們剛才建立的iis_test_user,並輸入相應的密碼iisuser→儲存並退出。
最後設定伺服器的安全。
C:只給administrators和system完全控制的權利,刪除掉其他所有的許可權,不替換子目錄
C:/Documents and Settings繼承父項,並替換子目錄。
C:/Program Files繼承父項,並替換子目錄,並把C:/Program Files/Common Files/Microsoft Shared繼承屬性刪除並複製現有屬性,增加users的讀取許可權並替換子目錄(這樣做是為了能夠讓asp,asp.net使用access等資料庫)。
C:/windows刪除繼承,並複製現有屬性,只給予administrators,system完全控制和users讀取的許可權並替換子目錄。
其餘所有的盤都只給於administrators和system使用者的完全控制許可權,刪除其他所有使用者並替換子目錄。
D:/test(使用者網站目錄)繼承現有屬性並增加app_test_user和iis_test_user完全控制的許可權並替換子目錄。
以後每增加一個網站都以此類推。
但是,至此,system.io還是對c:/windows又讀取許可權的,(懷疑network servers使用者屬於users組,但是好多服務都要使用users組來執行的,所以不能把c:/windwos去掉users組的讀取許可權)但必須知道系統路徑,有兩種方案解決。
1、 再安裝系統的時候使用無人值守安裝,更換c:/windows預設安裝路徑,如更改為c:/testtest(要符合dos的命名規則,不能超過8個字元)。這個是必需的
2、 以下位置具有指派給 IIS_WPG 的許可權:
%windir%/help/iishelp/common – 讀取
%windir%/IIS Temporary Compressed Files – 列出、讀取、寫入
%windir%/system32/inetsrv/ASP Compiled Template – 讀取
Inetpub/wwwroot(或內容目錄)- 讀取、執行
此外,IIS_WPG 還具有以下使用者權限:
忽略遍曆檢查(SeChangeNotifyPrivilege)
作為批次工作登入(SeBatchLogonRight)
從網路訪問此電腦(SeNetworkLogonRight)
當然兩種方法結合起來算是最安全的方案,一般使用第一種方案已經算是很安全的,畢竟是用一個webshell來猜測8位字元的目錄還是需要花費時間的。使用防火牆很容易就能察覺出來,並加以控制。
第二種可能根據所安裝軟體不同還要相應增加目錄的讀取許可權,詳細情況要根據軟體來確定。
如果主機使用者比較多,這將是一個相當大的勞動量,推薦使用程式來解決問題,下面給出網上不常見的針對iis應用程式集區操作的代碼和針對iis虛擬目錄的作業碼。
操作iis應用程式集區
using System; using System.DirectoryServices; using System.Reflection;namespace ADSI1 { /// /// Small class containing methods to configure IIS. /// class ConfigIIS { /// /// The main entry point for the application. /// [STAThread] //主程式入口,可以選擇用哪些,我為了方便,全部功能都寫上去了。 static void Main(string[] args) { string AppPoolName = "MyAppPool"; string newvdir1 = "MyVDir"; DirectoryEntry newvdir = CreateVDir(newvdir1); CreateAppPool(AppPoolName); AssignAppPool(newvdir, AppPoolName); ConfigAppPool("Stop",AppPoolName); } //建立虛擬目錄 static DirectoryEntry CreateVDir (string vdirname) { DirectoryEntry newvdir; DirectoryEntry root=new DirectoryEntry("IIS://localhost/W3SVC/1/Root"); newvdir=root.Children.Add(vdirname, "IIsWebVirtualDir"); newvdir.Properties["Path"][0]= "c://inetpub//wwwroot"; newvdir.Properties["AccessScript"][0] = true; newvdir.CommitChanges(); return newvdir; } //建立新的應用程式集區。 static void CreateAppPool(string AppPoolName) { DirectoryEntry newpool; DirectoryEntry apppools=new DirectoryEntry("IIS://localhost/W3SVC/AppPools"); newpool=apppools.Children.Add(AppPoolName, "IIsApplicationPool"); newpool.CommitChanges(); } static void AssignAppPool(DirectoryEntry newvdir, string AppPoolName) { object[] param={0, AppPoolName, true}; newvdir.Invoke("AppCreate3", param); } //method是管理應用程式池的方法,有三種Start、Stop、Recycle,而AppPoolName是應用程式集區名稱 static void ConfigAppPool(string method,string AppPoolName) { DirectoryEntry appPool = new DirectoryEntry("IIS://localhost/W3SVC/AppPools"); DirectoryEntry findPool = appPool.Children.Find(AppPoolName,IIsApplicationPool"); findPool.Invoke(method,null); appPool.CommitChanges(); appPool.Close(); } //應用程式集區的列表 static void AppPoolList() { DirectoryEntry appPool = new DirectoryEntry("IIS://localhost/W3SVC/AppPools"); foreach(DirectoryEntry a in appPool.Children) { Console.WriteLine(a.Name); } } private void VDirToAppPool() { DirectroryEntry VD = new DirectoryEntry("IIS://localhost/W3SVC/1/ROOT/ccc"); Console.WriteLine(VD.Properties["AppPoolId"].Value.ToString()); } } } |
iis6操作的例子
using System; using System.DirectoryServices; using System.Collections; using System.Text.RegularExpressions; using System.Text; namespace Wuhy.ToolBox { /// </summary> public class IISAdminLib { #region UserName,Password,HostName的定義 public static string HostName { get { return hostName; } set { hostName = value; } } public static string UserName { get { return userName; } set { userName = value; } } public static string Password { get { return password; } set { if(UserName.Length <= 1) { throw new ArgumentException("還沒有指定好使用者名稱。請先指定使用者名稱"); } password = value; } } public static void RemoteConfig(string hostName, string userName, string password) { HostName = hostName; UserName = userName; Password = password; } private static string hostName = "localhost"; private static string userName; private static string password; #endregion #region 根據路徑構造Entry的方法 /// <summary> /// 根據是否有使用者名稱來判斷是否是遠程伺服器。 /// 然後再構造出不同的DirectoryEntry出來 /// </summary> /// <param name="entPath">DirectoryEntry的路徑</param> /// <returns>返回的是DirectoryEntry執行個體</returns> public static DirectoryEntry GetDirectoryEntry(string entPath) { DirectoryEntry ent; if(UserName == null) { ent = new DirectoryEntry(entPath); } else { // ent = new DirectoryEntry(entPath, HostName+"//"+UserName, Password, AuthenticationTypes.Secure); ent = new DirectoryEntry(entPath, UserName, Password, AuthenticationTypes.Secure); } return ent; } #endregion #region 添加,刪除網站的方法 /// <summary> /// 建立一個新的網站。根據傳過來的資訊進行配置 /// </summary> /// <param name="siteInfo">儲存的是新網站的資訊</param> public static void CreateNewWebSite(NewWebSiteInfo siteInfo) { if(! EnsureNewSiteEnavaible(siteInfo.BindString)) { throw new DuplicatedWebSiteException("已經有了這樣的網站了。" + Environment.NewLine + siteInfo.BindString); } string entPath = String.Format("IIS://{0}/w3svc", HostName); DirectoryEntry rootEntry = GetDirectoryEntry(entPath); string newSiteNum = GetNewWebSiteID(); DirectoryEntry newSiteEntry = rootEntry.Children.Add(newSiteNum, "IIsWebServer"); newSiteEntry.CommitChanges(); newSiteEntry.Properties["ServerBindings"].Value = siteInfo.BindString; newSiteEntry.Properties["ServerComment"].Value = siteInfo.CommentOfWebSite; newSiteEntry.CommitChanges(); DirectoryEntry vdEntry = newSiteEntry.Children.Add("root", "IIsWebVirtualDir"); vdEntry.CommitChanges(); vdEntry.Properties["Path"].Value = siteInfo.WebPath; vdEntry.CommitChanges(); } /// <summary> /// 刪除一個網站。根據網站名稱刪除。 /// </summary> /// <param name="siteName">網站名稱</param> public static void DeleteWebSiteByName(string siteName) { string siteNum = GetWebSiteNum(siteName); string siteEntPath = String.Format("IIS://{0}/w3svc/{1}", HostName, siteNum); DirectoryEntry siteEntry = GetDirectoryEntry(siteEntPath); string rootPath = String.Format("IIS://{0}/w3svc", HostName); DirectoryEntry rootEntry = GetDirectoryEntry(rootPath); rootEntry.Children.Remove(siteEntry); rootEntry.CommitChanges(); } #endregion #region Start和Stop網站的方法 public static void StartWebSite(string siteName) { string siteNum = GetWebSiteNum(siteName); string siteEntPath = String.Format("IIS://{0}/w3svc/{1}", HostName, siteNum); DirectoryEntry siteEntry = GetDirectoryEntry(siteEntPath); siteEntry.Invoke("Start", new object[] {}); } public static void StopWebSite(string siteName) { string siteNum = GetWebSiteNum(siteName); string siteEntPath = String.Format("IIS://{0}/w3svc/{1}", HostName, siteNum); DirectoryEntry siteEntry = GetDirectoryEntry(siteEntPath); siteEntry.Invoke("Stop", new object[] {}); } #endregion #region 確認網站是否相同 /// <summary> /// 確定一個新的網站與現有的網站沒有相同的。 /// 這樣防止將非法的資料存放到IIS裡面去 /// </summary> /// <param name="bindStr">網站邦定資訊</param> /// <returns>真為可以建立,假為不可以建立</returns> public static bool EnsureNewSiteEnavaible(string bindStr) { string entPath = String.Format("IIS://{0}/w3svc", HostName); DirectoryEntry ent = GetDirectoryEntry(entPath); foreach(DirectoryEntry child in ent.Children) { if(child.SchemaClassName == "IIsWebServer") { if(child.Properties["ServerBindings"].Value != null) { if(child.Properties["ServerBindings"].Value.ToString() == bindStr) { return false; } } } } return true; } #endregion #region 擷取一個網站編號的方法 /// <summary> /// 擷取一個網站的編號。根據網站的ServerBindings或者ServerComment來確定網站編號 /// </summary> /// <param name="siteName"></param> /// <returns>返回網站的編號</returns> /// <exception cref="NotFoundWebSiteException">表示沒有找到網站</exception> public static string GetWebSiteNum(string siteName) { Regex regex = new Regex(siteName); string tmpStr; string entPath = String.Format("IIS://{0}/w3svc", HostName); DirectoryEntry ent = GetDirectoryEntry(entPath); foreach(DirectoryEntry child in ent.Children) { if(child.SchemaClassName == "IIsWebServer") { if(child.Properties["ServerBindings"].Value != null) { tmpStr = child.Properties["ServerBindings"].Value.ToString(); if(regex.Match(tmpStr).Success) { return child.Name; } } if(child.Properties["ServerComment"].Value != null) { tmpStr = child.Properties["ServerComment"].Value.ToString(); if(regex.Match(tmpStr).Success) { return child.Name; } } } } throw new NotFoundWebSiteException("沒有找到我們想要的網站" + siteName); } #endregion #region 擷取新網站id的方法 /// <summary> /// 擷取網站系統裡面可以使用的最小的ID。 /// 這是因為每個網站都需要有一個唯一的編號,而且這個編號越小越好。 /// 這裡面的演算法經過了測試是沒有問題的。 /// </summary> /// <returns>最小的id</returns> public static string GetNewWebSiteID() { ArrayList list = new ArrayList(); string tmpStr; string entPath = String.Format("IIS://{0}/w3svc", HostName); DirectoryEntry ent = GetDirectoryEntry(entPath); foreach(DirectoryEntry child in ent.Children) { if(child.SchemaClassName == "IIsWebServer") { tmpStr = child.Name.ToString(); list.Add(Convert.ToInt32(tmpStr)); } } list.Sort(); int i = 1; foreach(int j in list) { if(i == j) { i++; } } return i.ToString(); } #endregion } #region 新網站資訊結構體 public struct NewWebSiteInfo { private string hostIP; // The Hosts IP Address private string portNum; // The New Web Sites Port.generally is "80" private string descOfWebSite; // 網站表示。一般為網站的網站名。例如"www.dns.com.cn" private string commentOfWebSite;// 網站注釋。一般也為網站的網站名。 private string webPath; // 網站的主目錄。例如"e:/tmp" public NewWebSiteInfo(string hostIP, string portNum, string descOfWebSite, string commentOfWebSite, string webPath) { this.hostIP = hostIP; this.portNum = portNum; this.descOfWebSite = descOfWebSite; this.commentOfWebSite = commentOfWebSite; this.webPath = webPath; } public string BindString { get { return String.Format("{0}:{1}:{2}", hostIP, portNum, descOfWebSite); } } public string CommentOfWebSite { get { return commentOfWebSite; } } public string WebPath { get { return webPath; } } } #endregion } |
至此,一個相對安全的.net主機就建立起來了,隨著.net2.0的發布越來越逼近,希望ms能針對此問題作一個妥善的防範。
我們已經簡單的介紹了一下ASP.NET中關於檔案IO系統的漏洞的防治方法,這一方法有些繁瑣,但是卻可以從根本上杜絕一些漏洞,我們討論的只是很少的一部分,更多的解決放法需要大家共同來探索、學習。