简单地说,SQL注入是一种技术,它可以让恶意用户注入SQL命令,这些命令在语义上是合法的,但可以改变命令预期的行为,并且有可能危及应用程序的安全。这会儿你可能在挠头,并尝试多读几次看是否有更多的意义。通过示例解释起来会更直观一些。
想想前面章节看到的SQL,一个典型的例子是根据所属的类别检索记录:
<span style="font-size:14px;">var sql = "SELECT* FROM Items where CategoryId = 2";</span>
|
该语句的问题是:类别是常量。它不会改变,这意味着始终会检索出属于WHERE语句指定的类别的数据。根据网站的情况和需求,这可能确实不是一件坏事。向用户提供一种可以通过类别过滤产品的方法是非常常见的需求。通常使用表单,将类别列表放在下拉菜单或文本框中。服务器端代码接收到传递过来的选项,用它动态组装成一条可用的SQL语句,如下所示:
<span style="font-size:14px;">var sql = "SELECT * FROM Items WHERE CategoryId = " + Request["categoryId"];</span>
|
此刻,任何传入Request[“categoryId”]的值都将和SQL字符串连接在一起并在数据库中执行。没有阻止用户向文本框里输入非法信息的措施。如果用户向文本框中输入垃圾数据,连接在一起的结果可能会不符合SQL语法,网站会出现问题;否则SQL会成功执行。在前面的例子里,如果用户输入数值2,同时对前一章的示例数据库执行SQL,那么将导致所有属于Computing类别的条目被检索出来。但是用户还可以向文本框中输入下面的内容:
<span style="font-size:14px;">2 or 1 = 1</span>
|
当被拼接在一起时候,结果会是这样:
<span style="font-size:14px;">SELECT * FROMItems WHERE CategoryId = 2 or 1 = 1</span>
|
这完全是有效的SQL,同时会返回所有行,因为WHERE 1 = 1这个条件一直是成立的。
该SQL的初衷是提供一种方法,在用户知道哪些有效的类别可用的情况下,可以将结果限制在一种类别里;但是由于允许用户通过注入额外的过滤器修改SQL的行为,导致了预料不到的结果。这就是SQL注入,是安全性不高的网站暴露出的最多的两个漏洞之一。
前面的例子看起来不怎么危险,那么SQL注入能导致什么问题呢?首先,为了好的初衷才提供了过滤器。因为有上百万条记录,过滤器原本可能是用来防止检索出所有的记录,同时持续请求所有的记录会降低网站的性能。这也使得拒绝服务攻击(DoS)变得更加容易,另外还有更大的风险。
看看下面的代码段:
<span style="font-size:14px;">var sql = @"SELECTCount(*) FROM Users WHERE UserName ='" + Request["UserName"] + "' AND Password = '"+ Request["UserPass"] + "' ";</span>
|
这常作为用户认证的方式,用于匹配在登录表单中提供的用户名和密码。它用于测试数据库中有多少条记录和用户提供的用户名和密码匹配,如果结果大于0,用户就可以进一步操作。在前面的示例中,展示了总是成立的条件并导致返回了所有的记录。这里有另外一个总是成立的条件:
<span style="font-size:14px;">WHERE ' ' = ' '</span>
|
对于黑客来说,很容易向这个SQL中注入代码,从而返回用户表中的所有数据,因此只需要向用户名或密码文本框中输入'or'"=',就可以访问网站中的限制区域。拼接将完成创建有效的可被执行的SQL语句的工作。
幸运的是,相比SQL Server完整版,SQL Server Compact有一些功能性限制。例如,Compact版本不支持批处理语句,更强大的数据库可以做到这点,这会令它们面临潜在的严重后果。批处理语句可以在一条SQL语句中传递多个操作,每个独立的命令都会依次执行。下面展示了一个批处理示例:
<span style="font-size:14px;">SELECT * FROMItems; DROP TABLE Users</span>
|
在SQL Server数据库中执行该语句,首先会返回Items表中的所有条目,然后会永久删除名为Users的数据表。甚至还有更强大的命令,能够导致整个数据库被删除,甚至导致黑客获取数据库所在服务器的完整控制权。
参数保护
现在知道了问题的本质,以及能够导致的潜在的非常严重的后果,就需要学习如何使代码远离SQL注入的威胁。一些人建议使用黑名单作为有效途径。这涉及监控用户是否输入了SQL关键词,如果检查到关键字,就驳回提交的请求。这种做法具有两面性:很多SQL关键词是日常用语(or、and等),并且SQL关键词时常变更。这意味着如果黑名单中引入了新的关键词,就不得不更新构建的每个网站。第二个建议是避开单引号,当然这也是有效的,但如果字符串不是SQL的一部分就起不了作用,就像前面的第一个示例。
事实是,只有一种被证明有效的方法能够保护网站不受SQL注入攻击,那就是使用参数。参数是动态数据的占位符,在执行SQL时,这些动态数据以一种安全的方式和SQL语句拼接在一起。为了帮助大家理解它的工作机制,下面举一个例子展示数据库辅助程序如何支持参数的使用:
<span style="font-size:14px;">var sql ="SELECT* FROM Items WHERE CategoryId = @0; var data =db.Query(sql, Request["categoryId"]);</span> |
注意,@0标记代表了SQL语句中的动态数据,@符号后面跟一个0。像往常一样,SQL被传递到Query方法,但是这次它后面跟着动态数据的源。在内部,数据库辅助程序会创建ADO.NET参数对象并且将其传递到数据库中。数据库会依次检查传入的参数值以保证它们的数据类型和目标列是匹配的,从而避免期望的是数值类型、但传入的是字符串类型的情况;也能保证按照字面含义处理所有被成功传入的字符串,而不是作为被执行SQL的一部分。当然,即使这样还是应该按照第5章中介绍的对用户输入的值进行类型验证。但是现在应该明白参数是如何提供保护网站的。如果只通过参数的形式验证数据类型,应用程序会产生很多异常。
在下面的SQL片段中,按照顺序传入了多个参数:
<span style="font-size:14px;">var sql = "INSERT INTO items (Title,Description, Price) VALUES (@0, @1, @2) "; var title =Request["title"]; var desc =Request["desc"]; var price =Request["price"]; db.Execute(sql,title, desc, price);</span>
|
参数标识从@0开始,依次递增,这是预先设定的,无法改变。如果序列从@1开始就会产生异常,遗漏任何一个数字也会产生异常。
现在清楚了如何使用数据库辅助程序和参数与数据库安全地交互。下一节将使用这些知识,将之应用到第5章中构建的表单中,在数据库中保存提交的数据。 |