Rails 4では、普通に書けば意識しなくていい程度にXSS対策のエスケープが施されています。ただし、複雑なことをやろうとすれば引っかかる場面も出てきます。
TL;DR
- 全般
- できる限り、HTMLのコード生成にはヘルパーを使う
-
SafeBufferを左辺に置く -
SafeBufferを文字列中に展開しない - モデル・ヘルパー内で
- HTML出力するものは
html_safeしておく - ビュー内で
-
rawや<%==は原則使わない -
html_safeは純粋リテラルだけに使う
2つの文字列型
Railsでは、ビューの出力側で特に工夫しなくても、HTMLを出力すべきものとエスケープしてから出力されるものが区別されています。
<!-- こっちはエスケープ処理が入る -->
<%= 'a < b & c > d' %>
<!-- こっちはそのまま出力 -->
<%= link_to 'Qiita', 'http://qiita.com/' %>
こんな機能を実現するために、Railsでは通常のString型以外に、Stringを継承したActiveSupport::SafeBufferという型があって、後者ではHTMLエスケープなしで出力していいかを記録する機能が付いています1。ERBの出力では通常の文字列ならエスケープをかけて、SafeBufferはそのまま通す、ということをしています。
SafeBufferの生成
もちろんnewで作れなくもないのですが、通常は以下の2つのどちらかの方法で生成します。
-
'文字列'.html_safeというようにhtml_safeメソッドを呼び出す -
html_escape('文字列')、あるいは短縮形のh('文字列')でエスケープする
前者については、html_safeだとマークするだけですので、中身が何であろうとそのまま出力することになります。タグを生成するにはこちらが必要ですが、下手にユーザー入力などの素性がわからない文字列に適用すればXSSを作りこんでしまいます。変数代入のない純粋なリテラルに適用するのならともかく、それ以外のものにビューでhtml_safeを乱発するのは避けたいものです。
なお、link_toやcontent_tagといったHTML用のヘルパーがありますが、これらはSafeBufferを生成しています。基本的には、HTMLタグはこういったヘルパーで生成しましょう。
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
content = ERB::Util.unwrapped_html_escape(content) if escape
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
end
SafeBufferの演算
文字列は連結したりもよくしますが、SafeBufferを連結するとどうなるのでしょうか。
-
SafeBuffer + SafeBuffer…そのまま連結される(SafeBuffer) -
SafeBuffer + String…String側をエスケープした上で連結(SafeBuffer) -
String + SafeBuffer…単なるStringとして両者を連結
最後のパターンでは、一度付いたhtml_safeが外れる結果、出力時にエスケープされて、HTMLタグがそのまま表示される結果となります。この形は避けるようにしましょう(SafeBufferを二重引用符の中で変数展開してしまっても同様なことになります)。ヘルパーなどでは、以下のような書き方も見られます。
def hoge
str = ''.html_safe # バッファとして宣言
str << foo
str << bar(42)
str
end
最後に
どうしてもうまく整わなければ、最後にビューで raw出力してしまう手段もなくはないのですが、まさにそれは最後の手段です。きちっとHTMLと文字列を意識してデータを生成すれば、ビューでの制御の必要はほぼなくなるでしょう。
外部リンク
-
破壊的変更を加えた場合など、
SafeBufferでもhtml_safeでなくなることもあります。ただし、以下では特に断らない限りhtml_safeな場合についてを考えます。 ↩