TECH EXPERT 68日目

TECH::EXPERT

1次面接だけど事実上の内定。内容も悪くないのでココにしようと思う。少なくとも今まで面接受けた中では環境は良さそうだった。給料面は今一つだけど、交渉次第か。
と同時にこれ以上Techに通う意味も失った。即答してもつまらないので会社には週明けに返答しますと伝えました。

enum

enumはint型、boolean型で定義されたカラムを、文字列で表現できるようにする機能。

class Drink < AppricationRecord
# Drinkモデルのkindrカラムのenumを定義する
# DBに保存されている値が0の時はtea, 1の時はcoffee、2の時はbeerを意味する
  enum kind: [:tea, :coffee, :beer]
end
Drink.kinds
=> {"tea"=>0, "coffee"=>1, "beer"=>2}

Drink.kinds.keys
=> {"tea", "coffee", "beer"}

具体例としてform.selectに渡すなどで利用できる。
f.select :kind, Task.kinds.keys, {}, class: 'form-control', placeholder: 0

enumを指定したカラムにデフォルト値を与えない場合、Drink.newした場合にはnilが入ることとなり、tea?もcoffee?もbeer?も全てfalseを返すようになる。
条件付けの場合に、バグを残す余地となる。

activerecord-import

BULK INSERTを行うためのgem。
DBに複数のデータをまとめて登録する際に、INSERTを登録回数分繰り返すのではなく、1回のINSERTで登録する。

INSERT INTO users (column) VALUES (values), (values), (values);

使い方は登録するデータを配列にまとめてimportメソッドで渡す。

users = []
100.times do |i|
  users << User.new(name: "dummy-#{i+1}", ticket_count: 0)
end
# importメソッドの引数に配列を渡して、まとめてレコードを作成する
User.import users

例外処理

Exceptionというクラスを継承する形で様々な例外が定義されています。ここまでよく見てきた NoMethodError、SyntaxError などは、全てExceptionクラスの子孫クラスであり、例外です。例外が発生すると、それ以降の処理は中止され、実行されなくなってしまいます。

Ruby on Railsの開発を行う際に、よく利用するのがrescue。

rescue

rescueとは、発生した例外を捕捉し、例外が起こった際に呼び出される条件節です。
例外が発生しそうな部分をbeginから始まるブロックで囲み、ブロックの内部にrescueを記述して使用します。

通常、each, times, whileなどの繰り返しの最中に例外が発生した場合、繰り返し処理は途中で中止され、次のループが実行されることはありません。ただし、繰り返し処理の内部にrescueを記述していた場合、例外が発生したとしても、次のループに移ることができます。

大量のレコードを扱う処理で、例外が発生しても繰り返しを続行したい場合には、rescueを使うようにしましょう。

通常、レコードを操作するメソッド(save create update destroyなど)は、実行に成功したらtrue、失敗したらfalseを返すため、例外を生じることがありません。
例外を意図的に発生させたい場合、元々のメソッド名に「!」をつけると、実行に失敗した際にfalseを返すのではなく例外を発生させるようになります。先ほど挙げたメソッドで言うと、save! create! destroy!が例外を発生させるメソッドになります。

namespace :distribute_ticket do
  desc "全ユーザーのticket_countをrescueしながら10増加させる"
  task rescue: :environment do
    User.find_each do |user|
      begin
        user.increment!(:ticket_count, 10)
      rescue => e
        Rails.logger.debug e.message
      end
    end
  end

rescue => eという記述がありますが、これは発生した例外をrescue節以下で‘e’として扱うという意味です。
続くRails.logger.debug e.messageで、発生した例外をログに記録しています。

トランザクション

複数のレコードの更新を1つにまとめて行うこと。
「全て実行されるか、それとも全て実行されないか」という1か0の状況を作る。

以下のようにトランザクションを用いて行うことで、処理に例外が発生した場合には全てのレコードを保存せずに戻る。

ActiveRecord::Base.transaction do 
  current_user.pay!(@product.price)
  current_user.confirm_purchase!(@product)
end

セキュリティ

表示処理に伴う問題

xss(クロスサイトスクリプティング)という、外部からの入力に対しての脆弱性を突いた攻撃と対策。

