原文: Rails Dynamic Render to RCE (CVE-2016-0752)
如果你的应用中使用了动态渲染路径 (
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 目录下查找?或是在其他位置?;该参数的值是一个模板的名称?是一个有特定后缀的文件名?或是一个完整的文件路径?这里有许许多多的疑问,我们需要查看实现的细节才能够得到答案。
框架的渲染机制(
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
对应用户权限来执行系统命令了。
安装
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
会有远程代码执行的风险。
Rails Team
Rails Team
同意修复漏洞
Patch
在2015年7月内部验证通过。-- 距离报告日期超过5个月
Patch
在2016年1月25日发布,漏洞编号
CVE-2016-0752
,-- 距离验证通过超过6个月,距离报告日期将近一年
Rails
的渲染机制是一个比较神秘的功能,如果没有深入挖掘实现细节很难弄明白。与
CVE-2014-0130
类似,使用动态渲染路径会导致目录遍历和代码执行。在评估多个流行的
Rails
开源项目的过程中,我已经看过许多由框架开发者所引入的类似漏洞。
如果你还没有阅读过
Jeff Jarmoc
的文章,我建议你去阅读以下。他深入了研究了
CVE-2014-0130
的漏洞细节和风险点。本文中的许多内容与他的文章都很相似,事实上,这两个是非常相似的漏洞。
我已经写了
Metasploit
的
POC模块
,用于检测和攻击
web
应用程序的远程打码执行漏洞。
已经有白帽子在三个白帽上搭建起了相应的漏洞环境,小伙伴们感兴趣的来测试下吧,有效期一个小时:
https://b4bfc94e13431d5d4.jie.sangebaimao.com