Railsでユーザーが投稿したテキストにURLがあればリンクとして表示した時のXSS対策について
質問内容
最後に改めて記載しますが、最初に質問を記載します。
以下の2点について、ご教授いただきたいです。
- そもそも実装方法が適切か
- Gemを使うのがスタンダード、コードがおかしい、など
- XSS脆弱性に対処できているか
やりたいこと
現在、Railsで掲示板を作成しています。
Twitterなど多くのサイトで使われているような、ユーザーが投稿するテキストにURLがある場合はtarget="_blank"を付けてリンク化して表示する機能を実装したいです。
また、テキスト内の改行を反映させたいため、Railsが用意しているビューヘルパーのsimple_formatを使用します。
実装に際して検索をかけてみたところ、以下の2つのGemが出てきたのですが、どちらのGemもネットに存在する情報が少ないことから、Gemを使った実装方法がスタンダードなのか分からないため、Gemを使わずに実装する形にしました。
- tenderlove/rails_autolink
https://github.com/tenderlove/rails_autolink
- vmg/rinku
参考にしたURL
以下が実装時に参考にしたURLです。
- テキスト内のURLをリンク化して表示するため
[Ruby][Rails]テキスト内のURLをaタグに書き換える - Qiita
- simple_formatをそのまま使用するとtarget="_blank"が消えるのに対処するため
ruby on rails - simple_format is stripping out target _blank - Stack Overflow
環境
Rails 5.0.0.1
Ruby 2.3.1
コード
以下で、ユーザーが投稿したテキストにURLが含まれていれば、その部分をリンクにするtext_url_to_linkメソッドを定義しています。
app/helpers/application_helper.rb
module ApplicationHelper
require "uri"
def text_url_to_link(text)
URI.extract(text, ['http', 'https']).uniq.each do |url|
sub_text = ""
sub_text << "<a href=" << url << " target=\"_blank\">" << url << "</a>"
text.gsub!(url, sub_text)
end
return text
end
end
以下で、ユーザーが投稿したテキストを表示しています。
前提としまして、Discussionsテーブルにtext型のcontentカラムがある状況です。
質問したいコードをdivで囲ってidを設定しているのはAjaxでDiscussionのcontentを編集するためです。
app/views/discussions/index.html.erb
<% @discussions.each do |discussion| %>
<div id="discussion-content-<%= discussion.id %>" class="discussion-content">
<!-- 以下が質問をしたいコード -->
<%= simple_format(text_url_to_link(h(discussion.content)), {}, sanitize: false) %>
</div>
<% end %>
上記のviewファイル内の質問したいコードの流れは以下のようになっていると認識しています。
- discussion.contentでユーザーが投稿したテキストを取得
- hメソッドでタグをエスケープ
- 自分で定義したtext_url_to_linkメソッドによりURL形式の文字列をaタグで囲む
- simple_formatメソッドをそのまま使用すると、target="_blank"が効かなくなるので、オプションでsanitize: falseを指定
- 最終的にテキスト内のURL形式の文字列がリンクで表示される
上記のコードを書いた状況で
<script>alert('hoge')</script>
と自分が作っている掲示板に投稿したところ、エスケープされて表示されました。
質問内容
改めて、質問内容を記載致します。以下の2点になります。
- そもそも実装方法が適切か
- Gemを使うのがスタンダード、コードがおかしい、など
- XSS脆弱性に対処できているか
初心者のため、内容に至らない点もあるかもしれませんが、ご教授いただけますと幸いです。よろしくお願いいたします。