2010-02-26
Railsノート - link_to <string> は安全か?
redirect_to に引き続き、こっちも気になったので調べてみた。link_to は渡された String をサニタイズするか? それとも、それはこちらでやるべきなのか?
リファレンスには相変わらず書いてないので、ソースを見る。ググったらわかるようなことだと思うのだが、ソースを読んだ方が調べる過程が面白くていいのだ。勉強にもなるし。
結論から言うと、link_to にユーザーからの入力値を含む String を渡すときはこちら側の責任できっちりサニタイズする必要がある。(参照している Rails のバージョンは 2.3.5)
# actionpack/lib/action_view/helpers/url_helper.rb def link_to(*args, &block) if block_given? options = args.first || {} html_options = args.second concat(link_to(capture(&block), options, html_options).html_safe!) else name = args.first options = args.second || {} html_options = args.third url = url_for(options) if html_options html_options = html_options.stringify_keys href = html_options['href'] convert_options_to_javascript!(html_options, url) tag_options = tag_options(html_options) else tag_options = nil end href_attr = "href=\"#{url}\"" unless href "<a #{href_attr}#{tag_options}>#{name || url}</a>".html_safe! end end
生成したリンクに対し html_safe! というメソッドを呼んでいて、これが危険なタグを除去するメソッドのように見えるのだが……
# activesupport/lib/active_support/core_ext/string/output_safety.rb def html_safe? defined?(@_rails_html_safe) && @_rails_html_safe end def html_safe! @_rails_html_safe = true self end
実は全然違うどころかむしろ逆で、これは String に対し「これは安全な HTML です!」という印を付けるものだ。
こんな風に使われている↓
# actionpack/lib/action_view/safe_buffer.rb module ActionView #:nodoc: class SafeBuffer < String def <<(value) if value.html_safe? super(value) else super(ERB::Util.h(value)) end end # snip
つまり、html_safe! が呼ばれた String に関しては、h によるエスケープさえも行われずそのまま出力用のバッファに追記される。link_to では、内部で呼び出している url_for が安全な文字列を返すと完全に信頼し切っているわけだ。
では、肝心の url_for が渡された String をサニタイズしてくれるのかというと……
# actionpack/lib/action_controller/base.rb def url_for(options = {}) options ||= {} case options when String options when Hash @url.rewrite(rewrite_options(options)) else polymorphic_url(options) end end
ぷ。url_for は String を与えられるとそれをそのまま返すのであった。なので、
link_to parameter_from_user
みたいなことをしでかすと終わりで、見事な XSS脆弱性が出来上がることになる。
link_to <string> が内部で sanitize を実行しないのは、「除去すべきタグとそうでないタグを決めるのはアプリケーション側の責任」であり、フレームワーク側では(アプリケーションが期待する)適切な sanitize ができないからだと推測するが、この辺、フレームワークがよきにはからってくれるだろうなんて高をくくっていると痛い目をみる好例なので注意したい。それこそ、フレームワークの実装に信頼をおかず、
- このメソッドは引数を適切にエスケープしないかも知れない
- このメソッドは引数を適切にサニタイズしないかも知れない
と疑ってかかる態度が必要だと思う。いわゆる、「かも知れない」運転というやつ。まあ、当たり前のことなのかも知れないけど。
まとめ
- link_to にユーザーからの入力値を含む String 渡すときは sanitize すべし
