Ruby on Rails 动态渲染远程代码执行漏洞 (CVE-2016-0752)

原文: 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 时间轴


0x04 结论


Rails 的渲染机制是一个比较神秘的功能,如果没有深入挖掘实现细节很难弄明白。与 CVE-2014-0130 类似,使用动态渲染路径会导致目录遍历和代码执行。在评估多个流行的 Rails 开源项目的过程中,我已经看过许多由框架开发者所引入的类似漏洞。

如果你还没有阅读过 Jeff Jarmoc 的文章,我建议你去阅读以下。他深入了研究了 CVE-2014-0130 的漏洞细节和风险点。本文中的许多内容与他的文章都很相似,事实上,这两个是非常相似的漏洞。

我已经写了 Metasploit POC模块 ,用于检测和攻击 web 应用程序的远程打码执行漏洞。

已经有白帽子在三个白帽上搭建起了相应的漏洞环境,小伙伴们感兴趣的来测试下吧,有效期一个小时:

https://b4bfc94e13431d5d4.jie.sangebaimao.com