CSRF対策を行うprotect_from_forgeryメソッド

こんにちは!kossyです!





本日の気になったニュースはこちら
https://headlines.yahoo.co.jp/hl?a=20181013-00000003-reut-bus_allheadlines.yahoo.co.jp

facebookくらいのアプリになれば、
セキュリティ対策を念入りに行っていると思うのですが、
それでも流出してしまうのですね。
むしろ大規模すぎて隅々まで対策を行うことができないのでしょうか。

情報流出は今回が初めてではないはずで、
facebook社のセキュリティリスクへの認識の甘さを感じます。



セキュリティ対策に関しては、いわゆる「徳丸本」がネットでの評判がよく、
ぜひ読んでみたいと思ってます。










さて、今回はprotect_from_forgeryメソッドが何をしているのか気になったので、
ブログに残してみようかと思います。





環境
Rails 5.1.6
Ruby 2.5.1
haml
MacOS





CSRFってなんぞや

CSRF(クロスサイトリクエストフォージェリ)とは、Webサイトの脆弱性をつくようなサイバー攻撃の一種で、
サイトに攻撃用のコード(主にjavascript)を埋め込むことで、
アクセスしてきたユーザに対して意図しない操作を行わせる攻撃のことを言います。

www.itmedia.co.jp
mixi、懐かしいですね。。。


そんなことはさておき、
CSRF攻撃を受けることで、SNS等で意図しない書き込みが行われたり、
ECサイトで勝手に商品購入をさせられたりといった事象が発生します。



Railsで行われているCSRF対策

Railsでは、

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
end
app/views/layouts/application.html.haml(今回はhamlで記述しています)

!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title アプリ名
    = csrf_meta_tags

省略

という記述がデフォルトでされており、
特に意識することなくCSRF対策を行うことができます。

ただ、htmlタグだけでフォームやリンクを生成すると、
対策の恩恵を受けることができません。


フォーム生成の際はビューヘルパーを使え

フォームやリンクを生成するときは、
form_forやlink_to等のビューヘルパーを利用するようにしましょう。
これによって、アプリ側でトークンと呼ばれる証明書のようなランダムな文字列が生成され、
フォームに埋め込まれるようにできます。

以下はform_forを用いたフォーム画面の例と、
ページのソースを表示したものを一部抜粋して記載しています。

_form.html.haml

  = form_for [@review] do |f|
    .form-group
      = f.label 'タイトル'
      = f.text_field :bookname, class: 'form-control', placeholder: 'Type a bookname'
    .form-group
      = f.label '言語'
      = f.select :language, [["",""], ["HTML&CSS","HTML&CSS"], ["Ruby","Ruby"]], {}
    .form-group
      = f.label 'レベル'
      = f.select :level, [["",""], ["初級","初級"], ["中級","中級"], ["上級","上級"]], {}
    .form-group
      = f.label '画像'
      = f.file_field :image
    .form-group
      = f.label '本文'
      = f.text_area :content, class: "form-control form-textarea"
    = f.submit '投稿', class: "btn btn-primary"
chromeの「ページのソースを表示」機能で展開されたソースコード

<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
<title>FriendlyReview</title>
<b><meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="D0b+EkIWQ2kuSoVqp2mzED5lsJP/hzsr8yugtFGaQ+XBC..." /></b>

省略

<form class="new_review" id="new_review" enctype="multipart/form-data" action="/reviews" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="&#x2713;" />
<input type="hidden" name="authenticity_token" value="kHbl258N5/V5eChnjoZ8P1QhionN978xO..." />

authenticity_tokenの後に続くvalueトークンになっています。
Railsでは、リクエスト処理時にアプリ側で保持しているトークンと、
リクエスト情報として送信されるトークンと比較し、
一致していればそれ以降の処理を行います。
もし一致していない、またはトークンそのものが存在しないと、
ActionController::InvalidAuthenticity Token という例外を発生します。







まとめ

・HTMLタグだけでフォームを作るな、ビューヘルパーを使え
・レイアウトファイルにcsrf_meta_tagsの記載を忘れるな
・HTTP GET によるリンクでデータの操作をしない

CSRF対策における注意事項は上記3点です。





参考にさせていただいた記事

Rails セキュリティガイド - Rails ガイド