原文:Rails Dynamic Render to RCE (CVE-2016-0752)
0x00 概述
如果你的应用中使用了动态渲染路径 (dynamic render paths
) ,如渲染params[:id]
,通过本地文件包含(local file inclusion
),可能会导致远程代码执行。可以通过更新到Rails的最新版本,或重构你的controllers来修复漏洞。
文章主要介绍了在特定的场景下,Ruby on Rails
框架的一个缺陷导致攻击者能够远程执行代码。
在Rails controllers
的设计中,会根据被调用的方法,隐式的渲染对应的view
文件。例如,当调用controller
的show
方法时,如果代码中没有明确指定要渲染的view file
,框架就会隐式的渲染show.html.erb
文件。
然而在很多情况下,开发者会根据请求格式,如text, JSON, XML
来明确渲染的内容。此时,view file
是一个模板语言文件,如ERB, HAML
等。Rails
框架中有多个方法能够用于影响和改变view
内容。本文中重点关注渲染方法(render method
)。Rails
的文档中给出了多种调用渲染方法的方式,包括使用file:
选项来明确指定要渲染的view file
路径。
如果你阅读了该方法的文档,你会对功能的说明感到迷惑。让我们来看一段代码:
def show
render params[:template]
end
第一眼看,这段代码非常简单,该controller action
的主要功能为渲染template
参数指定 view 模板。但Rails如何查找指定的模板呢?在views
目录下查找? 在应用的 root 目录下查找?或是在其他位置?;该参数的值是一个模板的名称?是一个有特定后缀的文件名?或是一个完整的文件路径?这里有许许多多的疑问,我们需要查看实现的细节才能够得到答案。
0x01 细节说明
框架的渲染机制(render mechanism
)是一个在单一函数内试图完成太多功能的例子,这也是导致远程代码执行的原因所在。
我们假定渲染机制的预期行为是渲染在app/views/user/#{params[:template]}
文件,该假设看起来似乎是合理的。如果template
参数的值为dashboard
,那么就会去加载app/views/user/dashboard.{ext}
文件,其中.ext
是任意允许的后缀类型,如.html, .haml, .html.erb
等
假设用户给出的template
参数值为../admin/dashboard
。 程序执行的结果会是什么? 如下图所示,执行后,程序抛出了缺少模板错误(missing template error
)
通过分析报错信息,我们可以知道,框架试图在多个路径下查找要渲染的view file
,包括RAILS_ROOT/app/views
,RAILS_ROOT
和 文件系统的根目录。
从文件系统的根目录加载并渲染文件的行为是非常危险的。如果我们将/etc/passwd
作为template
参数的值传入,并且能够读取到passwd
文件的内容。那么将会是一个很严重的问题。
如果我们能够读取passwd
文件的内容,那么也可以读取应用程序的源代码和配置文件,如config/initializers/secrettoken.rb
文件。
导致该问题的原因是,在应用中使用了动态渲染路径(dynamic render paths
)
def show
render params[:template]
end
这个简单的代码的例子证明了攻击者能够读取我们的源代码和应用程序配置文件。不幸的是,这不是最坏的结果。
如Jeff Jarmo
在他的文章The Anatomy of a Rails Vulnerability – CVE-2014-0130: From Directory Traversal to Shell中描述的,我们能够利用这个漏洞获得一个shell。Jeff
的文章中描述了 在某些版本的Rails的 隐式渲染机制的一个相似漏洞,能够允许目录遍历(directory traversal
),更精确的说,本地文件包含(local file inclusion
)。本文中重点关注显示渲染(explicit rendering
),这是一个由框架开发者所引入的漏洞。
在进入细节分析前,这里将该漏洞的分类归为文件包含而不是目录遍历,原因是我们将文件作为代码(ERB
)进行加载、解释并执行。从传统意义上来说,目录遍历漏洞返回的是不可执行的(non-executable
)的内容,如CSV
文件。因此从本质来说,我们不仅仅能够读取应用程序的源代码和其他可读的系统文件,还能够执行Ruby
代码。既然我们能够执行Ruby
代码,我们也能够在web server
上执行系统级别(system-level
)的命令。
在从文件包含到得到shell
的过程中,用到了一种关键的技术,称为 日志文件污染 (log file tainting
)。Rails
在运行过程中,会将请求信息记录到日志文件中,包括请求参数,如development.log
。日志文件是纯文本格式,能够包含Ruby
代码。日志文件污染可以通过向web
应用程序发送一个恶意的请求,请求的参数中包含有效的Ruby
代码来实现。
在下面的例子中,我们向web
应用程序发起了一个合法的请求,但在URL
中带上了包含恶意的、经过URL
编码的参数 <%= `ls` %>。
通过查看日志文件,我们可以看到,日志中请求参数是以hash
键值对的方式保存,并进行了 URL解码。这是有效的Ruby
代码,能够在文件被渲染时执行。
因此,我们能够利用文件包含漏洞来尝试加载日志文件,执行包含的Ruby
代码。
当日志文件返回时,我们能够看到参数的hash
值。之前包含我们payload
的部分已经被ls
命令的执行结果所替换。到这里,我们已经能够以web-server
对应用户权限来执行系统命令了。
0x02 修复方法 & 缓解措施
安装Rails
特定版本的补丁
如果还没有安装patch
,可以考虑禁止渲染白名单之外的文件。具体方法为,在action
中定义允许的文件名白名单hash
,对用户的参数进行校验,确保参数在文件白名单hash
中。这种方法需要在应用程序中所有使用到动态渲染路径的地方都增加校验。
def show
template = params[:id]
valid_templates = {
"dashboard" => "dashboard",
"profile" => "profile",
"deals" => "deals"
}
if valid_templates.include?(template)
render " #{valid_templates[template]}"
else
# throw exception or 404
end
end
另一种类似的方法为校验给定的文件是否在特定的目录下存在。
def show
template = params[:id]
d = Dir["myfolder/*.erb"]
if d.include?("myfolder/#{template}.erb")
render "myfolder/#{template}"
else
# throw exception or 404
end
end
此外,我们可以使用Rails
静态分析工具Brakeman来扫描应用程序。Brakeman
检测报告会给出使用了动态渲染路径的controllers
,可以根据此来分析哪些controllers
会有远程代码执行的风险。
0x03 时间轴
- 2015年2月1日漏洞被报告给
Rails Team
- 2015年2月10日
Rails Team
同意修复漏洞 Patch
在2015年7月内部验证通过。-- 距离报告日期超过5个月Patch
在2016年1月25日发布,漏洞编号CVE-2016-0752,-- 距离验证通过超过6个月,距离报告日期将近一年
0x04 结论
Rails
的渲染机制是一个比较神秘的功能,如果没有深入挖掘实现细节很难弄明白。与CVE-2014-0130
类似,使用动态渲染路径会导致目录遍历和代码执行。在评估多个流行的Rails
开源项目的过程中,我已经看过许多由框架开发者所引入的类似漏洞。
如果你还没有阅读过Jeff Jarmoc
的文章,我建议你去阅读以下。他深入了研究了CVE-2014-0130
的漏洞细节和风险点。本文中的许多内容与他的文章都很相似,事实上,这两个是非常相似的漏洞。
我已经写了Metasploit
的POC模块,用于检测和攻击web
应用程序的远程打码执行漏洞。
已经有白帽子在三个白帽上搭建起了相应的漏洞环境,小伙伴们感兴趣的来测试下吧,有效期一个小时: