Railsガイドにきちんと目を通して新しい知識を得る - Rails セキュリティガイド編 -

ドキュメントを読み込むのは大事、ということでRailsガイドを頭から読んでいく取り組みをしています。 各章ごとに、(Railsガイドにちゃんと書いてあるのに)知らなかった機能を雑にまとめていきます。

今回は、Rails セキュリティガイドの章です。今回はセキュリティの話なので、知らなかったものと合わせて、改めて頭に入れておくべきと思った内容も含めてメモしました。

railsguides.jp

セッション固定攻撃の対策

リンクはこちら

最も効果的な対応策は、ログイン成功後に古いセッションを無効にし、新しいセッションIDを発行することです。

devisegemを見てみると、sign_in時にセッションを一度捨てる処理がされています。

# lib/devise/controllers/sign_in_out.rb
def sign_in(resource_or_scope, *args)
  # ...(略)

  expire_data_after_sign_in!

  # ...
end

private

def expire_data_after_sign_in!
  session.keys.grep(/^devise\./).each { |k| session.delete(k) }
end

ファイルのダウンロード機能の注意

リンクはこちら

ファイルダウンロードをする際に、ダウンロードパスにparamsの値を含める場合は../../xxxなどのparamsで悪さをされないように、構築されたダウンロードパスとアプリ側が想定しているパスが一致するかを検証する必要があります。

結構うっかりしてしまいそうな箇所にも思うので、知識としてしっかり頭に入れておかないといけないですね。

アカウント総当たり攻撃に対する対策

リンクはこちら

Webアプリケーションの設計でおろそかにされがちなのは、いわゆる「パスワードを忘れた場合」ページです

ついついユーザーの目先の便利さを求めて「指定されたメールアドレスは存在しません」といった内容にしてしまいそうですが、これは攻撃者にとってはヒントになるので危ないということですね。
ただ、ユーザーがtypoした場合などもあるので、そういった際に本当に困っているユーザーが自主的に気付けるようにする仕組みが必要そうです。

メールの変更にもパスワードを要求するべし

リンクはこちら

アカウントのメールアドレスを変更する攻撃を仕掛けることでパスワードをお忘れですか?からアカウントを乗っ取るということができてしまうので、メールアドレスを変更する際にもパスワードを要求するべしという内容です。

パスワード変更の際に旧パスワードを要求するのは当然必要として忘れないと思いますが、メールアドレスの方は漏れやすいようにも思いました。

正規表現

リンクはこちら

Rubyの^や$は、入力全体の冒頭と末尾ではなく「行の」冒頭と末尾にマッチしてしまうので、\A\zを使うべしという内容です。

これは知らないとすぐ踏んでしまうと思います。

str = "javascript:exploit_code();/*\nhttp://hi.com\n*/"

str.match?(/^https?:\/\/[^\n]+$/i)
#=> true

str.match?(/\Ahttps?:\/\/[^\n]+\z/i)
=> false

ヘッダーインジェクションの対策

リンクはこちら

ヘッダーの情報も比較的容易に操作できてしまうので、User-Agentを管理者画面でエスケープなしで表示するなどしてはいけないという内容です。
また、refererもそのまま使うと危ないので適切な対処が必要です。

DNSバインディングの対策

リンクはこちら

DNSバインディングをきちんと理解できていなかったので、これを機に調べました。

罠サイトはユーザーが一度アクセスした直後に別のIPアドレスを返すように変更し、「信頼された」状態となったブラウザを通じてユーザーの内部ネットワークなどにアクセスします。

Railsでは、Rails.application.config.hostsで許可するホストを制限することでDNSバインディング対策を実施します。

deep_mungeで安全でないクエリ生成を防止

リンクはこちら

意図しないクエリを発行しないようにしているのがdeep_mungeメソッドです。

mungeはdeeplだと翻訳が出ませんでしたが、weblioだと「コードを難読化する」という意味が表示されました。

  • config.action_dispatch.perform_deep_munge = falseを設定してJSON{ "person": null }のパラメータを送信した時
params  #=> {"person":[null], ...}
  • config.action_dispatch.perform_deep_munge = falseを設定せずにJSON{ "person": null }のパラメータを送信した時
params  #=> {"person":[]... }

CORS

リンクはこちら

Rack CORSミドルウェアによってCORSを有効にできます。
Railsガイドの例のように設定してから以下のようにcurlでレスポンスヘッダーを確認すると、access-control-allow-originなどが設定できていることを確認できました。

$ curl -I -X OPTIONS http://localhost:3000 -H 'Origin: http://example.com'
...
access-control-allow-origin: http://example.com
access-control-allow-methods: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
...