Web 服务和 IIS 安全障碍






4.31/5 (9投票s)
2006年1月27日
11分钟阅读

61996

769
如何配置 IIS 以便在 Web 服务中使用命名内核对象。
引言
众所周知,安全是一个重要问题,应该认真对待。近年来,随着互联网的广泛使用以及为了防范攻击者和黑客的恶意活动,安全措施的需求日益增长。说了这么多,我们也要承认,安全是软件开发人员的噩梦,将安全性融入我们的项目并使用安全框架需要大量时间,这主要是由于配置问题。
本文讨论 IIS(Internet Information Server),特别是 Web 服务。 IIS 是微软实现的 HTTP 服务器;它在行业中被广泛用于网站和 Web 服务。这些特点使 IIS 处于恶意攻击的最前沿,因此 IIS 具有多层安全保护。本文旨在为读者提供关于 IIS 安全配置的信息。
IIS 安全机制是一个庞大的主题,足以写成一本书,因此我选择聚焦于一个特定的案例研究:如何配置 IIS 以便在 Web 服务中使用命名内核对象。以下是使用此案例研究的两个主要原因:
- 命名内核对象需要安全配置,否则它们将无法工作。
- 命名内核对象是一个很酷的功能,在 Web 或 MSDN 上文档不充分。
免责声明
与我之前的文章一样,我只与开发社区分享信息。本文不是关于 IIS 安全性的全面论文,而只是一个关于我在工作中遇到的令人恼火的障碍的简短指南。
谁应该阅读本文?
如果您正在处理 IIS 或计划处理,如果您需要将网站或 Web 服务移植到 IIS 6 版本和 Windows 2003,那么本文将对您有所帮助。此外,文章中的案例研究涉及命名内核对象,我认为该主题的文档不够充分。读者还会从 Microsoft 操作系统中找到有关内核对象的信息。我希望读者具备 Web 服务、IIS 和 C# 的基本知识。
案例挑战
出于安全原因,IIS 5.x 和 6.x 与命名内核对象无法正常工作。例如,在 IIS 5.x 中,调用 CreateEvent
API 时返回的句柄无效,GetLastError
方法返回错误 5(拒绝访问)。在 IIS 6.x 中,该方法返回有效的句柄(GetLastError
返回 0 - 成功);看起来工作正常,但要小心,它不会做您认为它做的事情。句柄不会被共享。问题的全部规模暴露于这样一个事实:您实际上不知道哪里出错了。内核对象数据结构的性质使其只能在内核模式下访问;因此,不可能在内存中检测到它们。
案例环境
该案例在本环境下进行了测试:
- Windows XP Professional SP2 和 Windows 2003 Server SP 1
- IIS 5.1 和 IIS 5
- .NET framework 1.1x SP
内核对象 (KO)
在深入案例研究的特点之前,有必要通过描述什么是内核对象来铺平道路。如果您不熟悉此主题,我建议您先跳到附录 A。
案例原因
命名内核对象功能在 Web 服务中不起作用,原因是默认的安全属性。在 Windows XP SP 2 中,原因是 ProcessModel
属性中缺少用户权限;在 Windows 2003 Server 中,原因是 IIS_WPG 组缺少用户权限,并且需要在命名内核对象的名称中使用 \Global 前缀。
案例解决方案
IIS 5 Windows XP Professional 和命名内核对象
要在 Win XP Pro SP 2 和 IIS 5.x 中成功使用命名内核对象,您需要在 Machine.Config 文件(<Windows 文件夹>\Microsoft.NET\Framework\<框架版本>\CONFIG)的 ProcessModel
XML 节点中更改 userName
属性。
<processModel enable="true" timeout="Infinite" idleTimeout="Infinite"
shutdownTimeout="0:00:05" requestLimit="Infinite"
requestQueueLimit="5000" restartQueueLimit="10"
memoryLimit="60" webGarden="false" cpuMask="0xffffffff"
userName="System" password="AutoGenerate"
logLevel="Errors" clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00" maxWorkerThreads="20"
maxIoThreads="20"/>
重置 IIS:从命令行键入 iisreset。
IIS 6 Windows 2003 Server 和命名内核对象
不幸的是,以上方法不适用于 IIS 6。这是因为 Windows 2003 Server 中有更复杂的安全机制。出于某些原因, Redmond 的“狐狸”(微软)认为不常见或不重要地写下绕过此问题的方法。MSDN 中的命名内核对象 API 文档强调以下内容:
“终端服务:名称可以具有“Global\”或“Local\”前缀,以显式在全局或会话命名空间中创建对象。名称的其余部分可以包含除反斜杠字符(\)以外的任何字符。”
尽管 \Global 前缀是解决方案的一部分,但它与终端服务无关。问题发生在控制台以及直接登录 Windows 2003 Server 计算机时。Windows 2003 Server 使用会话命名空间,因此始终在会话上下文中返回句柄(即使对于命名内核对象)。为了成功使用 Windows 2003 Server 中的命名内核对象,您需要添加 Global\ 前缀。这将告诉操作系统命名内核对象应在全局会话命名空间中创建。我想提一下,在 Windows 2003 Server 中,这个问题更加烦人,因为 API 返回有效的句柄,并且 GetLastError
返回成功。API 调用成功,只是没有做到我们期望它做的事情。
我们克服了这个障碍。快点!别放松,我们还没完成。上述解决方案适用于使用带有 Global\ 前缀的命名内核对象并在同一用户下运行的进程。如果您在 Web 服务中使用命名内核对象,它将不起作用。为什么?在 IIS 6 中,您定义了一个对象池或使用默认设置。对象池在某个标识下运行(默认情况下是 IWAM 或 Network System)。如果 Web 服务和 Windows Forms 应用程序等运行的用户不同,即使您使用了 Global\ 前缀,它们也不会共享会话命名空间。为了克服这个障碍,您应该将运行 Windows Forms 应用程序的用户添加到 IIS_WPG 组。如果仍然不起作用,请确保 IIS_WPG 组具有“绕过遍历检查”权限。
下图显示了 Windows 2003 Server 中的组。您需要将用户与此组关联。
下图显示了上述用户与应用程序池(NamedKOTest)的关联。
下图显示了 Web 服务与应用程序池的关联。
实现
使用源代码
您会发现,源代码并不花哨。它仅演示了问题和解决方案。您会找到两个项目:一个非常简单的 Web 服务和一个只有几行代码的“惊人”的 Windows Forms 应用程序。为简单起见,将 Web 服务放在 IIS 根目录(c:\inetput\wwwroot)下,并为它创建一个虚拟目录。运行 Windows Forms 应用程序并启动 Wait 方法。浏览 WebService1.asmx 并激活 SetEvent
方法。很抱歉简述了部署 Web 服务的说明,这完全超出了本文的范围,而且这些都是可以在网上找到的简单步骤。
结论
命名内核对象在 Web 服务中使用这一案例的解决方案很简单。然而,找到这个解决方案的旅程并非如此简单。原因是 Web 和 MSDN 上都缺乏文档。这正是我写这篇文章的根本原因。本文在一个非常狭窄的案例研究中展示了 IIS 安全配置的用法。我希望我解决这个问题的经历能对开发社区有所回报。
了解一下
出于某种原因,在使用两个 Windows Forms 应用程序时,Windows 2003 Server 中不需要 \Global 前缀。不过,我认为使用此前缀是一个好习惯,因为您无法预测代码将来可能在何处运行。
技巧
- 请注意为内核对象分配明显的名称。“MyEvent”是一个非常糟糕的名称,容易出错。
- .NET Framework 包装类不支持命名内核对象。您需要使用 P/Invoke 来享受此功能。
- 在更改 machine.config 之前,请先复制一份。
- 有时,在 ASP.NET 方面解决 IIS 问题的唯一方法是恢复到默认设置。这可以通过卸载 ASP.NET(使用 aspregis –ua)然后重新安装(使用 aspregis –a)来轻松完成。该实用程序可以在 <系统盘>:\windows\Microsoft.net\Framework\<框架版本> 中找到。
- 既然我们讨论了命名内核对象(只能通过 P/Invoke 访问),提及关闭内核对象句柄的必要性非常重要。
- 对于 IIS 的任何配置更改,最好重新启动 IIS 服务。
链接
- 内核对象命名空间 - MSDN
- www.iisanswers.com
- IIS 6.0 资源工具包
- www.iisfaq.com
- 关于 IIS 的 PowerPoint 演示文稿
- 使用 IIS 的十件事
- IIS 6.0 的默认权限和用户权限
致谢
我要感谢我的朋友 Arye Shapiro 医生,感谢他在英语语法方面的帮助以及技术建议。
附录 A
内核对象 (KO)
如果您不熟悉内核对象这个术语,请不要担心,您可能在使用它们而不自知。以下是关于内核对象的简要描述:内核对象是数据结构。微软在 Windows 中大量使用它来封装对象,如:事件、互斥体、信号量、线程、进程、文件映射等。内核对象是通过调用 Win32 API 来创建的。通常,内核对象是特定于进程的。从 API 调用返回的句柄是创建进程的属性。命名内核对象是一个例外。
命名内核对象可以在进程之间共享。命名内核对象的 API 使用一个字符串参数作为名称。打开或创建命名内核对象会返回一个与由操作系统管理的共享内核对象相关联的句柄。当进程(进程也是一个内核对象)初始化时,操作系统会分配一个内核对象句柄表。下图显示了初始化后的过程。
下图包含两个进程 A 和 B。每个进程创建一个未命名的事件。
- 进程 A 调用
CreateEvent
API,传入一个NULL
名称。 - 系统在内核内存中创建内核对象。
- 系统将地址返回给进程。API 返回一个有效句柄:0。
- 进程 B 调用
CreateEvent
API,传入一个名称。 - 系统在内核内存中创建内核对象。
- 系统将地址返回给进程。API 返回一个有效句柄:0。
如您所见,每个进程有两个事件内核对象,因此没有共享。两个操作都返回索引为 0 的句柄,因为两个进程刚刚初始化。
为什么要使用命名内核对象?
首先,它是一个重要且有用的功能。此外,它是创建单实例进程的最简单方法(例如,当您尝试第二次运行 ICQ 进程时,会显示第一个实例)。这是通过在 Main
方法中创建命名互斥体并检查 GetLastError
API 的返回值来实现的。ERROR_ALREADY_EXISTS
表示进程已在运行。另一个实现是使用文件映射对象在进程之间进行共享内存,以及在进程之间进行事件信号。
下图包含一个进程。它创建一个命名事件。
- 进程 A 调用
CreateEvent
API,传入一个唯一的名称。 - 系统检查具有该名称的事件内核对象是否存在。为举例起见,假设不存在。系统在内存中(由内核管理)创建内核对象,并将其添加到其自己的命名内核对象表中。
- 系统将索引返回给进程。API 返回一个有效句柄:1。
注意:示例显示系统返回索引 14 作为地址。我不确定。系统也可能返回 0xAEdad4 地址。我做出这个假设是因为这个主题未被记录,而且它不会干扰讨论。
下图包含两个进程(A 和 B)。它们都创建一个命名事件。如我们在上图所示,进程 A 先创建。
- 进程 B 调用
CreateEvent
API,传入一个NULL
名称。 - 系统检查具有该名称的事件内核对象是否已存在,并发现存在。系统不创建它或将其添加到自己的内核对象表中。
- 系统将地址返回到索引。API 返回一个有效句柄:2。
如您所见,API 返回的索引是 2。我故意向索引 1 添加了另一个内核对象(假设进程 B 创建了一个线程)。我这样做是为了强调索引号与命名内核对象功能无关。