当应用程序使用输入内容来构造动态 SQL 语句以访问数据库时,会发生 SQL 注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生 SQL 注入攻击。SQL 注入可能导致攻击者能够使用应用程序登录在数据库中执行命令。如果应用程序使用特权过高的帐户连接到数据库,这种问题会变得很严重。
注意 传统的安全措施(如使用 SSL 和 IPSec)不能防止 SQL 注入攻击。
防止 SQL 注入
使用下列对策来防止 SQL 注入攻击:
•限制输入。
•使用类型安全的 SQL 参数。
限制输入
验证输入内容的类型、长度、格式和范围。如果您不希望获得数值,则不要接受它们。应该考虑输入内容来自何处。如果它来自受信任源,而且您知道已针对该来源执行过彻底的输入验证,则可以选择在数据访问代码中忽略数据验证。如果数据来自不受信任源或者用于深层防御,则数据访问方法和组件应该验证输入。
使用类型安全的 SQL 参数
SQL 中的 Parameters 集合提供类型检查和长度验证。如果您使用 Parameters 集合,则输入内容将被视为文本值,SQL 不会将其视为可执行代码。使用 Parameters 集合还有一个好处,那就是可以强制进行类型和长度检查。超出范围的值会触发异常。这是深层防御的一个有力示例。
要点 SSL 不能防止 SQL 注入。对于任何应用程序来说,如果它在没有正确的输入验证和适当的数据访问技术的情况下访问数据库,都很容易受到 SQL 注入攻击。
尽可能使用存储过程,并使用 Parameters 集合来调用它们。
结合使用 Parameters 集合和存储过程
下面的代码片断阐释了 Parameters 集合的用法:
SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn); myCommand.SelectCommand.CommandType = CommandType.StoredProcedure; SqlParameter parm = myCommand.SelectCommand.Parameters.Add( "@au_id", SqlDbType.VarChar, 11); parm.Value = Login.Text;
在本例中,@au_id 参数被视为文本值,而非可执行代码。另外,还针对参数进行了类型和长度检查。在上例中,输入值不能长于 11 个字符。如果数据不遵循由参数定义的类型或长度,就会生成异常。
请注意,使用存储过程不一定会防止 SQL 注入。重要的是结合使用参数和存储过程。如果不使用参数,则在存储过程使用未筛选的输入内容时,它们很容易受到 SQL 注入攻击。例如,下面的代码片断很容易受到攻击:
SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" + Login.Text + "'", conn);
要点 如果使用存储过程,请确保同时使用参数。
结合使用 Parameters 集合和动态 SQL
如果您不能使用存储过程,仍可以使用参数,如下面的代码片断所示:
SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn); SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11); parm.Value = Login.Text;
使用参数批处理
通常会产生如下误解:如果将几个 SQL 语句连接在一起,以便在单个往返中向服务器发送一批语句,则不能使用参数。但是,如果您能确保参数名不重复,则可以使用这种技术。通过在 SQL 文本连接过程中,向每个参数名中添加一个数字或其他某个唯一值,可以方便地执行此操作。
使用筛选例程
用来防止 SQL 注入攻击的另一种方法是开发筛选例程,以便向具有特殊 SQL 含义的字符添加转义符,如单撇号字符。下面的代码片断阐释了一个用来添加转义符的筛选例程:
private string SafeSqlLiteral(string inputSQL) { return inputSQL.Replace("'", "''"); }
这种例程存在着一定的问题,而且您不应完全依赖它们,因为攻击者可以使用 ASCII 十六进制字符来回避检查。但是应该筛选输入内容,并将其作为深层防御策略的一部分。
注意 不要依赖筛选输入。
使用 LIKE 子句
请注意,如果您使用 LIKE 子句,通配符仍需要转义符。下面的代码片断阐释了这种技术:
s = s.Replace("[", "[[]"); s = s.Replace("%", "[%]"); s = s.Replace("_", "[_]");