SQL呼び出しに伴う脆弱性

SQLインジェクションという、アプリケーション側のSQLの呼び出し時のセキュリティ不備を意図的に利用する攻撃と対策。

「重要な処理」に伴う脆弱性

CSRF(クロスサイトリクエストフォージェリ)という、ユーザーにとって重要な処理を勝手に行ったことにしてしまう攻撃手法と対策。

セッション管理の不備

セッションハイジャックという、セッションに関する攻撃と対策。

セッション

複数回に渡るリクエストにおいて、クライアントを特定するための仕組み。
具体的には、クライアントは初回のリクエストで自身を識別させるIDをサーバーに渡す。
以降、サーバーはそのIDを持ってクライアントを認識する。

クッキー(cookie)

クライアント側のブラウザに保持することができる情報のことです。通常、初回の通信でサーバーがクライアントにクッキーとしてセッションIDを保持させ、以降クライアントはそれを用いてサーバーに対して自身を特定させます。

サーバー側で明示的にクッキー(cookie)を設定しますよ、という宣言をします。
Railsでは、session_store.rbというファイル内でセッションの管理方法を指定します。デフォルトでクッキー(cookie)を利用する設定になっているため、Railsでは意識せずクッキー(cookie)でのセッション管理を行うことができます。

config/initializer/session_store.rb

TechReviewSite::Application.config.session_store :cookie_store, key: '_tech_review_site_session'

XSS(クロスサイトスクリプティング)

一般的な対策方法

XSSが発生する主要因として、フォームから入力されたHTMLタグがそのままページに反映されてしまっていることがあげられます。
したがって、XSSを防ぐためにはHTMLを生成する際に意味を持つ「”」や「<」を文字参照によってエスケープすることが基本となります。

文字参照

文字参照とはHTML上で直接記述できない特殊文字を表記する際に用いられる記法です。例えば、HTML中に「<」もしくは「>」と記述するとこの二つはタグの初め、終わりと認識されてしまいます。これでは文字列として上記の記号を用いることができません。そこで、文字参照を利用します。

変換前変換後
<& lt;
>& gt;
&& amp;
& quot;
& #39;

上の表は、XSSを対策する際にエスケープすべき特殊文字の一覧です。これらの特殊文字を文字参照に変換して保存すれば、外部から埋め込まれたスクリプトが実行されることはありません。

railsではデフォルトでエスケープ処理される。
<%= %>で囲まれたものが出力される際は、文字参照の変換が行われた状態で表示されるという仕組みになっています。

raw()

<%= raw(tweet.text) %>

rawを用いるとエスケープ処理を行わない。

SQLインジェクション

SQLの呼び出し方においてセキュリティ上の不備を意図的に利用し、データベースシステムを不正に操作する攻撃

一般的な対策方法

SQLインジェクションの原因は、ユーザからの入力がアプリケーション内部でSQL文として解釈されてしまい、実行されてしまうことが挙げられます。
そのため、SQLインジェクション脆弱性を解消するためには、SQL文を組み立て実行される際にSQL文を変更されることを防ぐことです。
SQL文の変更を防ぐには以下の対策方法があります。

  • SQL文中で意味を持つ特殊文字をエスケープする
  • バインド機構を利用する

SQL文中で意味を持つ特殊文字をエスケープする

SQL文中では「’」が文字列の終端、「/」がエスケープ文字を意味しています。

下記のようなフォームでエスケープ処理を行わなかった場合、

# $uidと$pwdはユーザのフォームの入力値
SELECT * FROM user WHERE user_id='$uid' AND password='$pwd' 
$uid  tarou
$pwd  ' OR 'A'='A

SELECT * FROM user WHERE user_id='tarou' AND password='' OR 'A'='A'

バインド機構を利用する

ユーザからの値を割り振る前に、そのままデータベースを処理するところに送られ、SQL文の構造を確定する仕組み。

SELECT * FROM user WHERE user_id=? AND password=?

RailsでのSQLインジェクション対策

find_by_sql

