#25 SQL Injection
真的有人会把他的孩子起名为 Robert'); DROP TABLE Students;吗?
- XKCD No. 327
接下来的几篇,我们会讨论一些关于安全的话题,以免你的站点频频遭受黑客的攻击。其中第一个基本安全原则就是永远不要信赖来自用户的输入。在Rails中来讲就是说,从页面参数中传递进来的数据一定要小心对待。用户可以有意的设置参数值,甚至可以设置参数键,所以得谨慎的使用。这个原则对于cookie数据也同样适用。而会话数据中的内容是由我们的程序控制的,可以放心使用。
所有安全问题中,SQL注入是最最臭名昭著的了。当糟糕的代码实现将用户输入的SQL语句直接拼接在请求中提交至数据库,这悲剧的一切就发生了。通过一个例子来说明吧:
小例子:任务查询
在这个例子中,用户可以在输入框中写入查询条件,点击"Search"后系统会向数据库提交一个类似于SQL语句的查询并返回名称与输入匹配的所有任务。
用户输入被直接传递到了向数据库发送的请求中,这样实现是有安全风险的。
class TasksController < ApplicationController def index @tasks = Task.find(:all,:conditions=>"name LIKE ’%#{params[:query]}%’") end end
TasksController 代码展示了一个不安全的SQL查询。
问题在于,如果用户在输入框中不是简单的输入一个任务的名称,而是一个单引号后面再随便写点什么比如他输入Task1 'test
。查询下发后就会报错了。
Processing TasksController#index (for 127.0.0.1 at 2009-02-01 21:29:26) [GET] Parameters: {"query"=>"Task 1’TEST"} Task Load (0.0ms) SQLite3::SQLException: near "TEST": syntax error: SELECT * FROM "tasks" WHERE (name like ’%Task 1’TEST%’) ActiveRecord::StatementInvalid (SQLite3::SQLException: near "TEST": syntax error: SELECT * FROM "tasks" WHERE (name like ’%Task 1’TEST%’) ):
如果用户输入单引号会导致SQL语句生成错误。
我们代码中的SQL语句中本来期待用户输入一个查询条件,可是没想到他却输入了一个单引号,一下子将SQL语句给结束了。单引号后面的部分又成了一个不完整的SQL语句,执行起来肯定就要报错了。如何做能避免这种情况发生呢?
解决方法是避免让查询条件中出现本应我们自己控制的单引号。Rails为我们提供了方便的手段完成这个工作。将SQL语句中的参数用一个?
代替后放在一个数组的第一个元素。数组中后面的元素依次替换SQL语句中站位的?
的值。如果语句中有三个地方需要通过参数传入,那么我们需要创建一个长度是四的数组,第二个到第四个元素分别代表三个参数
@tasks = Task.find(:all, :conditions=> [ "name LIKE ?", "%#{params[:query]}%" ]
这是更好更安全的实现方法。
接下来如果在界面中输入带有单引号的查询条件,Rails会将单引号转义为普通的字符,而不是被认为SQL格式中的一部分。还差一点儿,咱们希望进行的是LIKE操作的模糊查询,需要在查询条件两边带上百分号。需要注意的是,百分号需要添加在参数数组的元素两边而不是在SQL语句的问号两边。日志中可以看到查询是正确的。
Processing TasksController#index (for 127.0.0.1 at 2009-02-01 21:59:31) [GET] Parameters: {"query"=>"Task 1’TEST"} Task Load (0.5ms) SELECT * FROM "tasks" WHERE (name like ’%Task 1’’TEST%’)
这样就完美解决了单引号的问题。
Note that as we’re using Sqlite the quote is escaped by using two single quotes. Other databases may use a backslash before the quote.
只有在使用find
方法中使用:conditions
参数进行查询是才需要考虑避免SQL注入。如果你使用的是更新的查询方法find_by
可以参考 episode 2 Rails会自动处理这些问题防止SQL注入发生。