构建安全的数据访问-异常管理(八)

异常条件可能会由配置错误、代码中的错误或恶意输入引起。如果没有正确的异常管理,这些条件可能会透露有关数据源位置和特性的敏感信息,以及有价值的连接详细信息。下面的建议适用于数据访问代码:

•捕获和记录 ADO.NET 异常。

•确保数据库连接总是处于断开状态。

•在 ASP.NET 应用程序中使用一般错误页面。

捕获和记录 ADO.NET 异常

将数据访问代码放在 try/catch 块中并处理异常。在编写 ADO.NET 数据访问代码时,由 ADO.NET 生成的异常类型取决于数据提供程序。例如:

•SQL Server .NET Framework 数据提供程序生成 SqlException

•OLE DB .NET Framework 数据提供程序生成 OleDbException

•ODBC .NET Framework 数据提供程序生成 OdbcException

捕获异常

下面的代码使用 SQL Server .NET Framework 数据提供程序,并显示应该如何捕获类型为 SqlException 的异常。

try
{
// 数据访问代码
}
catch (SqlException sqlex) // 比较具体
{
}
catch (Exception ex) // 比较一般
{
}
记录异常

还应该记录来自 SqlException 类的详细信息。此类公开那些包含异常条件详细信息的属性。这些属性包括 Message 属性(用来描述错误)、Number 属性(用来唯一标识错误类型)以及 State 属性(其中包含其他信息)。State 属性通常用来指示特定错误条件出现的具体位置。例如,如果某个存储过程从多个行中生成同一错误,则 State 属性可以指出错误出现的具体位置。最后,Errors 集合中包含 SqlError 对象,这些对象提供详细的 SQL 服务器错误信息。

下面的代码片断显示了如何通过使用 SQL Server .NET Framework 数据提供程序来处理 SQL Server 错误条件:

using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;

// 由数据访问层 (DAL) 组件公开的方法
public string GetProductName( int ProductID )
{
SqlConnection conn = new SqlConnection(
"server=(local);Integrated Security=SSPI;database=products");
// 将所有的数据访问代码包含在 try 块中
try
  {
conn.Open();
SqlCommand cmd = new SqlCommand("LookupProductName", conn );
cmd.CommandType = CommandType.StoredProcedure;

cmd.Parameters.Add("@ProductID", ProductID );
SqlParameter paramPN = 
cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 );
paramPN.Direction = ParameterDirection.Output;

cmd.ExecuteNonQuery();
// 在该方法返回之前先执行 finally 代码
return paramPN.Value.ToString();  
  }
catch (SqlException sqlex)
  {
// 处理数据访问异常条件
// 记录具体的异常详细信息
LogException(sqlex);
// 将当前异常包装在一个相关性更强的
// 外部异常中,并重新引发新异常
throw new Exception(
"Failed to retrieve product details for product ID: " + 
ProductID.ToString(), sqlex );
  }
finally
  {
conn.Close(); // 确保连接处于断开状态
  }
}

// Helper 例程,该例程将 SqlException 详细信息记录到
// 应用程序事件日志中
private void LogException( SqlException sqlex )
{
EventLog el = new EventLog();
el.Source = "CustomAppLog";
string strMessage;
strMessage = "Exception Number :" + sqlex.Number + 
"(" + sqlex.Message + ") has occurred";
el.WriteEntry( strMessage );

foreach (SqlError sqle in sqlex.Errors)
  {
strMessage = "Message:" + sqle.Message +
" Number:" + sqle.Number +
" Procedure:" + sqle.Procedure +
" Server:" + sqle.Server +
" Source:" + sqle.Source +
" State:" + sqle.State +
" Severity:" + sqle.Class +
" LineNumber:" + sqle.LineNumber;
el.WriteEntry( strMessage );
  }
}
确保数据库连接总是处于断开状态

如果发生异常,一定要断开数据库连接,并释放其他所有受限制的资源。使用 finally 块或 C# using 语句,可以确保无论是否发生了异常条件,都会断开数据库连接。上面的代码阐释了 finally 块的用法。还可以按如下方式使用 C# using 语句:

using ((SqlConnection conn = new SqlConnection(connString)))
{
conn.Open();
// 在以下情况下将断开连接:生成异常或者控制流
// 通常会离开 using 语句的使用范围
}
在 ASP.NET 应用程序中使用一般错误页面

如果您的数据访问代码由 ASP.NET Web 应用程序或 Web 服务调用,则应该对 <customErrors> 元素进行配置,以防异常详细信息传播回到最终用户。还可以通过使用该元素来指定一般错误页面,如下所示。

<customErrors mode="On" defaultRedirect="YourErrorPage.htm" />

对于生产服务器设置 mode="On"。只有在发布之前开发和测试软件时才使用 mode="Off"。如果不这样做,将导致向最终用户返回大量错误信息(如图 14.4 中显示的信息)。这些信息可能包含数据库服务器的名称、数据库名称和连接凭据。

详细的异常信息会透露敏感数据

图 14.4
详细的异常信息会透露敏感数据

图 14.4 还显示了数据访问代码中接近导致异常的行的大量漏洞。特别是:

•连接字符串是硬编码的。

•特权极高的 sa 帐户用于连接到数据库。

sa 帐户有一个弱密码。

•SQL 命令的构造容易受到 SQL 注入攻击;输入内容未进行验证,代码不使用参数化存储过程。

posted @ 2010-08-03 18:13  远大 光明  阅读(285)  评论(0编辑  收藏  举报