开源项目 Rails 这次漏洞有点严重啊

hooopo · 2013年01月10日 · 最后由 suxu 回复于 2013年01月18日 · 12593 次阅读
本帖已被管理员设置为精华贴

https://community.rapid7.com/community/metasploit/blog/2013/01/09/serialization-mischief-in-ruby-land-cve-2013-0156?x=1 https://groups.google.com/forum/#!topic/rubyonrails-security/61bkgvnSGTQ/discussion

连 2.3.x 都发布新版本了..

3.x 系列升级到最新版本就 OK

2.3 的升级不到最新版本可以在 config/initializers 里面加上:

ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING.delete('symbol') 
ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING.delete('yaml') 

PS. 通过这个漏洞发现两个好玩的工具:

Rails 浑身都是洞啊...

#1 楼 @bhuztez 发现的漏洞多说明用的人多..........

#2 楼 @hooopo 不是说Rails is secure by default ...

#2 楼 @hooopo Rails 4 之前找漏洞还挺容易的吧,光 SQL 注入就不少 ...

#3 楼 @bhuztez secure by default是原则。你不能因为一个人在路上摔了一跤,说他走的方向是错的。也不能说因为他走这个方向所以才会摔跤。。。两者没什么必然联系。

#5 楼 @hooopo 问题是框架自己没符合这个原则啊 ...

自己用代码 escape SQL 语句也算符合secure by default

#4 楼 @bhuztez talk is cheap, show me your PoC.

8 楼 已删除

#6 楼 @bhuztez I suck at explaining things. Wikipedia doesn’t. http://en.wikipedia.org/wiki/Secure_by_default

#7 楼 @hooopo design flaw 和 bug 的区别。碰到 design flaw,就绕着走啊,找 bug 是跟自己过不去,如果是一个比较大的问题,一找倒下一大片的,而且你也不确定你能不能找完的 ...

这下是代码注入了,比较严重。

Rails 把 PHP 能犯的错误都犯了一遍啊,虽然说到 Rails 4 已经不是这样了。

所以,赶紧都升 Rails 4 吧...

  • 默认模板不 escape
  • mass assignment 默认所有 field 都能改
  • SQL 用自己的代码拼接字符串

“Ruby on Rails, the PHP of a new generation.” — beefhooked

http://harmful.cat-v.org/software/ruby/

#9 楼 @hooopo secure by default -> 白名单 ...

默认模板不 escape —— Rails3 就改了啊 mass assignment 默认所有 field 都能改 ——Rails3 也改了 SQL 用自己的代码拼接字符串—— 啥?

#14 楼 @hooopo 就是做后一个是 Rails 4 才改的。Rails 4 之前,Rails 是自己拼字符串的,所以才各种 SQL 注入 ...

因为最开始的时候,DHH 那个 15 分钟开发 blog 要依赖这个该死的特性 ...

#15 楼 @bhuztez 你又没搞清楚事实啊!AREL 是 Rails3 引进的~

#16 楼 @hooopo model.find_by_xxx 这个是一开始就有的吧 ... 一开始那个 15 分钟 blog 依赖这里参数的 magic

前几次 SQL 注入全是为了和这东西兼容引起的。这次的 YAML 是另外一个坑...

#12 楼 @bhuztez

字符串拼 SQL... 他用的难道是 rails 部的东西而不是 rails? 这个世界上就只有国内某些还在用 ibatis 的大公司还这么做吧...

#18 楼 @luikore Rails 就是自己拼接字符串的啊,不然怎么被 SQL 注入啊 ...

Rails 3 框架是在自己拼接字符串,而不是交给数据库的客户端库或者数据库解决 ...

Rails 4 引入了革命性的改进,就是把这东西去掉了,之前存在就是为了那 15 分钟 blog 那个 demo 临时用用而已,完全没必要的东西 ...

#19 楼 @bhuztez 哦,你说的"自己"是框架而不是用户... 所谓 ORM 就是框架拼 SQL 而不是用户拼,我还没见过实现中不拼 SQL 的 ORM... 不过底层框架里没处理好某些情况的 escape 被抓住痛脚必须认栽...

#20 楼 @luikore 不是啊,完全不拼接是不现实的,但是用户输入部分 Rails 在自己拼。

我去找一下地址 ...

2660 2661 2694 2695 5664

#19 楼 @bhuztez 你说的数据库解决指的是Prepared Statements? Rails3.1 就有了啊 http://patshaughnessy.net/2011/10/22/show-some-love-for-prepared-statements-in-rails-3-1

不耻上问 囫囵吞枣没看明白的说 是 xml 解析器的问题么?

#21 楼 @bhuztez 关于上一次出的问题,框架翻译过程大概是这样的: find_by_id(x) => where({id: x}).first => 最后得到 prepared statement "select * from foo where id = ?" 和 x, 没处理好的是那个 x, 因为后来给第二步的 where 方法加了可以处理嵌套参数的新功能,x 会影响到那个 prepared statement 而不是像最初那样简单的传给数据库参数了。在 prepared statement 之前的翻译都必须框架自己做啊...

