您的位置:首页 > 其它

web安全方面的一些小总结

2009-09-17 10:01 260 查看
1、数据访问授权

通过设置访问某些方法的访问角色来控制代码的安全性。代码如下:

代码应该在用户连接数据库之前根据角色或者标识对其授权。角色检查通常用在应用程序的业务逻辑中,但是如果您没有明确地区分业务和数据访问逻辑,则应该在访问数据库的方法上使用主体权限要求。

1)以下属性确保了只有是 Manager 角色成员的用户可调用 DisplayCustomerInfo 方法:

[PrincipalPermissionAttribute(SecurityAction.Demand, Role="Manager")]

public void DisplayCustomerInfo(int CustId)

{

}

2)以下代码片段使用一个显式的、编程实现的角色检查确保调用方是 Manager 角色的成员:

public void DisplayCustomerInfo(int CustId)

{

if(!Thread.CurrentPrincipal.IsInRole("Manager"))

{

. . .

}

}

2、保护敏感数据,通过添加salt 值的密码散列值:

如果您需要实现包含用户名和密码的用户存储区,则不要以明文或者加密的格式存储密码。不要存储密码,而应该存储带附加 salt 的非可逆散列值以降低字典攻击的风险。

注 salt 值是一个强加密的随机数。

创建 salt 值

以下代码说明了如何通过使用 System.Security.Cryptography 命名空间中的 RNGCryptoServiceProvider 类提供的随机数生成功能生成 salt 值。

public static string CreateSalt(int size)

{

RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

byte[] buff = new byte[size];

rng.GetBytes(buff);

return Convert.ToBase64String(buff);

}

创建(带 salt 值的)散列值

以下代码片段说明了如何从提供的密码和 salt 值生成散列值。

public static string CreatePasswordHash(string pwd, string salt)

{

string saltAndPwd = string.Concat(pwd, salt);

string hashedPwd =

FormsAuthentication.HashPasswordForStoringInConfigFile(

saltAndPwd, "SHA1");

return hashedPwd;

}

3)捕获和记录 ADO.NET 异常

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

• OLE DB .NET Framework 数据提供程序将生成 OleDbExceptions。

• ODBC .NET Framework 数据提供程序将生成 OdbcExceptions。

捕获异常

以下代码使用 SQL Server .NET Framework 数据提供程序,并说明了如何捕获 SqlException 类型的异常。

try

{

// Data access code

}

catch (SqlException sqlex) // more specific

{

}

catch (Exception ex) // less specific

{

}

日志记录异常

您还应该将来自 SqlException 类的详细信息记录下来。这个类公开了包含异常情况详细信息的属性。这包括一个说明错误的 Message 属性,一个唯一标识错误类型的 Number 属性,和一个包含其他信息的 State 属性。State 属性通常用来指示特定错误情况的某次出现。例如,如果存储过程在不止一行中出现了同样的错误,State 属性可指示特定的那一次。最后,Errors 集合包含可提供详细 SQL Server 错误信息的 SqlError 对象。

以下代码片段说明了如何通过使用 SQL Server .NET Framework 数据提供程序处理 SQL Server 错误情况:

using System.Data;

using System.Data.SqlClient;

using System.Diagnostics;

// Method exposed by a Data Access Layer (DAL) Component

public string GetProductName( int ProductID )

{

SqlConnection conn = new SqlConnection(

"server=(local);Integrated Security=SSPI;database=products");

// Enclose all data access code within a try block

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();

// The finally code is executed before the method returns

return paramPN.Value.ToString();

}

catch (SqlException sqlex)

{

// Handle data access exception condition

// Log specific exception details

LogException(sqlex);

// Wrap the current exception in a more relevant

// outer exception and re-throw the new exception

throw new Exception(

"Failed to retrieve product details for product ID: " +

ProductID.ToString(), sqlex );

}

finally

{

conn.Close(); // Ensures connection is closed

}

}

// Helper routine that logs SqlException details to the

// Application event log

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 );

}

}

4)在 ASP.NET 应用程序中使用一般性错误页

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

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

为产品服务器设置 mode="On" 。当您在发布前开发和测试软件时,只能使用 mode="Off"。如果无法做到这一点,将导致返回给最终用户丰富的错误信息,如图 4 中所示。这些信息可能包括数据库服务器名称、数据库名称和连接凭据。

图 4. 详细的异常信息会暴露敏感的数据

