2006-10-19

在 .NET 中防止 SQL 注入攻击(SQL Injection)的方法

SQL 注入(SQL Injection)是最常见的数据库攻击方式,其原理为构造巧妙的参数,传入数据库执行时改变原有的含义。
举个例子,如果我们要在数据库用户表 table_user 中查询 group_id 为 3 的用户,代码是这样的:

SELECT * FROM table_user WHERE group_id = 3

其中“3”为参数,通常采用的做法是将前一部分字符串与 3 进行拼接,合并后的 SQL 语句传入数据库直接执行。如果我们更改参数,改为“3 OR 1 = 1”,那么代码就变成这样了:

SELECT * FROM table_user WHERE group_id = 3 OR 1 = 1

上面的代码已经完全绕过了 WHERE 部分的条件限制,与我们的本意不相符合,这种情况在安全方面是非常危险的。
SQL 注入的攻击方式都是通过构造参数来实现的,程序中必须要有拼接字符串产生执行语句的部分才有这方面的问题,所以,最安全的防范 SQL 注入攻击的方式就是把所有访问数据库的部分全部使用存储过程(Store Procedure)来实现,在存储过程内部也不拼接语句。这种方式也是最简单的,对于各种开发语言都有效,然而,如果需要在 .NET 程序设计中避免 SQL 注入,又不想使用存储过程,还有另外一种方式,这就是参数化查询。
ADO.NET 对于参数化查询提供了良好的封装,然而,在 ADO.NET 中,每种数据库的参数化查询语句都不一样,下面就是访问 SQL Server 数据库查询表单的一个语句:

...
string sqlText = "SELECT * FROM table_user WHERE group_id = @group_id";
SqlCommand cmd = new SqlCommand(sqlText, connection);
cmd.Parameters.Add("@group_id", SqlDbType.Int);
cmd.Parameters["@group_id"].Value = 3;
...

在上面的代码中,我们使用参数化查询的方式构造了一个 SqlCommand 对象,指定了参数的类型和值。在这种情况下, ADO.NET 底层将会处理 SQL 注入的问题,保证查询过程是安全的。
下面给出访问 MySQL 数据库查询同样数据的例子:

...
string sqlText = "SELECT * FROM table_user WHERE group_id = ?group_id";
MySqlCommand cmd = new MySqlCommand(sqlText, connection);
cmd.Parameters.Add("?group_id", SqlDbType.Int);
cmd.Parameters["?group_id"].Value = 3;
...

以下是访问 Oracle 数据库查询数据的例子:

...
string sqlText = "SELECT * FROM table_user WHERE group_id = :group_id";
OracleCommand cmd = new OracleCommand(sqlText, connection);
cmd.Parameters.Add("group_id", SqlDbType.Int);
cmd.Parameters["group_id"].Value = 3;
...

上面列出了在 3 种主流数据库中使用 .NET 参数化查询的代码,它们中的大部分都相同,只有一些细微的差别,如果需要开发面向多种数据库的程序,这点很麻烦,相对而言, JAVA 在这方面做的非常好。

0 Comments: