订阅软件研发RSS CSDN首页> 软件研发

Rails所有版本都有SQL注入漏洞?其实没那么严重

发表于2013-01-06 11:22| 次阅读| 来源CSDN| 0 条评论| 作者王然

摘要:近日,Ruby on Rails被爆存在严重的SQL注入漏洞,涉及到其所有版本,这在国内外论坛上都引起了强烈关注。可惜的是,这份公告并没有详细解释这一危险性的出现原因,造成了很多不必要的困惑和恐慌,对于不熟悉Ruby on Rails的人来说尤其如此。

Rails安全团队的Michael Koziarski是这样说的:“When we told people they should upgrade immediately we meant it. It *is* exploitable under some circumstances, so people should be upgrading immediately to avoid the risk.”

下面是他们对该问题的安全性评估:

总结:这个漏洞是什么?


  • 该bug允许通过动态查询方法(比如:find_by_foo(params[:foo]))进行SQL注入。我将在之后介绍动态查询。
  • 该bug影响到了Ruby on Rails所有版本。
  • 已知应用场景必须满足以下所有条件:1.你使用了Authlogic (一个流行的第三方身份认证库);2.你必须知道session secret令牌(You must know the session secret token)。
  • 还有其它应用场景,但取决于应用正在做什么。因为并不能用来说明某个具体应用是不是安全的,所以你应该认真对待这个漏洞,即使你认为你的应用并不会受到影响,最好也升级一下Rails。

这个漏洞不是什么?


了解Rails的人需要知道:

  • 这个bug不会影响到正常的查询方法(比如:find(params[:id]))。
  • 这个bug并不能利用请求参数。
  • bug不在Authlogic上而在于Rails,只是Authlogic恰好触发了该bug。
  • Devise(另一个第三方身份认证库)并不会触发该bug。
  • 正如 Rails Security Digest. ‘params’ Case中描述的,Point 6是个完全不同、不相关的问题。这个问题很严重,应当引起认真对待,所以请注意任何相关新信息。

不了解Rails的人需要知道:

  • 这并不意味着未升级的Rails应用都会受到影响。
  • 这并不意味着Rails没有逃离SQL输入(doesn’t escape SQL inputs)。
  • 这并不意味着Rails没有提供参数化SQL API。
  • 这并不意味着Rails鼓励天生容易SQL注入的代码。代码应该安全,但瑕疵不可避免。现在这个问题已经被修复了。

主要利用场景


Rails为所有ActiveRecord(database)模型提供了查询器方法。比如:使用提供的主键“id”来请求user参数,一般这么写:

  1. User.find(params[:id]) 

Rails同样提供“动态查询方法”,一般写作“find_by_*”,可用于查询模型中所有数据库column。如果你的“user”表中有“id”、“name”和“phone”,它将会自动生成这样的方法:

  1. User.find_by_id(params[:id]) 
  2. User.find_by_name(params[:name]) 
  3. User.find_by_phone(params[:name]) 

问题正出在这些动态查询方法上,而普通的查询方法(如常用的find方法)没有问题。

