Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: ERBファイルで<%==を使うときはセキュリティに注意(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

参考: §7.3 クロスサイトスクリプティング(XSS) -- Rails セキュリティガイド - Railsガイド

Rails: ERBファイルで<%==を使うときはセキュリティに注意(翻訳)

クロスサイトスクリプティング(XSS)は、他のユーザーが閲覧するWebページに対して悪意のあるスクリプトを注入可能になる一般的な脆弱性です。こうしたスクリプトは、個人情報の抜き取りやページコンテンツの操作といった悪意のある操作を攻撃者に代わって実行するのに使われる可能性があります。

Railsのようなフレームワークを使うメリットのひとつは、クロスサイトスクリプティングの保護が組み込まれていて、ユーザーが入力したコンテンツに自動的に適用されることです。Railsでは、コンテキストに応じた出力エンコーディングと、自動エスケープ機能を組み合わせる形で、クロスサイトスクリプティングの脆弱性を緩和しています。

Railsは、HTMLテンプレートからのユーザー入力を出力するときに、デフォルトでHTMLエンティティを自動的にエスケープしてから、ActiveSupport::SafeBufferという特殊なクラスを利用して文字列を結合し、ユーザーによって生成されたコンテンツを最終的にレンダリングします。このSafeBufferは、文字列を誤って二重エスケープしたりエンコードメカニズムをバイパスしたりすることを防ぐように設計されています。

しかし、一部の人がやっているように、この仕組みは回避できてしまいます。

🔗 以下のように書くのではなく

<%== %>を使う。

<%== @some_text_to_render %>

🔗 以下のように書くこと

Railsのsanitizeメソッドで確実にコンテンツを安全な状態にしてからビューに渡す。

# コントローラのコード
@some_text_to_render = "some text #{params[:user_text]}".sanitize
<%= @some_text_to_render %>

編集部注

上のサンプルコードのうち、コントローラのコードは実際には動きません。また、ビューで<%= %>を使うだけでエスケープされます。

参考: § 5.1.2 安全な文字列 -- Active Support コア拡張機能 - Railsガイド

サニタイズとエスケープは厳密には同じではありません。「入力のサニタイズ」と「ビューの出力のエスケープ」を両方行うのが基本と考えるとよいと思います。

🔗 そうする理由

主な目的は、悪意のあるユーザー入力からアプリケーションを保護するためです。Railsのビューテンプレートで<%== something %> ERBタグを使うと、<%= raw(something) %>と書いたのと同じ動作になります。こんな書き方をすると、SafeBufferによる保護を完全にすり抜けてしまいます。

ERBで<%==<% rawが使われているのを見かけたら、まずいことが起きている兆候です。HTMLエスケープをバイパスする決定を下す前に、必ず「使われているコンテキスト」「データの出元」「潜在的なセキュリティリスク」を慎重に評価してからにしましょう。一般に、HTMLエスケープをバイパスすべきではありません。

ActiveSupport::SafeBufferを見ると、html_safe?などのメソッドがあります。これらは、Railsがブラウザで表示するユーザー入力の安全性を管理するために使われるメソッドです。

html_safeメソッドのAPIドキュメントにも書かれているように、「ユーザー入力に対してこのメソッドを呼び出すのは絶対禁物」なのです。Railsが得意なことはRailsに任せましょう。

🔗 そうしない理由があるとすれば

エスケープされていないHTMLが使われている場所をときたま見かけます。

ddnexus/pagy - GitHub

pagy gemでは、<%== pagy_nav(@pagy) if @pagy.pages > 1 %>というエスケープなしの構文が推奨されています。pagy_navは(ユーザー入力ではなく)このgemが出力するものなので、クロスサイトスクリプティングの危険性はまずありません。

エスケープなしのHTMLを容認できそうなケースは、他に2つ考えられます。

ひとつのケースは、パフォーマンス上の理由です。各文字列のサニタイズを管理するときのオーバーヘッドは小さいのですが、ものすごく長いまたは大量の文字列を扱う場合は、エスケープを回避することでパフォーマンスが向上する可能性もあります

もうひとつのケースは、複雑な文字列のエスケープ処理が複雑になってしまう場合です。しかしこれはまさに「危険な可能性のある入力を自分で正しく管理すべき」場合であり、さもなければフレームワークに任せるべきです。

関連記事

Railsでセッションリプレイ攻撃を防ぐ方法(翻訳)

Railsアプリで実際にあった5つのセキュリティ問題と修正方法(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。