ASP.NET 安全功能分析






4.70/5 (9投票s)
分析 ASP.NET 的各种安全功能
引言
ASP.NET 是开发 Web 应用程序的一个非常有潜力的技术。现在很多应用程序都是用 ASP.NET 开发的。对于任何 Web 应用程序来说,安全性都是一个非常重要的特性。特别是那些包含敏感用户信息或任何财务数据的 Web 应用程序,安全性对它们来说至关重要。有时 Web 应用程序提供的额外安全功能可能会降低 Web 应用程序的整体性能。因此,应该正确选择安全功能,以确保应用程序可靠,并且性能也不会受到太大影响。本文考虑了 ASP.NET 2.0 中的安全功能。但这些也适用于更高版本。
我们可以将整个安全功能分析分为以下几个主要类别
- 身份验证
- 配置
- 数据访问安全
- 代码访问安全
- 异常处理
- 通信安全
身份验证
身份验证是指检查用户是否是注册用户或受信任用户。ASP.NET 2.0 提供了两种身份验证类型
- 表单身份验证
- Windows 身份验证
wherever it is possible. Because Windows Authentication uses the active directory or similar structure to store the user name and passwords, and we need not to create password policies and password encryption to store them in database. ASP.NET uses identity object to represent the current user’s identity. This object internally implements the System.Security.Principal.IIdentity interface to represent the authenticated user. We can access the authenticated user through HttpContext.Principal.IIdentity regardless of authentication method, whether it is Forms authentication or Windows authentication.
表单身份验证
在表单身份验证中,用户名和密码组合存储在数据库或配置文件中。用户提供凭据,并将其与存储的凭据进行验证。ASP.NET 2.0 提供了 MembershipProvider 抽象类。Membership 功能内置了用户存储的提供程序,包括 SQL Server、Active Directory 和 Active Directory Application Mode (ADAM)。
在实现表单身份验证之前,应考虑以下几点
- 我们应该使用 ASP.NET 内置的 MembershipProvider 类,而不是自己实现自定义逻辑来验证凭据。
- 在身份验证中,用户凭据会通过网络传输,因此我们应该使用 SSL 来通过网络发送凭据。
- 如果我们无法使用安全套接字层 (SSL),则应尝试缩短会话超时时间。
- 密码不应直接存储在配置文件中。如果密码直接存储在任何地方,则密码必须经过适当加密,并且应处理加密和解密的逻辑。
- 应强制执行强密码策略,以获得更高的安全性。
- 我们永远不应持久化身份验证 Cookie,因为它们存储在用户的配置文件中,可能会被攻击者窃取。
Windows 身份验证
wherever it is possible. Because by windows authentication we are having the benefits of Active directory, enforceable account and password policy and one centralized storage of credentials. For making Secure Windows authentication we can consider the followings
- 应设置密码长度和复杂性。
- 应设置密码过期。这样,密码在一段时间后会不断更改。
wherever possible, you should use Windows authentication because this enables you to use an existing identity store such as your corporate Active Directory, it enables you to enforce strong password policies, you do not need to build custom identity store management tools and passwords are not transmitted over the network.
配置
任何 ASP.NET Web 应用程序都有大量的配置设置。这些设置对于任何 Web 应用程序都非常重要。其中连接字符串是数据库连接最重要的部分。有时开发人员直接在 web.config 文件中明文提供数据库凭据。这是一个糟糕的做法,如果您在连接字符串中提供凭据,那么我们应该使用以下命令加密连接字符串
aspnet_regiis -pe "connectionStrings" -app "/<Name of the application>"
现在,如果您想将连接字符串解密回来,可以运行以下命令
aspnet_regiis -pd "connectionStrings" -app "/<Name of the application>"
修改后的连接字符串将如下所示
<connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider">
<EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyName>Rsa Key</KeyName>
</KeyInfo>
<CipherData>
<CipherValue>R7cyuRk+SXJoimz7wlOpJr/YLeADGnwJVcmElHbrG/B5dDTE4C9rzSmm
TsbJ9Xcl2oDQt1qYma9L7pzQsQQYqLrkajqJ4i6ZQH1cmiot8ja7Vh+yItes7TRU1AoXN9
T0mbX5H1Axm0O3X/285/MdXXTUlPkDMAZXmzNVeEJHSCE=</CipherValue>
</CipherData>
</EncryptedKey>
</KeyInfo>
<CipherData>
<CipherValue>
d2++QtjcVwIkJLsye+dNJbCveORxeWiVSJIbcQQqAFofhay1wMci8FFlbQWttiRYFcvxrmVfNS
xoZV8GjfPtppiodhOzQZ+0/QIFiU9Cifqh/T/7JyFkFSn13bTKjbYmHObKAzZ+Eg6gCXBxsVEr
zH9GRphlsz5ru1BytFYxo/lUGRvZfpLHLYWRuFyLXnxNoAGfL1mpQM7M46x5YWRMsNsNEKTo/
PU9/Jvnh/lT+GlcgCs2JRpyzSfKE7zSJH+TpIRtd86PwQ5HG3Pd2frYdYw0rmlmlI9D
</CipherValue>
</CipherData>
</EncryptedData>
</connectionStrings>
数据访问安全
通过数据访问安全,我们的意思是说不应允许未经授权的非法访问数据库。一些黑客可能通过表单注入有害代码,从而非法访问数据库。因此,在将所有数据提交到后端之前,都应进行适当的验证。
为检查此类安全性,我们应考虑以下几点
- 在进行任何处理之前,所有数据都应根据长度、输入和格式进行验证。
- 我们应该使用正确的正则表达式类来验证后端数据的格式。
- 应避免基于用户提供的数据生成动态查询,这意味着用户提供的任何参数都不应直接参与查询生成。
- wherever possible, rather than just batch of queries.
- 如果发生任何错误,则应进行适当的回滚,这样数据库就不会处于不一致的状态。
- 使用具有受限权限的视图,而不是在数据库存储过程和直接 SQL 查询中使用表名。
- 创建一个权限最少的窗口帐户,用于访问数据库中的每个对象,并使用该帐户访问数据库。
- 即使数据库本身也不应直接包含任何密码。如果存在密码,则必须是加密形式。
- 也尽量使用 Windows 身份验证来访问数据库,因为它提供了一些好处,例如将凭据存储在集中且安全的地方,并且 Active Directory 已经实现了密码策略等。
- 必须为每一个数据访问函数进行适当的异常处理。最佳实践是创建一个日志文件来记录异常。我在这里提供一个用于创建日志文件的示例代码。
//*******************************************************************************************************
//1. File Name : CreateLogFiles.cs *
//2. Description : This is the class for logging all the erros in log files wherever the *
// exceptions occurred in web application *
//3. Created by : Mudit Agarwal *
//4. Modification Log *
// |=======================================================================================| *
// | Ver. No | Date | Author | Modification | *
// | 1.0 | April-01, 2008 | Mudit Agarwal | First Version | *
// | 1.01 | May -13, 2008 | Mudit Agarwal | Second Version | *
// |=======================================================================================| *
//*******************************************************************************************************
using System;
using System.IO;
using System.Text;
/// <summary>
/// Summary description for CreateLogFiles
/// </summary>
///
namespace DataAccessLayer
{
/// <summary>
/// The class used for creating the log files
/// </summary>
public class CreateLogFiles
{
/// <summary>
/// Declaration of variables
/// </summary>
//The static variable intCount for keeping track of number of exceptions before writing to log
private static int intCount;
//The string in which we are appending the exceptions and after 20 exceptions we write it in file
private static String strError;
//Starting format for writing to log file
private string sLogFormat;
//The name of LogFile so as to create the log files at daily basis
private string sErrorTime;
/// <summary>
/// The constructor for CreateLogFiles class
/// </summary>
public CreateLogFiles()
{
//Setting the values for sLogFormat as date Time + arrow sign
sLogFormat = DateTime.Now.ToShortDateString().ToString() + " " +
DateTime.Now.ToLongTimeString().ToString() + " ==> ";
//Store the value of sYear, sMonth, sDay and concatenate them to create
//sErrorTime
string sYear = DateTime.Now.Year.ToString();
string sMonth = DateTime.Now.Month.ToString();
string sDay = DateTime.Now.Day.ToString();
sErrorTime = sYear + sMonth + sDay;
}
/// <summary>
/// This function is used to write the exception(s) to log file. Path to log
/// file, and exception message are
/// passed as parameters
/// </summary>
/// <param name="sPathName">This is the path for Log file, if it is empty
/// string then some default path is set</param>
/// <param name="sErrMsg">This is the actual error message</param>
public void ErrorLog(string sPathName, string sErrMsg)
{
//If the path is empty string then set some default path
if (sPathName == "")
{
sPathName = "<Path>\\ErrorLog";
}
//Format the error message
sErrMsg = DateTime.Now.ToString() + " ==> " + sErrMsg;
//Use try catch block for file handling operations
try
{
//Create the object of stream writer class for logging into the file
StreamWriter sw = new StreamWriter(sPathName + sErrorTime + ".txt", true);
//Use synchronization in case of multiple users access the file at same time
lock (sw)
{
//Writing the error message to log file
sw.WriteLine(sErrMsg);
//Use flush to remove the message from buffer and then close the streamwriter object
sw.Flush();
sw.Close();
}
}
catch (Exception) // For handling all the exceptions
{
}
}
/// <summary>
/// The function used to create error string which contains multiple exceptions, when it reached to 20;
/// it writes all of them at one go
/// </summary>
/// <param name="msg">The message to be written as Exception</param>
public void CreateErrorString(String msg)
{
//If 20 exceptions has not been occurred then append the exception to string
if (intCount < 21)
{
intCount++;
strError += DateTime.Now.ToString() + "==> " + msg + "\r\n";
}
//As soon as 20 exceptions occurred then write them to file and set the
//counter again to 0
else
{
CreateLogFiles obj = new CreateLogFiles();
obj.ErrorLog("<Path>\\ErrorLog", strError);
strError = "";
intCount = 0;
}
}
}
}
上面的代码包含两个方法:“public void ErrorLog(string sPathName, string sErrMsg)” 和 “public void CreateErrorString(String msg)”。第一个方法应在用户负载较低或错误发生次数较少时使用,第二个方法则在并发用户数较多时使用。
代码访问安全
通过代码访问安全,我们的意思是限制我们的代码可能访问的系统资源以及我们的代码可能执行的特权操作类型。这些限制与调用代码的用户无关。为了使用代码访问安全,我们应考虑以下指南
- 如果我们的应用程序仅使用托管代码,我们可以使用各种信任级别来限制安全攻击的暴露。为此,我们可以在 web.config 文件中添加以下行。
<trust level="Full|High|Medium|Low|Minimal" />
- 我们应该使用不超出应用程序要求的信任级别。我们可以使用 ASP.NET 提供的权限计算器工具 (Permcal.exe) 来计算任何代码所需的权限。
- 在共享网络中,我们应该使用 Medium Trust。
- 我们可以使用 ASP.NET 的运行状况监视和审计事件来检查不同用户对某些敏感代码的可访问性。它的作用很简单,就是记录每次他们访问某个资源或受限制的内容时的事件。
这些事件在内部将 Web 应用程序的错误和警告记录到事件查看器中。
- 要使用代码访问安全,我们应在应用程序中执行以下步骤
首先,我们应该确定我们的应用程序所需的权限。我们可以通过手动分析权限或使用 Permcal.exe 工具来完成。要查看程序集所需的权限,我们应该从 Visual Studio 命令提示符运行以下命令
Permcal –Show <assembly>
上述命令的输出如下
<assembly />
<namespace name="ClassLibrary1" />
<type name="Class1" />
<method sig="instance void test()" />
<method sig="instance void .ctor()">
<demand />
<permissionset class="System.Security.PermissionSet" version="1" />
<ipermission class="System.Security.Permissions.RegistryPermission, mscorlib,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" read="true" />
<ipermission class="System.Security.Permissions.FileIOPermission, mscorlib,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" unrestricted="true" />
</permissionset />
</demand />
<sandbox />
<permissionset class="System.Security.PermissionSet" version="1" />
<ipermission class="System.Security.Permissions.RegistryPermission, mscorlib,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" read="true" />
<ipermission class="System.Security.Permissions.FileIOPermission, mscorlib,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" unrestricted="true" />
</permissionset />
</sandbox />
</method>
</type />
</namespace />
</assembly />
检查 <demand> 元素中列出的权限。之后,尝试根据下表评估应用程序可能需要的信任级别。
信任级别 | 主要功能和限制 |
满 | 代码访问安全不施加任何限制。 |
高 | 无非托管代码。 无企业服务。 可以访问 Microsoft SQL Server 和其他 OLE DB 数据源。 可以使用 SMTP 服务器发送电子邮件。 非常有限的反射权限。无法通过反射调用代码。 提供了广泛的其他框架功能。应用程序可以完全访问文件系统和套接字。 |
媒体 | 权限仅限于应用程序在其目录结构内可以访问的范围。 不允许访问应用程序虚拟目录结构之外的任何文件。 可以访问 SQL Server。 可以使用 SMTP 服务器发送电子邮件。 对某些常用环境变量的访问权限有限。 完全没有反射权限。 无套接字权限。 要访问 Web 资源,您必须显式添加端点 URL — 在 <strong>originUrl</strong> 属性的 <strong><em>trust</em></strong> 元素中,或在策略文件内部。 |
低功耗 | 旨在模拟一个无网络连接的只读应用程序的概念。 在应用程序的虚拟目录结构内对文件 I/O 进行只读访问。 |
最小 | 仅执行。 无法更改线程上的 IPrincipal 或 HttpContext。 |
为该特定信任级别配置 ASP.NET 应用程序。在 web.config 中使用以下节点。
...
<system.web>
...
<trust level="Medium" />
...
</system.web>
...
异常处理
异常处理也是安全性中重要的一部分。ASP.NET 为我们提供了同时处理所有未处理异常的功能。我们可以在 global.asax 中添加一个全局应用程序错误处理程序,它将处理我们应用程序中发生的所有异常。我写了一个全局错误处理程序的示例,应该将其添加到 global.asax 文件中。
void Application_Error(object sender, EventArgs e)
{
// Code that runs when an unhandled error occurs
Exception ex = Server.GetLastError().GetBaseException();
// log the details of the exception and page state to the
// event log
CreateLogFiles Err = new CreateLogFiles();
try
{
HttpException httpException = (HttpException)ex;
httpCode = httpException.GetHttpCode();
}
catch
{
}
finally
{
Err.ErrorLog("<path>\\ErrorLog", DateTime.Now.ToString() +
" User : " + name[1].ToString() + " ==>" + Server.GetLastError().TargetSite.ToString()
+ " Error Status code : " + httpCode.ToString() + ex.Message);
switch (httpCode)
{
case 500: Response.Redirect("../../../ErrorPages/InternalServerError.htm"); break;
case 403: Response.Redirect("../../../ErrorPages/NoAccess.htm"); break;
case 404: Response.Redirect("../../../ErrorPages/PageNotFound.htm"); break;
default: Response.Redirect("../../../ErrorPages/ErrorStatus.htm"); break;
}
}
}
CreateLogFiles 是我们在开始时用于记录错误的同一个类。除此之外,我们还可以为每个函数或需要的地方编写单独的异常处理代码。我们可以向 web.config 添加自定义错误节点,将用户重定向到某个错误页面,而不是显示错误。
通信安全
在使用敏感数据时,我们应考虑以下几点,我们应使用 SSL 或 IPSec。我们还应考虑优化使用 SSL 的页面。对于必须使用 SSL 的页面,我们应考虑以下几点
- 我们应使页面大小尽可能小。
- 我们应避免在这些页面上使用图像或视频内容。
摘要
本文帮助开发人员快速回顾 ASP.NET Web 应用程序的安全功能。开发人员可以快速实现 Web 应用程序的安全功能,而无需通读 ASP.NET 安全功能的全部大部头书籍。