ActiveRecord能够通过转义输入,从而保护你免受SQL注入威胁,比如:

  1. User.find_by_name("kotori'; DROP TABLE USERS; --"
  2. # => SELECT * FROM users WHERE name = 'kotori\'; DROP TABLE USERS; --' LIMIT 1 

但是ActiveRecord同样定义了注入SQL碎片的方法,因此程序员可以方便地调整请求。这个注入接口都是documented,程序员不应该将user输入传入这些接口中。一般来说,传入注入接口的字符串都应该是常量,不可以修改。其中一个注入接口是“find_by_*”方法的选项参数(通常是第二参数):

  1. # Fetches a user record by name, but only fetch the 'id' and 'name' fields. 
  2. User.find_by_name("kotori":select => "id, name"
  3. # => SELECT id, name FROM users WHERE name = 'kotori' LIMIT 1 
  4. # You can inject arbitrary SQL if you wish: 
  5. User.find_by_name("kotori":select => "id, name FROM users; DROP TABLE users; --"
  6. # => SELECT id, name FROM users; DROP TABLE users; -- FROM users WHERE name = 'kotori' LIMIT 1 

该漏洞在于,“find_by_*”同样接受只有选项参数的调用,这种情况下,它会默认值参数(valve parameter)是nil。

  1. User.find_by_name(:select => "1; DROP TABLE users; --"
  2. # => SELECT 1; DROP TABLE users; -- FROM users WHERE name IS NULL LIMIT 1; 

会使用到第二参数的人不是很多,但下面的代码很常见:

  1. User.find_by_name(params[:name]) 

params[:name]通常是字符串,攻击者能够确定params[:name]是option hash吗?当然!Rails会将请求参数的一种特定形式转化为hash。假设你调用控制器的方法如下:

  1. /example-url?name[select]=whatever&name[limit]=23 
  2. params[:name]现在是一个hash:{ "select" => "whatever""limit" => 23 } 

但实际上,它是一个不可利用的漏洞。Ruby有两个数据类型,字符串和符号,符号类似于字符串常量,比如,:select就是一个符号。这个漏洞只有在键是符号的时候才会被触发,但Rails生成的请求参数hash都有字符串键,这得感谢HashWithIndifferentAccess的工作方式。

如果应用程序以某种方式向“find_by_*”传入任何hash,同时还要符号密钥,这时候攻击者就可以进行破坏了。现在开始难题的第二部分:Authlogic。其描述在这里,工作方式如下。

Authlogic会通过多种方式接受认证:cookie、Rails会话数据、HTTP基本认真,等等。所有的用户帐户都有有所谓的持久性令牌,用户必须提供这种持久性令牌才能通过其中身份验证。Authlogic使用以下调用来查找用户相关联的持久化令牌:

  1. User.find_by_persistence_token(the_token) 

攻击者能确定the_token是option hash吗?当然!但只有通过Rails会话数据认证方法。在其它方法中,the_token都是字符串。

Rails会话机制允许存储任意Ruby对象,包括符号密钥和hash。Rails提供了多种会话存储机制,但默认是在客户端使用cookie存储会话数据。cookie数据没有加密,但使用HMAC防止被篡改。cookie存储很快,而且不需要服务器端维护,但是只可以用于非敏感信息,比如信用卡号码就是不允许的。会话中存储敏感信息的应用应该使用数据库会话存储代替。然而,事实上,95%的所有Rails应用在会话中只存储用户身份验证凭据,所以默认使用cookie存储。

所以,如果想注入任意的SQL,你需要篡改cookie,这又需要HMAC密钥(又被称为session secret,正如其名,它应该是秘密)。Rails会在项目创建时生成一个随机的512位secret,因此大多数使用Authlogic的Rails应用都没有问题:因为攻击者无法得知secret。开源Rails应用会导致一个问题,它们中很多都会使用默认的session secret,而使用者并不会去定制它,这就导致它们都使用了相同的HMAC secret,因此非常容易被利用。当然,这种情况下维护者应该担心的不仅仅是SQL注入了。如果HMAC被知道了,那么人人都可以向应用发送虚假的身份认证。

其它可利用场景


如果你调用Foo.find_by_whatever(bar)也会出现问题,因为bar是用户使用符号密钥定义的hash。HashWithIndifferentAccess把密钥当作字符串存储,而非符号,因此不会触发这个漏洞。

解决办法


以下几种方法能减轻该问题:

  • 升级到最新版本的Rails。这就没问题了,你不需要做任何事情。“find_by_*”问题已经被修补,因此第一个参数不会是option hash。
  • 确保你的代码只会向“find_by_*”传入字符串或者整数,比如:find_by_name(params[:name].to_s)。这需要修改所有的代码,包括第三方代码,因此我并不推荐。
  • 确保你的session secret是安全的!如果你使用了开源Rails应用,那么一点要修改其默认session secret。

Demo


我们提供了一个demo来展示,这个bug无法利用请求参数。安装完,运行,再这样试图攻击:

  1. curl 'http://127.0.0.1:3000/?id\[limit\]=1' 

你会发现使用SQL注入肯定是行不通的。

原文链接:PHUSION

0
0
  • CSDN官方微信
  • 扫描二维码,向CSDN吐槽
  • 微信号:CSDNnews
程序员移动端订阅下载

微博关注

相关热门文章