#24 楼 @luikore 对,我说的就是这个,这地方拼接了一下字符串里面是含有用户输入的 ... 这个功能没必要存在啊,等到 Rails 4 终于去掉了

肯定有很多人被那个 15 分钟 blog demo 坑了 ... 虽然 average Rails 程序员不会犯这个错误,但是新手咋办 ...

#25 楼 @bhuztez 其实去掉这类方法是因为新版写起来更简单统一... find_by_xx(xx) 里全部都 xx.to_s 一刀切也毛问题都没有了...

#26 楼 @luikore 其实早就可以去掉的来着,好像新版对应的功能 Rails 3 出来之前就有了啊 ...

不过,因为Rails is Omakase,所以不能这么急,必须等到 DHH 觉得时机成熟了也就是 Rails 4 才能加进去呢 ...

#27 楼 @bhuztez 一会儿Rails is Omakase一会儿15分钟blog demo。。。你要看清楚楼主不是 DHH 啊..........!

15 分钟 demo 已经很久不提了,最新是 Rails is Omakase,早前是 Rails is not for beginers.

#23 楼 @shooter 应该是 post 请求的 content-type (注意不是 accept) 为 xml 而且请求体的 xml 里面又嵌入了 yml 的话,yml 中就可以执行 SomeClass.new 或者 Class.new{include SomeModule}.new

要修改请求的 content-type 不能在浏览器里上做,大概要这样:

curl -H'content-type: application/xml' --data '<?xml version="1.0" encoding="UTF-8"?>
<b type="yaml"><![CDATA[--- !ruby/object:ActiveRecord::Base {}]]></b>' some.site/post-url

当然还要加上 cookie 或者 csrf 等参数,挺麻烦的...

有能人士赶紧 0day 哦,不过不了解 ruby 的话仅靠一个 new 啥事都做不了哈。

#25 楼 @bhuztez ... 我想起以前一个抽查比较,发现 wiki 有 12 处错但大英百科全书只有 6 处错,但是比较完了 wiki 上出的错都修好了...

#30 楼 @luikore http://blog.codeclimate.com/blog/2013/01/10/rails-remote-code-execution-vulnerability-explained/ 这里的办法可以直接 get 执行文件里的 ruby 代码,不需要 post... 刚才在本机测试代码运行成功。(rails3.2.10),2.3 的没成功,可能是中间件不一样。


这篇 blog 里的内容被改了。。执行代码被去掉了。还是好人多哇!!

好像 Rails 4 默认不是关掉 csrf 了吗 由于缓存的原因

https://twitter.com/homakov/status/289317965713776640

这个有意思:-)

ruby ./rce.rb http://rails.app/ '`gem update rails;touch tmp/restart.txt`'
# 所以吧,不要锁定版本号...要不雷锋想帮你也帮不到了

这次的漏洞,其实只要代码中没有把解析出来的内容做 eval 之类操作的话,还是不会被代码注入的。

<lol type="yaml">:omakase</lol> via: Twitter

#35 楼 @kgen 胡说 只要你代码里有 XXX.find_by_xxx(params[xxx]) 就别想避免被注入了 另外 只要你用 1.9 就死定了。。。

#37 楼 @iBachue find_by_xxx 只会导致 SQL 注入吧?

#38 楼 @kgen 是的 还不够嘛?

#39 楼 @iBachue 我的意思是,没有代码注入,危害小很多。SQL 注入毕竟是应用级的,代码注入很容易变成系统级的漏洞,整个服务器的安全性就有威胁了。

#40 楼 @kgen 哦 那在我看来都差不多的 都会引发重大损失的

#41 楼 @iBachue 如果一台服务器一个 App,那么损失基本差不多。 如果一台服务器,几个 App,代码注入的损失极大。因为只要一个 App 没有修复,理论上所有 App 都有风险。

#42 楼 @kgen 饿 这个是的 如果没用虚拟机的话

@iBachue

find_by_xxx 就是上次的问题,这里 不能注入, 你试试 User.find_by_id "1'; drop table users;" 就知道了。只是 modle 里写了 scope 的话是可能可以构造出某些条件来篡改数据 (很多人都不知道 scope 是啥呢...). 不是很重要因为这个漏洞很难利用...

这两个问题和 1.9 关系在哪里...

@kgen

这次是可以通过构造一个 xml 请求体进行攻击的问题,比较严重,和 eval 无关,所有请求都有影响,赶紧升级或者加上那两行把选项关掉吧。

#44 楼 @luikore 这次的问题用User.find_by_id "1"; drop table users;去注入是无效的 但是用User.find_by_id "1 or 1 = 1"就是有效的了 和 Ruby 1.9 的关系在于 Ruby 1.9 的 GC 不回收 Symbol 类型 可以利用这次的漏洞制造海量 Symbol 把 Ruby 的内存全部吃光。

#44 楼 @luikore 这次构造 XML 请求体进行攻击的,只有后续代码把解析出来的内容 eval 的情况下,才能实现代码注入。否则只有 SQL 注入。 我说代码注入的意思是,类似操作系统缓冲区溢出的漏洞,可以运行溢出部分以数据方式填入的代码,在这次的漏洞中,不用 eval 是做不到执行 XML 反序列化成对象后的代码的。