图 4 还显示了在导致异常的行附近的数据访问代码中仍然存在的许多缺陷。具体如下: • 连接字符串是硬编码的。

• 使用了高特权 sa 帐户来连接数据库。

• sa 帐户的密码很脆弱。

• SQL 命令构造很容易遭到 SQL 注入攻击,输入没有进行验证,而代码没有使用参数化的存储过程

下面是一个综合的示例演示如何安全操作数据层:

using System;

using System.Data;

using System.Data.SqlClient;

using System.Text.RegularExpressions;

using System.Collections.Specialized;

using Microsoft.Win32;

using DataProtection;

public static int CheckProductStockLevel(string productCode)

{

int quantity = 0;

// (1) Code protected by try/catch block

try

{

// (2) Input validated with regular expression

// Error messages should be retrieved from the resource assembly to help

// localization. The Localization code is omitted for the sake of brevity.

if (Regex.IsMatch(productCode, "^[A-Za-z0-9]{12}$") == false)

throw new ArgumentException("Invalid product code" );

//(3) The using statement ensures that the connection is closed

using (SqlConnection conn = new SqlConnection(GetConnectionString()))

{

// (4) Use of parameterized stored procedures is a countermeasure for

// SQL injection attacks

SqlCommand cmd = new SqlCommand("spCheckProduct", conn);

cmd.CommandType = CommandType.StoredProcedure;

// Parameters are type checked

SqlParameter parm =

cmd.Parameters.Add("@ProductCode",

SqlDbType.VarChar,12);

parm.Value = productCode;

// Define the output parameter

SqlParameter retparm = cmd.Parameters.Add("@quantity", SqlDbType.Int);

retparm.Direction = ParameterDirection.Output;

conn.Open();

cmd.ExecuteNonQuery();

quantity = (int)retparm.Value;

}

}

catch (SqlException sqlex)

{

// (5) Full exception details are logged. Generic (safe) error message

// is thrown back to the caller based on the SQL error code

// Log and error identification code has been omitted for clarity

throw new Exception("Error Processing Request");

}

catch (Exception ex)

{

// Log full exception details

throw new Exception("Error Processing Request");

}

return quantity;

}

// (6) Encrypted database connection string is held in the registry

private static string GetConnectionString()

{

// Retrieve the cipher text from the registry; the process account must be

// granted Read access by the key's ACL

string encryptedString = (string)Registry.LocalMachine.OpenSubKey(

@"Software\OrderProcessing\")

.GetValue("ConnectionString");

// Use the managed DPAPI helper library to decrypt the string

DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE);

byte[] dataToDecrypt = Convert.FromBase64String(encryptedString);

return Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null));

}

上面给出的代码说明了以下安全特征(用注释行的号码标识)。 1. 数据访问代码放在一个 try/catch 块中。这对于防止在出现异常时将系统级信息返回给调用方至关重要。调用方 ASP.NET Web 应用程序或者 Web 服务可能会处理异常并将合适的一般性错误消息返回给客户端,但是数据访问代码并不依赖于此。

2. 使用正则表达式验证输入。检查了所提供的产品 ID,以验证它只包含 A–Z 和 0–9 的字符,而且不超过 12 个字符。这种设计是用来防止 SQL 注入攻击的第一个对策。

3. 在 Microsoft Visual C#_ using 语句中创建了 SqlConnection 对象。这可确保无论是否发生异常,连接都会在方法中关闭。这将降低拒绝服务攻击的威胁,这种攻击试图使用所有可用的数据库连接。您可以通过使用 finally 块得到类似的功能。

4. 使用参数化的存储过程进行数据访问。这是另一个防止 SQL 注入攻击的对策。

5. 不将详细的错误信息返回给客户端。对异常详细信息进行记录,以辅助问题的诊断。

6. 加密的数据库连接字符串存储在注册表中。最安全的存储数据库连接字符串的方式之一,是使用 DPAPI 加密字符串和将加密的密文存储在一个受到保护的带有受限 ACL 的注册表项下。(例如,使用管理员:Full Control 和 ASP.NET 或者企业服务进程帐户:Read,这取决于哪个进程承载着组件。)

注 代码说明了如何从注册表中检索连接字符串,然后使用托管的 DPAPI 辅助库将其解密。这个库是在“如何创建 DPAPI 库”中提供的,该文章在“Microsoft patterns & practices 第 I 卷,构建安全的 ASP.NET Web 应用程序:身份验证、授权和安全通讯”的“如何……”部分中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: