在C#3.0中使用LINQ轻松防御SQL注入攻击

发表于:2008-05-04来源:作者:点击数: 标签:sqlSQL防御LINQ攻击
随着Internet逐渐普及,基于Web的各种非法攻击也不断涌现和升级,因此,很多 开发 人员被要求使他们的程序变得更 安全 可靠,这也逐渐成为这些开发人员共同面对的问题和责任。而很多使用Web系统的企业也不断投入大量的资金,以购买和使用更安全的框架和 软件
 随着Internet逐渐普及,基于Web的各种非法攻击也不断涌现和升级,因此,很多开发人员被要求使他们的程序变得更安全可靠,这也逐渐成为这些开发人员共同面对的问题和责任。而很多使用Web系统的企业也不断投入大量的资金,以购买和使用更安全的框架和软件平台。很多开发人员也从一开始设计程序时就注重使用更有效的方法来建立安全的应用程序,并防止有破坏性的攻击。但不幸的是,开发团队的成员经常缺乏这方面的训练,以及进行安全设计的相关经验。

    当开发人员为应用程序加入更多的安全防护罩时,也许最先考虑的Web应用程序攻击就是我们众所周知的SQL注入攻击。这是一种通过命令注入方式进行攻击的方式。命令注入是任何通过在服务端运行非授权程序进行攻击的基本方法。这种方法一般是通过在客户端输入特殊或不可预料的字符串(在字符串中包含有破坏性的命令)来改变Web程序被期望的运行结果。由于这种攻击方式非常普遍,也非常容易掌握,因此,SQL注入攻击被大范围地使用,所以它是非常危险的,甚至是防不胜防的。但幸运的是,如果我们理解了SQL注入攻击的原理,是很容易防御的。随着微软的.NET技术的不断发展,也为开发人员提供了新的数据接口,如果适当使用它们,就可以完全防止SQL注入攻击。这项技术被称为集成查询语言,也就是LINQ(将在本文的后面进行讲解)。这项技术将和即将发布的Visual Studio2008以及.NET Framework 3.5一起发布。本文将带领读者探索如何利用LINQ来防止Web程序的SQL注入攻击,读者将会从本文深刻体会到LINQ在这方面的无限潜力。

一、什么是SQL注入

    SQL注入是一种对Web应用程序进行的攻击,黑客通过向应用程序传递恶意数据,并欺骗服务器将其当成正确的命令并执行。虽然这种攻击方法非常容易防御,但它也是非常普遍和有害的。这是因为它可以允许攻击者直接运行命令来操作我们的工作数据。在很多极端的情况下,攻击者不仅仅可以访问我们所有的数据,而且还可以删除表和数据库。甚至可以得到控制数据库服务器的权利。


    既然这些攻击如此容易防御,那么它们为什么如此危险呢?首先,我们的应用数据库是一个非常吸引人的蛋糕,原因很简单,因为在数据库中保存了很多黑客非常关心的数据。如果SQL注入成功后,攻击者是非常容易通过这种方式检测并浏览到这些数据的。虽然SQL注入错误并不是开发人员最容易犯的错误,但它可能却是攻击者最常使用,也是最容易成功的攻击方式。

   
检查SQL注入攻击最容易的方法就是在应用程序中可以访问数据库的表达式处插入一个元字符串。如一般网站都会包含一个search输入字段,如果一个攻击者输入一个数据库元字符,如一个"'"符号,如果应用程序反回一个数据库错误信息,攻击者不仅可以知道他已经发现了这个应用程序的数据库信息的一部分,而且他可以向其注入更多有意义的命令,并使服务器执行它们。应用程序安全专家Michael Sutton最近使用Google Search API在数分钟之内发现了数以百记的易受SQL注入攻击的网站。

二、SQL注入攻击剖析

   
在本节中将给出个简单的SQL注入的例子,通过这个例子读者可以了解了这个错误是多么容易犯,并且这些注入错误使用一些设计技巧和编程规范就可以很容易地避免。

   
在这个例子中,Web应用程序包含了一个简单的customer数据查询页SQLInjection.aspx。这个页布在一个非常易受SQL注入攻击的隐患。这个页包含了一个CompanyName输入服务端控件和一个数据网格控件,用来演示从微软的例子数据库Northwind查出来的结果(在本例中将使用微软SQL Server2005作为我们的演示数据库)。这个查询在执行时包含了一个非常普遍应用程序设计错误,也就是它根据用户的输入动态地建立一个SQL查询语句。这种方式对于Web应用程序访问数据库是非常不好的习惯。这是因为这样做必须有一个前提,就是使用这个Web程序的用户是可信赖的,如果用户怀有恶意,那将是非常危险的。下面是Search按钮中的生成SQL语句的代码:

protected void btnSearch_Click(object sender, EventArgs e) { String cmd = "SELECT [CustomerID], [CompanyName], [ContactName] FROM [Customers] WHERE CompanyName ='" + txtCompanyName.Text + "'"; SqlDataSource1.SelectCommand = cmd; GridView1.Visible = true; }
在这种情况下,如果一个用户输入"Oracle"作为公司名,并单击Search按钮,这样将在浏览器中显示显示和公司相符的正确的结果。但一个攻击者也可以很容易地控制动态查询语句的生成,如,通过插入UNION子句和并通过SQL的注释符号(--)来终止其余的语句。换句话说,就是使用以下的输入来代替"Oracle"作为公司名:

Oracle' UNION SELECT CustomerID, ShipName, ShipAddress

   FROM ORDERS--

   
上面SQL表达式在服务端的执行结果是向SQL语句中插入了一条恶意的请求命令。如果将其翻译成动态的SQL语句的话,代码如下:


SELECT [CustomerID], [CompanyName], [ContactName] FROM [Customers] WHERE CompanyName ='Oracle' UNION SELECT CustomerID, ShipName, ShipAddress FROM ORDERS--'
    上面所得到的SQL语句的语法完全正确,它将返回Customer表中Oracle的数据,同时也返回了Orders表中所有的数据。显示结果如图1如示
 图一个成功的SQL注入的显示结果。它通过在命令行后加入非法的命令来获取数据库中敏感的信息。
三、典型的SQL安全防护

    到现在为止我们已经看到在应用程序中开始一个SQL注入攻击并成功使用它是多么的容易。幸运的是,在上面已经提及过,SQL注入攻击很容易使用一些简单的手段防御。大多数有效的防御方法是在执行SQL语句之前验证应用程序中所有的被使用于数据访问的输入语句。我们可以在Web应用程序中直接来验证,也可以使用一些数据持久化组件,如Hibernate进行验证,当然,不管使用哪种方式,这种验证都需要在处理数据之前在服务端来验证类型、长度、格式和范围。但不幸的是,基于代码的验证方法并不十分简单,当遇到如下三种情况时,还会失败:

1. 验证程序设计不完善。

2. 验证只在客户端执行。

3. 验证失败,即使在应用程序中只有一个单独的字段。

    还有别外一种防止SQL注入攻击的方法,就是在应用程序中参数化所有的SQL查询,无论是动态的SQL表达式,或存储过程。如果写出象下面的代码应该是很安全的:

SELECT [CustomerID], [CompanyName], [ContactName]
   FROM [Customers]
   WHERE CompanyName = @CompanyName
   
参数化的查询当执行SQL表达式时将输入看到是一个字符串值;因此,将这个值作为可执行的代码是不可能的。即使我们使用存储过程,也必须使用参数化输入,这是因为存储过程并不提供使内嵌查询防止SQL注入的功能。


   
即便使用上述简单的防护措施,SQL注入仍然会成为许多组织的大问题。对于我们的开发团队来说,最大的挑战就是对所有的开发人员进行防御这种攻击的培训。甚至需要将这些标准和规范装订成册,以供开发人员随时查阅。而且这种方式为了使程序更安全,引入了很多的变量,这样可能会给我们带来更多的工作量。这就需要出现一种更方便和强大的防止SQL注入攻击的技术出现,这就是LINQ的由来。
 
四、LINQ概述

    LINQ在处理SQL注入方面是非常简单的,在LINQ中加入了标准的模式查询和更新任何
存储格式的数据 – 从SQL数据库到XML文档甚至到.NET对象。当我们建立数据库驱动的应用程序时,LING组件可以使开发人员将关系数据作为对象处理,也就是在C#和VB中的"LINQ to SQL",这种技术被认为是ADO.NET家族的数据技术的一部分。这种技术最初以CTP形式引入,因此,LINQ to SQL被称为DLINQ。

    LINQ to SQL
使我们在应用程序中可以象本地对象一样对待数据,就象我们在应用程序中对复杂的关系数据和数据连接进行管理(一般将数据库表映射成类的形式)。事实上,我们可以不写一行SQL语句通过LINQ演示和操作数据库中的数据。在运行时,LINQ to SQL将我们的动作翻译成本地的SQL语句,然后在数据库中执行。LINQ to SQL将查询结果作为对象返回,这一过程完全将我们的交互和数据库以及SQL分割开。目前并没有比在我们应用程序中消除SQL注入更快的方法。而使用LINQ to SQL,我们却能做到这一切。

五、通过LINQ使数据访问更安全

    我们用LINQ to SQL进行数据访问时,它可以很容易地消除在我们的应用程序中由于SQL注入而带来的安全隐患,这主要是由于在LINQ中每一个执行的SQL查询都是参数化的。当使用LINQ建立SQL查询时,任何提供给查询的输入都被看做是参数字符串,无论这个输入是从哪里来的。开发人员可以在Visual Studio2008中使用集成的LINQ,并通过智能编辑器和编译时语法检查来书写正确的代码。编译器可以捕捉很多可能引起功能性错误或其他类似的安全隐患的查询错误。与之对比,我们写的SQL表达式只能在运行时通过数据库系统对其进行分析和解释。这样我们就很难在运行之前知道它们是否正确。曾经有很多攻击者试着通过对LINQ采取欺骗的手段使其运行非法或具有恶意的SQL语句。但幸运的是,最新的语言和编译器阻止了这一切的发生。

    心动不如行动,在这里我们来使用LINQ实现一个customer搜索的例子,并使用LINQ来阻止SQL注入的攻击。第一步是在数据库中建立和关系数据对应的对象模型。Visual Studio2008包括了一个新的对象关系设计器(也就是O/R设计器),它可以通过拖拽的方式为我们产生全部的对象模型,其中包括对象的设计和它们之间的关系。为了对我们的Northwind中的Customers表建立对象模型,我们需要通过选择“Add New Item...”,并选择“LINQ to SQL File”模板(这个模板将打开O/R设计器)在应用程序中建立一个LINQ to SQL文件。为了自动建立完整的Customers表的对象模型,在Server Explorer中选择这个表,并将它拖到O/R设计器的界面上,如图2所示。在这个例子中,O/R设计器在应用程序中加入一个Customers.designer.cs文件来定义我们在代码中使用的类。而不是直接写代码和数据库交互。

     图2 使用O/R设计器映射Customers表

在定义完Customers表的对象模型类后,我们可以通过customer数据查询而在代码中直接查询数据。在LINQtoSQL.aspx.cs的Page_Load方法中,实例化了被O/R设计器建立的CustomersDataContext类,它重用了在上面例子中SQLInjection.aspx而使用的连接字符串。下面是LINQ查询获得的一个Customer对象的集合的代码:
protected void Page_Load(object sender, EventArgs e) { string connectionString = ConfigurationManager.ConnectionStrings ["northwndConnectionString1"].ConnectionString; CustomersDataContext db = new CustomersDataContext(connectionString); GridView1.DataSource = from customer in db.Customers where customer.CompanyName == txtCompanyName.Text orderby customer.CompanyName select customer; GridView1.DataBind(); }
使用LINQ to SQL,如果我们提供"Oracle"作为Search的值,那么在运行时和在服务器执行的通过LINQ产生的SQL表达式的代码如下:

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE [t0].[CompanyName] = @p0 ORDER BY [t0].[CompanyName]}
我们从上面的代码可以看出,WHERE子句被自动参数化了。因此,使用方便的SQL注入攻击是无法得逞的。不管用户在查询页的输入字段中输入什么值,查询总是类型安全的,而且在服务端不允许将输入字符串作为命令执行。如果我们使用上述的SQL注入攻击方法来浏览敏感数据,那么将什么也不会显示出来。

六、小结

    从上面的例子可以看出,使用LINQ是非常容易在Web程序中预防SQL注入攻击的,当然,也非常容易检查出这种错误。微软的LINQ to SQL技术使用户通过对象模型和数据库进行交互而不是直接使用SQL和数据库进行交互,从而有效地避免了SQL注入攻击。这个LINQ结架被C#和Visual Basic建立来格式化字符串和SQL表达式,并阻止SQL注入攻击的发生,以使开发人员可以将更多的经理集中到程序本身的特性上来。无论我们选择使用LINQ或是SQL作为.NET应用程序访问数据的接口,或是设计自己的数据访问接口,我们都将会做出一个选择来建立更安全的应用程序。

原文转自:http://www.ltesting.net