各位,别争论了,咱们还是好好想想,如何避免发生类似的问题吧。

#45 楼 @iBachue 请问,现在立刻升级到 Rails 3.2.11 就安全了吗?Ruby 的版本是否需要升级?

#48 楼 @ery 是的。升级后,Ruby 版本无关。

#46 楼 @kgen 你有哪些网站,我可以给你演示下... 问题就是嵌入 xml 的 yaml 可以执行某些代码,不用 eval 也可以的,是 yaml 的特性口牙!

#45 楼 @iBachue 1.8 也不回收 Symbol. Symbol 的问题以前就修过了,解析参数默认是不产生 Symbol 的。Symbol 内存溢出和次的 find_by_id 没有关系...

造成内存溢出的攻击方法是造 xml 请求,xml 中嵌入 yaml, yaml 中写 Symbol:

<?xml version="1.0" encoding="UTF-8"?>
<b type="yaml">
<![CDATA[---
:a:
:ab:
:ac:
:ad:
:ae:
...
]]></b>

#45 楼 @iBachue User.find_by_id "1 or 1 = 1" 这样生成的 sql 没意义吧?查询结果仅仅和第一个 1 有关系。

#46 楼 @kgen eval 不一定是项目调用的 也有可能是 Rails 自己干的 你应该知道那个攻击方法吧 我就是因为这个才说“凡是用 Rails 的都死定了”,任何 Rails App 都不能避免

#52 楼 @vkill 那如果是User.find_username_and_password('username', 'password or id = xxx')呢?那是不是我可以登陆任何一个帐户了?

#51 楼 @luikore 你有什么测试代码证明 1.8 不回收 Symbol 呢?我刚才写了https://gist.github.com/4502552 可以证明 1.9 确实是不回收的,而 1.8 可以

#55 楼 @iBachue

GC.stress
i = 0
lim = 10**12
while i < lim
  i.to_s.to_sym
  i += 1
end

rvm use system 用 1.8.7 执行,内存一直往上长,但是如果把 to_sym 改成 dup, 内存就稳定在 1.9 M.

附带 1.8 的详细版本

ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0]

#56 楼 @luikore 好吧 确实如此 那就改称 凡是用 Ruby 的都死定了吧

#54 楼 @iBachue 你试过了?这个生成的 SQL 是

select * from users where users.username = 'username' and users.password = 'password or id = xxx'

还是没注入

#58 楼 @luikore 当然不是我写的这么简单拉 有后门的哦 简单的方法当然不能越过 Rails 的各种 filter 拉

#60 楼 @hooopo 这篇文章误导人的 当初就是把我误导了 以为问题不大 结果其实是超级大漏洞了

#61 楼 @iBachue 你说说什么漏洞?

#57 楼 @iBachue 我想说 find_by_xx 是安全漏洞不是简单的注入漏洞,好费劲...

改成 "没时间仔细看贴,又不升级,就死定了" 才对

#62 楼 @hooopo 对于传统的字符串 find_by_xxx 能过滤的很好 但如果是 Arel::Nodes::SqlLiteral 的实例,由于是内部类,Rails 会完全信任它而不加过滤 这个时候就可以注入了

我没看到一个能注入的例子。

今天上班第一个 ticket 就是升级 Rails

#65 楼 @zgm 要自己发现的嘛

#65 楼 @zgm 的确是可以注入,我都尝试了

#67 楼 @iBachue #68 楼 @mouse_lin
我测试了这个例子

Post.find_by_title("whatever", :select => "title FROM posts; DROP TABLE posts; --")

生成的 sql 确实是危险的

SELECT  title FROM posts; DROP TABLE posts; -- FROM `posts`  WHERE `posts`.`title` = 'whatever' LIMIT 1

但是 active_record 似乎不允许这样的 sql 被执行,所以上面的 ruby 代码执行后的结果是

ActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DROP TABLE user; -- FROM `posts`  WHERE `posts`.`title` = 'whatever' LIMIT 1' at line 1: SELECT  title FROM posts; DROP TABLE user; -- FROM `posts`  WHERE `posts`.`title` = 'whatever' LIMIT 

#69 楼 @zgm 你们为什么这么喜欢 Drop table 或是 delete 什么东西的语句?真正的威胁来自于在你设好的条件后门再帮你加一个 or 条件。。 另外 这次漏洞最恐怖的地方在于代码注入 而且执行点在 Rails 代码里,也就是说无论你的 App 怎么写都不能避免了。。

71 楼 已删除

@zgm 这个篡改的 sql 不能被执行是因为 mysql 的限制。。。每次请求只能执行一条查询

这个漏洞太恐怖了,只要是没升级或者没打补丁的 rails 项目绝对秒杀,可执行任意 ruby 代码 (包括 system),昨晚做完实验后吓出一身冷汗。

#73 楼 @rainchen 我相信每个做完实验的人的第一反应就是停下手里的工作去升级 Rails,哈哈。

3.2.5 => 3.2.11

需要 登录 后方可回复, 如果你还没有账号请 注册新账号