Rails CVE-2015-7576 で見る タイミングアタック(Timing Attack)

2016-01-31
Rails セキュリティ

Rails 4.2.5.1 がリリースされました

いくつかのセキュリティフィックスのリリースです

あまり重要ではないですが、 CVE-2015-7576 の対応について見てみます

対象のコミットはこちらです

問題となったコード

http_basic_authenticate_with を使ったBasic認証の際にタイミングアタックの脆弱性がありました

authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
  name == options[:name] && password == options[:password]
end

http_basic_authenticate_with は以下のように Controllerに書くことで利用できます

http_basic_authenticate_with name: 'user', password: 'password'

そもそもタイミングアタックとはなんなのか?

以下の2つのコードはどちらが実行時間が早いでしょうか?

# コードA
'foo' == 'bar'
# コードB
'bar' == 'baz'

ms以下のレベルになるのですが、コードBのほうが実行時間が長くなります

  • コードAは1文字目が異なるので、1文字目で比較判定を打ち切ります
  • コードBは1文字目が同じで2文字目が異なるので 2文字目で比較判定を打ち切ります

そのため、コードBは1文字分だけ実行時間が長くなります

この実行時間の違いを検出してパスワード等で利用されている文字を推測するのがタイミングアタックと呼ばれる攻撃のようです

CVE-2015-7956 の修正コード

authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
  ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) &
    ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password])
end
  • `ActiveSupport::SecurityUtils.variable_size_secure_compare` を使用するように変更されている
  • && から & のみとなっている

ActiveSupport::SecurityUtils.variable_size_secure_compare

入力文字列を SHA256 のハッシュ文字列に変換後 byte単位での比較をしています

def variable_size_secure_compare(a, b) # :nodoc:
  secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
end

ハッシュ関数を通し、ActiveSupport::SecurityUtils.secure_compare を利用することで

文字長の違いからのタイミングアタックを防止しているようです

まとめ

ユーザからの入力文字列とサーバ上のセキュアなもの(パスワードとか)を比較する際には

ActiveSupport::SecurityUtils.variable_size_secure_compare

を利用するようにしたほうが良さそう

その他

Rails Basic認証等で検索すると以下のようなコードが散見されます

authenticate_or_request_with_http_basic do |user, pass|
  user == 'user' && pass == 'pass'
end

(サーバのスペック向上に伴いタイミングアタック自体が成立しにくくなっているとはいえ)

バージョンアップ後のRails標準のhttp_basic_authenticate_withを利用する

以下のように今回の対策と同じコードとしておく

authenticate_or_request_with_http_basic do |user, pass|
ActiveSupport::SecurityUtils.variable_size_secure_compare(name, 'user') &
  ActiveSupport::SecurityUtils.variable_size_secure_compare(password, 'password')
end