16.7 技术解惑
16.7.1 正确验证用户输入数据的经验
在Web应用开发过程中,程序员最大的失误往往是无条件地信任用户输入。假定用户(即使是恶意用户)总是受到浏览器的限制,总是通过浏览器和服务器交互,从而打开了攻击Web应用的大门。实际上,从最低级的字符模式的原始界面(如Telnet),到CGI脚本扫描器、Web代理、Web应用扫描器,恶意用户可能采用的攻击模式和攻击手段有很多,根本不必局限于浏览器。因此,只有严格地验证用户输入的合法性,才能有效地抵抗黑客的攻击。应用程序可以使用多种方法(甚至是验证范围重叠的方法)执行验证。例如,在认可用户输入之前执行验证。确保用户输入只包含合法的字符,而且所有输入域的内容长度都没有超过范围(以防范可能出现的缓冲区溢出攻击),在此基础上再执行其他验证,确保用户输入的数据不仅合法,而且合理。必要时不仅可以采取强制性的长度限制策略,而且还可以对输入内容按照明确定义的特征集执行验证。下面几点建议有助于读者正确验证用户输入数据。
(1)始终对所有的用户输入执行验证,验证必须在一个可靠的平台上进行,并且应当在应用的多个层上进行。
(2)除了输入、输出功能必需的数据之外,不要允许输入其他任何内容。
(3)设立“信任代码基地”,允许数据进入信任环境之前执行彻底的验证。
(4)登录数据之前先检查数据类型。
(5)详尽地定义每一种数据格式,例如缓冲区长度、整数类型等。
(6)严格定义合法的用户请求,拒绝所有其他请求。
(7)测试数据是否满足合法的条件,而不是测试不合法的条件。这是因为数据不合法的情况很多,很难详尽列举。
16.7.2 ASP.NET中的角色管理
角色管理可以帮助管理授权,而授权能够指定应用程序中的用户可访问的资源。角色管理允许向角色分配用户(如Manager、Sales、Member等),从而将用户组视为一个单元(在Windows 中,可通过将用户分配到Administrators、Power Users等组来创建角色)。
建立角色后,可以在应用程序中创建访问规则。例如,站点中可能包括一组只希望对成员显示的页面。同样,用户可能希望根据当前用户是否是经理而显示或隐藏页面的一部分。通过使用角色,可以独立于单个应用程序用户建立这些类型的规则。例如,无需为站点的各个成员授予权限,以允许他们访问仅供成员访问的页面;而是可以为成员角色授予访问权限,然后只需在登录时将用户添加到角色中或从角色中删除,或让用户的成员资格失效。有关更多信息,请参见实例演练:通过角色管理网站用户。
用户可以属于多个角色。例如,如果网站是个论坛,则有些用户可能同时具有成员角色和版主角色。可能会定义每个角色在网站中拥有不同的权限,同时具有这两种角色的用户将具有两组权限。即使应用程序只有很少的用户,您仍能发现创建角色的方便之处。角色使用户可以灵活地更改特权、添加和删除用户,而无需对整个站点进行更改。为应用程序定义的访问规则越多,使用角色这种方法向用户组应用更改就越方便。
建立角色的主要目的是为用户提供一种管理用户组的访问规则的便捷方法。创建用户,然后将用户分配到角色(在Windows中,将用户分配到组)。典型的应用是创建一组要限制为只有某些用户可以访问的页面。通常的做法是将这些受限制的页面单独放在一个文件夹内。然后,可以建立允许和拒绝访问受限文件夹的规则。例如,可以配置站点,使成员和经理可以访问受限文件夹中的页面,并拒绝其他所有用户的访问。如果未被授权的用户尝试查看受限制的页面,该用户会看到错误消息或被重定向到指定的页面。
16.7.3 ASP.NET角色管理的工作原理
若要使用角色管理,首先要启用它,并配置能够利用角色的访问规则(可选)。然后在运行时即可使用角色管理功能处理角色。
角色管理配置
如果要使用ASP.NET角色管理,需使用如下所示的配置代码在应用程序的Web.config文件中启用它。
<roleManager
enabled="true"
cacheRolesInCookie="true" >
</roleManager>
角色的典型应用是建立规则,用于允许或拒绝对页面或文件夹的访问。可以在Web.config 文件的<authorization>节中设置此类访问规则。下面的示例演示了如何允许members角色的用户查看名为“MemberPages”的文件夹中的页面,同时也拒绝任何其他用户的访问。
<configuration>
<location path="MemberPages">
<system.web>
<authorization>
<allow roles="members" />
<deny users="*" />
</authorization>
</system.web>
</location>
<!-- other configuration settings here -->
<configuration>
另外,还必须创建manager或member之类的角色,然后将用户ID分配给这些角色。如果应用程序使用Windows身份验证,则可以使用Windows计算机管理工具创建用户和组。如果使用Forms身份验证,则可以使用ASP.NET网站管理工具设置用户和角色。如果用户愿意,可以通过调用各种角色管理器的方法以编程方式执行此任务。下面的示例演示了如何创建角色members。
Roles.CreateRole("members");
而下面的代码演示了如何将用户JoeWorden单独添加到角色manager中,以及如何将用户JillShrader和ShaiBassli同时添加到角色members中。
Roles.AddUserToRole("JoeWorden", "manager");
string[] userGroup = new string[2];
userGroup[0] = "JillShrader";
userGroup[1] = "ShaiBassli";
Roles.AddUsersToRole(userGroup, "members");
另外,读者需要注意,角色管理功能不能通过ASP.NET角色服务使用,因为角色服务只可以返回有关特定用户的信息。
16.7.4 ASP.NET应用程序标识
当ASP.NET页正在执行时,服务器必须具有正在执行ASP.NET代码的进程的安全上下文(或标识)。在使用Windows集成安全性保护资源(如使用NTFS文件系统保护的文件或网络资源)时,需使用此标识。
例如,如果文件包含存储在应用程序的App_Code子目录中的应用程序代码,则只能由ASP.NET应用程序标识读取。因此,可以限制App_Code中文件的安全设置,以便ASP.NET应用程序标识只有读访问权限。ASP.NET应用程序的Windows的另一个常见用法就是作为使用集成安全性连接到SQL Server的标识。
ASP.NET应用程序的标识由若干因素确定。在默认情况下,ASP.NET页使用处理Web服务器上ASP.NET页的服务的Windows标识运行。在运行Windows Server 2003的计算机上,该标识是ASP.NET应用程序所属的应用程序池的标识(默认情况下为NETWORK SERVICE账户)。在运行Windows 2000和Windows XP Professional的计算机上,该标识是在安装.NET Framework时创建的本地ASPNET账户。用户可以根据需要,将该标识配置为其他标识。
通过使用system.web配置节的identity元素,可以修改ASP.NET页运行时使用的Windows标识。可以使用identity元素指示ASP.NET模拟Windows用户ID。模拟Windows标识意味着应用程序的ASP.NET页将以该Windows标识运行。可以指定要模拟的用户名和密码。或者,可以启用模拟功能。模拟功能启用后,ASP.NET将以下列两种方式之一运行:由IIS指定的匿名标识或由IIS确定的经过身份验证的浏览器标识(如匿名身份验证、Windows集成的(NTLM)身份验证等)。
如果模拟的是Windows标识,可以执行代码恢复为进程的原始标识而不是模拟用户ID。出于此原因,在需要将应用程序相互隔开的环境中,应将这些应用程序隔离在运行Windows Server 2003的计算机上的单独的应用程序池中。每个应用程序池都应配置为使用唯一的Windows标识。
例如,在如下所示的代码中,通过使用GetCurrent方法返回的WindowsIdentity的Name属性,可以很容易地确定正在运行ASP.NET页的操作系统线程的Windows标识。
<%=System.Security.Principal.WindowsIdentity.GetCurrent().Name%>
16.7.5 有关代码访问安全性的知识
每个以公共语言运行时为目标的应用程序(即每个托管应用程序)在运行时必须能够与系统进行安全的交互。在加载某个托管应用程序时,其宿主会自动向其授予一组权限。 这些权限由宿主的本地安全设置或该应用程序所在的沙盒决定。 根据这些权限的不同,该应用程序可能会正常运行,也可能会产生安全性异常。
桌面应用程序的默认宿主允许代码在完全信任的环境下运行。 因此,如果您的应用程序以桌面为目标,则其具有一个不受限制的权限集。其他宿主或沙盒为应用程序提供了一个有限的权限集。由于此权限集可能因宿主而异,因此必须将应用程序设计为仅使用目标宿主允许的权限。
为了编写以公共语言运行时为目标的有效应用程序,编程人员必须熟悉下面的代码访问安全性概念。
- 类型安全代码。类型安全代码是仅以定义完善的、允许的方式访问类型的代码。例如,给定有效的对象引用,类型安全代码可以按对应于实际字段成员的固定偏移量来访问内存。如果代码以任意偏移量访问内存,该偏移量超出了属于该对象的公开字段的内存范围,则它就不是类型安全的代码。若要使代码受益于代码访问安全性,必须使用可生成能验证为类型安全的代码的编译器。
- 强制性语法和声明式语法。以公共语言运行时为目标的代码可以通过特定的步骤与安全系统交互,该步骤包括请求权限,要求调用方拥有指定的权限,然后重写某些安全设置(如果有足够特权的话)。可使用两种形式的语法以编程方式与 .NET Framework 安全系统进行交互:声明式语法和强制性语法。声明式调用使用特性执行;强制性调用在代码中使用类的新实例执行。有些调用只能强制性地执行,有些调用只能以声明方式执行,还有些调用可以按照这两种方式中的任一种方式执行。
- 安全类库。安全类库使用安全要求来确保库的调用方拥有访问库公开的资源的权限。例如,安全类库可能有创建文件的方法,该方法可能要求其调用方拥有创建文件的权限。.NET Framework 包含安全类库。
- 透明代码。从.NET Framework 4 开始,除了标识特定权限之外,还必须确定用户的代码是否以“安全-透明”的方式运行。“安全-透明”代码不能调用标识为“安全-关键”的类型或成员。此规则适用于完全信任的应用程序和部分信任的应用程序。