您的位置:首页 > 数据库

ADO.NET 基础(防SQL注入)

2012-07-16 17:19 225 查看
与数据库交互的Web应用程序中最严重的风险之一:SQL注入攻击。

SQL注入是应用程序开发人员未预期的把SQL代码传入到应用程序的过程,它由于应用程序的糟糕设计而使攻击成为可能,并且只有那些直接使用用户提供的值构建SQL语句的应用程序才会受影响。

问题在于命令时如何被执行的。SQL语句通过字符串的构造技术动态创建,文本框的值被直接复制到字符串中,他可能是这样的:

stringsql="SELECT*FROMOrderswhereCustomerID='"+txtID.Text+"'";


在这个示例中,用户可以篡改SQL语句,通常,攻击的第一个目标是得到错误信息。如果错误没有被恰当处理,底层的信息就会暴露给攻击者。现在若是在文本框中输入:ALFKI'OR'1'='1,再看看这条SQL语句,它现在是这样的:

stringsql="SELECT*FROMOrderswhereCustomerID='ALFKI'OR'1'='1'";


这样产生的后果是没有显示当前用户的特定信息,却向攻击者提供了全部资料,如果屏幕上显示的是敏感信息,如社会保险号,生日或者信用卡资料等,就会带来严重的问题!

还可以进行更复杂的攻击!例如攻击者可以利用两个连接号(--)注释掉SQL语句剩余部分,虽然这样的攻击只限于SQLServer,不过对于其他类型的数据库也有等效的办法。另外,攻击者还可以执行含有任意SQL语句的批处理命令,对于SQLServer,攻击者只需加上分号(;),攻击者用这样的方式还可以删除其他表的内容。甚至调用SQLServer的系统存储过程xp_cmdshell在命令行执行任意的程序。

下面是攻击者在文本框中输入的,他的目的是删除Customers表的全部行:ALFKI';DELETE*FROMCustomers,得到的SQL语句是这样:

"SELECT*FROMOrderswhereCustomerID='ALFKI';DELETE*FROMCustomers"


如何防止SQL注入攻击?

预防手段

使用TextBox.MaxLength属性防止用户输入过长的字符,这样减少了贴入大量的脚本的可能性

使用ASP.NET验证控件锁定错误的数据

限制错误信息给出的提示,捕获到异常时只显示一些通用的信息,而不是显示Exception.Message属性中的信息,它会暴露出系统的攻击点

更为重要的是,一定要小心去掉特殊字符,比如将单引号替换为两个单引号

最好的解决方法是使用参数化的命令或者使用存储过程执行转义以防止SQL注入攻击

使用参数化命令:

参数化命令是在SQL文本中使用占位符的命令,占位符表示需要动态替换的值,它们通过Command对象的Parameters集合来传送。

例如下面这条SQL语句:


SELECT*FROMCustomerswhereCustomerID='ALFKI'


可以写成这样:


SELECT*FROMCustomerswhereCustomerID=@CustID

占位符随后单独提供并被自动编码



为每个参数创建一个Parameter对象,这些对象被加入到Command.Parameters集合中。下面的示例重写前面的代码防止可能的SQL注入攻击:

stringconnStr=WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;


SqlConnectionconn=newSqlConnection(connStr);




//这里使用参数化的SQL语句


stringsql="SELECT*FROMCustomerswhereCustomerID=@CustID";


SqlCommandcmd=newSqlCommand(sql,conn);




//这里配置Command.Parameters集合


cmd.Parameters.AddWithValue("@CustID",txtID.Text);


conn.Open();


SqlDataReaderreader=cmd.ExecuteReader();


若在修改后的页面上再次尝试SQL注入攻击,将得不到任何记录。因为没有客户ID值与文本框输入的ALFKI'OR'1'='1'相等的订单项,这正是我们期待的结果。

调用存储过程

参数化命令时调用完整功能存储过程的诸多命令中的一小部分。

存储过程当然是保存在数据库上的批次执行的一条或多条SQL语句。它们是良好的逻辑封装体,可以接收(输入参数)和返回(输出参数)数据。

存储过程有很多优点:

更易于维护:例如,你可以优化存储过程中的命令而不必重新编译使用它的程序。

可以更安全地使用数据库:例如,可以让执行ASP.NET程序的Windows账号可以执行数据库存储过程,但不能访问基表。

可以提升性能:因为存储过程是多条语句的集合体,访问一次数据库可以做很多事情,如果数据库在其他计算机(不是web服务器)上,可以极大的减少执行复杂任务的总时间。

我们通过一个较为完整的示例来学习这一过程,向Northwind数据库添加一个存储过程:

createprocedureInsertEmployee


@TitleOfCourtesyvarchar(25),


@LastNamevarchar(20),


@FirstNamevarchar(10),


@EmployeeIDintoutput


as


insertintoEmployees(TitleOfCourtesy,LastName,FirstName,HireDate)


values(@TitleOfCourtesy,@LastName,@FirstName,GetDate());




Set@EmployeeID=@@identity


这个存储过程有3个输入参数,1个输出参数。顺便一提,如果不使用存储过程的输出参数功能,要从刚刚插入的记录中获得自动生成的标识会是一件很麻烦的事。接着,我们创建一个程序来调用存储过程:

protectedvoidPage_Load(objectsender,EventArgse)


{


stringconnStr=WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;


SqlConnectionconn=newSqlConnection(connStr);




//调用存储过程,必须指定Command.CommandType


SqlCommandcmd=newSqlCommand("InsertEmployee",conn);


cmd.CommandType=System.Data.CommandType.StoredProcedure;




//向存储过程传递参数


//需要精确指定【数据类型】和【参数的大小】以便和数据库中的细节相匹配


//使用参数的Value属性进行赋值


cmd.Parameters.Add(newSqlParameter("@TitleOfCourtesy",SqlDbType.NVarChar,25));


cmd.Parameters["@TitleOfCourtesy"].Value=Title;


cmd.Parameters.Add(newSqlParameter("@LastName",SqlDbType.NVarChar,20));


cmd.Parameters["@LastName"].Value=LastName;


cmd.Parameters.Add(newSqlParameter("@FirstName",SqlDbType.NVarChar,10));


cmd.Parameters["@FirstName"].Value=FirstName;




//【输出参数】也使用相同的方式添加


//但是必须指定它的Direction属性为OutPut


cmd.Parameters.Add(newSqlParameter("@EmployeeID",SqlDbType.Int,4));


cmd.Parameters["@EmployeeID"].Direction=ParameterDirection.Output;




//执行数据库命令


using(conn)


{


conn.Open();


intrtv=cmd.ExecuteNonQuery();


Label1.Text=string.Format("Inserted<b>{0}</b>record(s)<br/>",rtv);




//获取存储过程的输出参数


intempID=(int)cmd.Parameters["@EmployeeID"].Value;


Label1.Text+="Newid:"+empID.ToString();


}


}


Parameters集合的一个方便的方法时AddWithValue()。该方法接收参数名及其值但不包括数据类型的信息,而是根据提供的数据猜测数据类型。显然,这对输出参数无效,因为你压根不会为输出参数提供值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐
章节导航