Laravel の脆弱性 CVE-2017-14775 の対処法

こんにちは。エンジニアの @localdisk です。2017/09/27に CVE-2017-14775 という Laravel の脆弱性が報告されました。CVE-2017-14775 はオートログイン処理に*1タイミング攻撃の脆弱性があるというものです。

タイミング攻撃についてとその対策については下記エントリに詳しくまとまっています。

この脆弱性は 2017/09/21 にリリースされた 5.5.10 で修正されています。対象のクラスは下記になります。

  • Illuminate\Auth\DatabaseUserProvider
  • Illuminate\Auth\EloquentUserProvider

修正された PR は下記になります。

以前は remember_token を DB の where 条件としていますがこの修正ではプライマリキーでモデルを取得し remember_token カラムの値と引数を hash_equals 関数で比較しています。

この hash_equals 関数がポイントでこの関数はタイミング攻撃の対策となります。

即座にバージョンアップできないあなたへ

さて、5.5 をお使いの人は composer update すれば万事解決なのですが、5.5 未満のバージョンを使っている場合はそうもいきません。現在の LTS は 5.5 であり、それ未満のバージョンを使っている場合は自力で対処するしかありません。なのでやっていきましょう💪

カスタムプロバイダへの差し替え

(2017/10/17 追記) EloquentUserProvider, DatabaseUserProvider の修正は大体一緒なので本エントリでは EloquentUserProvider の差し替え方を説明します。

CVE-2017-14775の脆弱性で修正が必要なのは下記のクラスの retrieveByToken メソッドです。

  • Illuminate\Auth\DatabaseUserProvider
  • Illuminate\Auth\EloquentUserProvider

config/auth.php の下記でどの Provider を使用しているかがわかります。

<?php
return [
'providers' => [
    'users' => [
        'driver' => 'eloquent', // ここ。デフォルトは eloquent
        'model' => App\User::class,
    ],
];

drivereloquent の場合は EloquentUserProviderdatabase の場合は DatabaseUserProvider が差し替えの対象になります。

まずは差し替える カスタムプロバイダ を作成しましょう。 app ディレクトリの下に Auth ディレクトリを作成し、下記 CustomEloquentUserProvider を作成しました。

<?php

namespace App\Auth;

use Illuminate\Auth\EloquentUserProvider;

class CustomEloquentUserProvider extends EloquentUserProvider
{

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed $identifier
     * @param  string $token
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token)
    {
        $model = $this->createModel();

        $model = $model->where($model->getAuthIdentifierName(), $identifier)->first();

        if (!$model) {
            return null;
        }

        $rememberToken = $model->getRememberToken();

        return $rememberToken && hash_equals($rememberToken, $token) ? $model : null;
    }
}

retrieveByToken メソッドは修正された内容を反映したものをそのままコピーしています。次は \App\Providers\AuthServiceProviderboot メソッドに下記内容を追加します。

<?php
class AuthServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // custom という名前で カスタムプロバイダ を登録
        Auth::provider('custom', function ($app, array $config) {
            return new CustomEloquentUserProvider($app['hash'], $config['model']);
        });
    }
}

最後に config/auth.php を変更します。カスタムプロバイダ を指定して終了。簡単ですね。

<?php
return [
'providers' => [
    'users' => [
        'driver' => 'custom', // eloquent から custom に変更
        'model' => App\User::class,
    ],
];

まとめ

今回はバージョンアップする工数はすぐには確保できない方に向けてエントリを書きましたが、一番の対策はこまめなバージョンアップです。Laravel AnnouncementsLaravel News 等、便利な情報源がありますので上手く活用していきましょう。

*1:Remember me のアレ