SQL文を直接書いて、レコードを取得するメソッドです。
以下はusersテーブルから全てのレコードを取得する処理をfind_by_sqlメソッドを使った例です。ActiveRecordとは異なり直接SQL文を記述する。

def search
  @products = Product.find_by_sql("select * from products where title like '%#{params[:keyword]}%' LIMIT 20")
end

上の状態では危険なので、下のように変更する。

def search
  keyword = "%#{params[:keyword]}%"
  @products = Product.find_by_sql(["select * from products where title like ? LIMIT 20", keyword])
end

CSRF(クロスサイトリクエストフォージェリ)

CSRFとは

Webサイトにスクリプトや自動転送(HTTPリダイレクト)を仕込むことによって、利用者に意図せず別のWebサイト上で何らかの操作(掲示板への書き込みや銀行口座への送金など)を行わせる攻撃手法のことをいいます。
CSRFの脆弱性が存在すると以下のような被害を被る可能性があります。

  • 利用者のアカウントによる物品の購入
  • 利用者の退会処理
  • 利用者のアカウントによる掲示板への書き込み
  • 利用者のパスワードやメールアドレスが変更

CSRF脆弱性の影響は「重要な処理」の悪用に限られるため、CSRFの脆弱性を個人情報の取得等に用いることはできません。

一般的な対策方法

CSRF攻撃を防ぐには、「重要な処理」に対するリクエストが利用者の意図によるものかどうかを確認することが必要になります。
このためCSRF対策としては、以下の2点が考えられます。

  • CSRF対策の必要なページを区別する
  • 正規利用者の意図したリクエストを区別できるように実装する

CSRF対策の必要なページを区別する

CSRF対策はすべてのページに行う必要はない。
むしろ、対策の必要がないページの方が多い。
対策に必要なページは、他のサイトから勝手に実行されては困るようなページ。
例えば、ECサイトの物品購入ページや、パスワード変更など個人情報の編集確定画面などが挙げられます。
CSRFの対策としては、まず実装するWebアプリケーションのどのページに脆弱性対策が必要なのか設計段階で明らかにすることです。

正規利用者の意図したリクエストを区別できるように実装する

CSRF対策で必要なことは、正規利用者の意図したリクエストなのかどうかということです。
ここでの意図したリクエストとは、利用者が対象のアプリケーション上で「実行」ボタンなどを押して、「重要な処理」のリクエストを発行することをいいます。
正規のリクエストかどうかを判断する方法は3種類あります。

  • 秘密情報の埋め込み
  • パスワードの再入力
  • Refererのチェック

秘密情報の埋め込み

フォームに秘密情報(トークン)を埋め込む。

RailsでのCSRF対策方法

protect_from_forgery with: :exception

まずサイトのHTMLに一意のトークンを埋め込みます。これと同じトークンを、セッションcookie(クッキー)にも保存しています。ユーザーがPOSTリクエストを送信すると、HTMLに埋められているCSRFトークンも一緒に送信されます。あとは、サーバ側でページのトークンとセッション内のトークンを比較し、両者が一致することを確認したらリクエストを受け付けます。

セッションハイジャック

  • セッションIDの推測
  • セッションIDの盗み出し
  • セッションIDの強制

セッションIDの強制

ここまでは正規利用者に設定されたセッションIDを推測したり、盗み出して利用する手法でしたが、セッションIDを悪意のある第三者が外部から強制させる方法があり、これをセッションIDの固定化といいます。
セッションIDの固定化は以下の手順で行われます。

  1. 悪意のある第三者は普通の利用者としてセッションID(abc123)を取得
  2. 被害者に対して1.で取得したセッションIDを強制
  3. 被害者は標的アプリケーションにログイン
  4. 悪意のある第三者は、被害者に強制したセッションID(abc123)を使って標的アプリケーションにアクセスする

セッションIDの固定化攻撃に対策する方法として、認証が完了した時点でセッションIDを変更するという方法があります。

以下のコメントアウトを外す。

/pictweet/config/environments/production.rb

config.force_ssl = true

セッション固定化攻撃に関してもRailsでは1行のコードで対策することが可能です。
対策として、ログイン成功後に古いセッションを無効にし新しいセッションIDを発行するという方法があります。

reset_session

コメント