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
な場合についてを考えます。 ↩