Rails: テキストフィールドのリンクを無効にしてスパムを防ごう(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: テキストフィールドのリンクを無効にしてスパムを防ごう(翻訳)

多くのアプリケーションでは、招待メールや通知メール、パスワード忘れのリマインダーメールの形でメールを送信します(必ずしもメインの機能とは限りません)。

ユーザーが生成したコンテンツを送信メールで許可してしまうと、たちまちアプリケーションはメールスパム業者の格好の標的になってしまいます。スパム業者はろくでなしの人間のこともあれば彼らの操るボット軍団のこともあり、あなたが正式なユーザーに許可してしまった自由文入力機能を悪用します。

AppleメールやGmailのようなメールクライアントは、メールのWebアドレス的な文字列を自動的にハイライト表示します。つまりハッカーたちはテキストフィールドにそれらしいWebアドレスを入力するだけで、HTMLの注入すら行うことなく、ユーザーをろくでもないWebサイトに導くことができてしまいます。

決して次のように書かないこと

アプリでスパム業者たちがメールを送信できるように書いてしまう。

<%= form_for :invitee do |f| %>
  Email: <%= f.text_field :email %>
  Message : <%= f.text_field :message %>
  <%= f.submit "Send Email" %>
<% end %>

常に次のように書くこと

バリデーションを用いて、テキストフィールドへのWebアドレスの挿入を阻止する。

<%= form_for :invitee do |f| %>
  Email: <%= f.text_field :email %>
  Message : <%= f.text_field :message %>
  <%= f.submit "Send Email" %>
<% end %>
  • validators/no_urls_validator.rb
class NoUrlsValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?

    if value.match?(%r{(http|\w+\.\w+\/?)})
      record.errors[attribute] << (options[:message] || "Webアドレスが含まれているようです")
    end
  end
end
  • app/models/invitee.rb
class Invitee
  # ...
  validates :message, no_urls: true
  # ...
end

そうすべき理由

これは気づかれにくい課題ですが、スパム業者たちに知られていて悪用される攻撃方法であることは間違いありません。ユーザーが入力した自由文を含むメール送信をアプリケーションで許可したら最後、テキストがどれほど短くても確実に標的にされてしまいます。

最も顕著なのは、支払いを受け取る前にメール送信をアプリケーションで許可している場合です。もしメール送信前に正しいクレジットカードの入力を義務付けていなければ、スパム業者が「無料お試し」にサインアップするスクリプトを自動で実行し、アプリからリンクを送信する可能性があります。

アプリから大量のスパムメールが送信されると、アプリのメール配信全体はもちろん、ビジネスドメインにまで悪影響が生じる可能性があります。そんな事態を望む人はいません。

ユーザーが生成したHTMLやJavaScriptからブラウザを保護する機能はRailsの初期から組み込まれていますが、Webサイトらしく見える非HTMLテキストを自動的にリンク化するメールクライアントまでRailsアプリケーションが面倒を見ることはできません。

ユーザーが入力したテキストをバリデーションすることで、このスパム手法の標的になる可能性を減らせます。

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

私がバリデーターで使っていた正規表現は出来が今ひとつで、たまにfalse positive(偽陽性)になることがありました。httpという文字列を含んでいたり、like.thisのようにドットでつながった文字があるだけで却下していたのです。

そうした正規表現は、マッチするパターンを狭めるよう変更しましょう。

あるいは、バリデーションで防いだりユーザーにエラーメッセージを表示する代わりに、コールバックでテキストを修正するという手もあるといえばあります(あらゆるドット.の後ろにスペースを追加するなど)。

"like.this".tr(".", ". ")
#=> "like. this"

他にも、もし可能なら、ユーザーがお金を払うまではテキスト入力などの機能を制限する方法を検討する手もあります。正しいクレジットカードの入力を義務付けることで、スパム業者を効果的に防げることがよくあります。

関連記事

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

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ

BPSアドベントカレンダー