本当は危ないCodeIgniter4の自動ルーティング

(2022-02-08 追記) この記事は古くなっています。 「【改訂版】本当は危ないCodeIgniter4の自動ルーティング」 を参照してください。

CodeIgniter4のルーティングはデフォルトではCodeIgniter3と同じくコントローラとメソッド名により、自動的にルーティングされます。 ルート設定を手動で行う必要がないため、非常に便利です。

しかし、コントローラフィルター機能が追加されたため、コントローラのロジックから一部がフィルターに分離されるようになりました。 例えば、認証済みかどうかの判定をフィルターに移すことができます。

この分離が、リスクを生み出しました。今日は、この自動ルーティングの危険性について解説します。

動作確認環境

  • CodeIgniter 4.1.6-dev (b5478b352bc6dc4b993651c41c7e157509d797cd)
  • PHP 8.0.13
  • PHPビルトインサーバー
  • macOS 10.15.7

準備

動作を確認するための、テストコードを作成します。

Blogコントローラの作成

まず、コントローラを作成します。

app/Controllers/Blog.php

<?php

namespace App\Controllers;

class Blog extends BaseController
{
    public function index()
    {
        echo __METHOD__ . '<br>';
    }
}

http://localhost:8080/blog にアクセスすると、以下のようにメソッド名が表示されます。

App\Controllers\Blog::index

Authフィルターの作成

認証済みかどうかをチェックするためのフィルターを作成します。

app/Filters/Auth.php

<?php

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

class Auth implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        echo __METHOD__ . '<br>';
    }

    public function after(
        RequestInterface $request,
        ResponseInterface $response,
        $arguments = null
    ) {}
}

ロジックはまだありませんが、beforeフィルターが適用されると、メソッド名が表示されます。

app/Config/Filters.php によるフィルター設定

フィルターの設定

それでは、http://localhost:8080/blog にアクセスされた場合に、Authフィルターが適用されるように設定します。

--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -2,6 +2,7 @@

 namespace Config;

+use App\Filters\Auth;
 use CodeIgniter\Config\BaseConfig;
 use CodeIgniter\Filters\CSRF;
 use CodeIgniter\Filters\DebugToolbar;
@@ -23,6 +24,7 @@ class Filters extends BaseConfig
         'honeypot'      => Honeypot::class,
         'invalidchars'  => InvalidChars::class,
         'secureheaders' => SecureHeaders::class,
+        'auth'          => Auth::class,
     ];

     /**
@@ -64,5 +66,7 @@ class Filters extends BaseConfig
      *
      * @var array
      */
-    public $filters = [];
+    public $filters = [
+        'auth' => ['before' => ['blog']]
+    ];
 }

動作確認

http://localhost:8080/blog にアクセスすると、以下のように実行されたメソッド名が表示されます。

App\Filters\Auth::before
App\Controllers\Blog::index

Authフィルターが適用されていることがわかります。OKですね。

フィルターの回避

さて、上記のコードにはフィルターを回避できる脆弱性があります。

その脆弱性を利用すると、フィルターを回避でき、以下の出力を得ることができます。

App\Controllers\Blog::index

これは、まずいですね。認証機能を回避できてしまうことになります。

正しいフィルター設定

正しいフィルター設定は以下になります。これで、フィルターを回避されません。

app/Config/Filters.php

    public $filters = [
        'auth' => ['before' => ['blog*']]
    ];

仮に、以下のようにURIを追加しても、まだ脆弱なので注意してください。

    public $filters = [
        'auth' => ['before' => ['blog', 'blog/index']]
    ];

app/Config/Filters.php でのフィルター設定では、URIの指定には必ず最後にワイルドカード * を含める必要があります。

app/Config/Routes.php によるフィルター設定

app/Config/Filters.php でのフィルター設定では設定ミスが起こりうるなら、 app/Config/Routes.php でルートを指定してフィルター設定する方がいいでしょうか?

今度は、そのようにしてみましょう。app/Config/Filters.php$filters は空の配列に戻します。

フィルターの設定

app/Config/Routes.php でルートを追加し、フィルターを指定します。

--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -33,6 +33,8 @@ $routes->setAutoRoute(true);
 // route since we don't have to scan directories.
 $routes->get('/', 'Home::index');

+$routes->get('blog', 'Blog::index', ['filter' => 'auth']);
+
 /*
  * --------------------------------------------------------------------
  * Additional Routing

これで、http://localhost:8080/blog に対して auth フィルターが適用されます。

動作確認

http://localhost:8080/blog にアクセスすると、以下のように実行されたメソッド名が表示されます。

App\Filters\Auth::before
App\Controllers\Blog::index

Authフィルターが適用されていることがわかります。OKですね。

フィルターの回避

しかし、上記の設定でもやはりフィルターを回避できる脆弱性があります。

その脆弱性を利用すると、以下のようにフィルターを回避でき、以下の出力を得ることができます。

App\Controllers\Blog::index

正しい設定

はい、もうおわかりかも知れませんが、これは自動ルーティングが有効だからです。

正しい設定方法は、自動ルーティングをオフにしてからルートを追加することです。

--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -21,7 +21,7 @@ $routes->setDefaultController('Home');
 $routes->setDefaultMethod('index');
 $routes->setTranslateURIDashes(false);
 $routes->set404Override();
-$routes->setAutoRoute(true);
+$routes->setAutoRoute(false);

 /*
  * --------------------------------------------------------------------

これで、blog へのルートしかなくなり、このルートにフィルターが指定されます。

まとめ

ルーティング&フィルター設定で最も安全な方法は以下です。

  • 自動ルーティングはオフに設定する
  • ルートはすべて手動でHTTPメソッド別に設定する
  • フィルターはルートに対して指定する

また、

  • app/Config/Filters.php でのフィルター設定では、URIの指定には必ず最後に * を含める

参考

Tags: codeigniter, codeigniter4, security

CodeIgniter4のルーティング

(2022-12-05 追記) この記事は古くなっています。 「【改訂版】CodeIgniter4のルーティング」 を参照してください。

CodeIgniter4のルーティングについて解説します。

自動ルーティング

デフォルトでは、CodeIgniter3と同じく、以下の規約により、自動でルーティングされます。

http://example.com/{コントローラ名}/{メソッド名}/{引数1}/{引数2}/...

手動ルーティング

CodeIgniter3と同じく、特定のルートを手動で設定することも可能です。

自動ルーティングをオフに

CodeIgniter3と異なるのは、自動ルーティングをオフに設定できることです。

app/Config/Routes.php を以下のように変更します。

app/Config/Routes.php

$routes->setAutoRoute(false);

オフに設定するとコントローラがあるだけではルーティングされないため、ルートを設定しない限り、コントローラにアクセスできなくなります。

なお、セキュリティ上の理由から、自動ルーティングは推奨しません。できる限り、オフに設定してください。

HTTPメソッドでのルーティング

ルートは、app/Config/Routes.php に設定します。

特定のHTTPメソッドを指定してルートを設定することができます。

GETメソッドの場合は、$routes->get() を使用します。

$routes->get('news', 'News::index');

上記は、http://example.com/news にアクセスすると、News コントローラの index() メソッドが呼び出されます。

POSTメソッドの場合は、$routes->post() を使用します。

$routes->post('news/create', 'News::create');

上記は、http://example.com/news/create にアクセスすると、News コントローラの create() メソッドが呼び出されます。

$routes->put()$routes->delete() なども同様です。

URLの一部をキャプチャ

(:segment)(:any) などのプレースホルダーを使います。 (:segment) は1つのURIセグメント、(:any) は任意の文字列にマッチします。

$routes->get('news/(:segment)', 'News::view/$1');

上記は、/news/foo の場合に、News コントローラの view() メソッドに foo を渡します。

ルートのグループ化

ルートをグループ化することもできます。

$routes->group('admin', function ($routes) {
    $routes->get('users', 'Admin\Users::index');
    $routes->get('blog', 'Admin\Blog::index');
});

上記は、admin/usersadmin/blog のルートを設定しています。

コントローラフィルターの適用

ルートに対してコントローラフィルターを指定することもできます。

以下は、login というフィルターをルートに適用するサンプルです。

$routes->post('news/create', 'News::create', ['filter' => 'login']);
$routes->group('admin', ['filter' => 'login'], function ($routes) {
    $routes->get('users', 'Admin\Users::index');
    $routes->get('blog', 'Admin\Blog::index');
});

ルート設定の確認

spark routes コマンドで設定したルートを確認することができます。

自動ルーティングされるものは表示されません。

$ php spark routes
CodeIgniter v4.1.5 Command Line Tool - Server Time: 2021-12-09 19:48:42 UTC-06:00

+--------+----------------------------+------------------------------------------------+
| Method | Route                      | Handler                                        |
+--------+----------------------------+------------------------------------------------+
| GET    | /                          | \App\Controllers\Home::index                   |
| CLI    | migrations/([^/]+)/([^/]+) | \CodeIgniter\Commands\MigrationsCommand::$1/$2 |
| CLI    | migrations/([^/]+)         | \CodeIgniter\Commands\MigrationsCommand::$1    |
| CLI    | migrations                 | \CodeIgniter\Commands\MigrationsCommand::index |
| CLI    | ci(.*)                     | \CodeIgniter\CLI\CommandRunner::index/$1       |
+--------+----------------------------+------------------------------------------------+

デフォルトでは上記のルートが設定されています。ルートは上から順に評価され、マッチすればそのメソッドが実行されます。

なお、CLI はコマンドライン用の特殊なルートです。

参考

Tags: codeigniter, codeigniter4

CodeIgniter4の認証ライブラリMyth Authを使う

(2022-05-26 追記) 「CodeIgniter4の公式認証ライブラリCodeIgniter Shieldを使う」 も参照してください。

CodeIgniter4の認証ライブラリMyth Authを使い、チュートリアルの記事作成にはログインが必要にしてみます。

動作確認環境

  • CodeIgniter 4.1.5
  • Myth Auth 1.0.1
  • PHP 8.0.10
  • MySQL 8.0.27
  • Docker Desktop 4.2.0
  • macOS 10.15.7

インストール

Composerでのインストール

ComposerでMyth Authをインストールします。

$ composer require myth/auth

設定

設定ファイルを変更します。

メール設定

app/Config/Email.php に管理者のメールアドレスを設定します。

--- a/app/Config/Email.php
+++ b/app/Config/Email.php
@@ -9,12 +9,12 @@ class Email extends BaseConfig
     /**
      * @var string
      */
-    public $fromEmail;
+    public $fromEmail = 'admin@example.jp';

     /**
      * @var string
      */
-    public $fromName;
+    public $fromName = 'Admin User';

     /**
      * @var string

バリデーション設定

app/Config/Validation.php にバリデーションルールを追加します。

--- a/app/Config/Validation.php
+++ b/app/Config/Validation.php
@@ -24,6 +24,7 @@ class Validation
         FormatRules::class,
         FileRules::class,
         CreditCardRules::class,
+        \Myth\Auth\Authentication\Passwords\ValidationRules::class,
     ];

     /**

デバッグツールバー設定

コレクターを追加します。

--- a/app/Config/Toolbar.php
+++ b/app/Config/Toolbar.php
@@ -42,6 +42,7 @@ class Toolbar extends BaseConfig
         Files::class,
         Routes::class,
         Events::class,
+        \Myth\Auth\Collectors\Auth::class,
     ];

     /**

コントローラフィルター設定

フィルタークラスを追加します。

--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -19,6 +19,9 @@ class Filters extends BaseConfig
         'csrf'     => CSRF::class,
         'toolbar'  => DebugToolbar::class,
         'honeypot' => Honeypot::class,
+        'login'      => \Myth\Auth\Filters\LoginFilter::class,
+        'role'       => \Myth\Auth\Filters\RoleFilter::class,
+        'permission' => \Myth\Auth\Filters\PermissionFilter::class,
     ];

     /**

認証の設定

記事の作成ページ news/create にのみ、loginフィルターを設定します。

--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -60,5 +63,7 @@ class Filters extends BaseConfig
      *
      * @var array
      */
-    public $filters = [];
+    public $filters = [
+        'login' => ['before' => ['news/create*']],
+    ];
 }

ルーティング

モジュール(vendor/myth/auth)で設定されているルートが有効になるように、(:any) で受けているルールのプライオリティを下げます。

--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -22,6 +22,7 @@ $routes->setDefaultMethod('index');
 $routes->setTranslateURIDashes(false);
 $routes->set404Override();
 $routes->setAutoRoute(true);
+$routes->setPrioritize();

 /*
  * --------------------------------------------------------------------
@@ -36,7 +37,7 @@ $routes->get('/', 'Home::index');
 $routes->match(['get', 'post'], 'news/create', 'News::create');
 $routes->get('news/(:segment)', 'News::view/$1');
 $routes->get('news', 'News::index');
-$routes->get('(:any)', 'Pages::view/$1');
+$routes->get('(:any)', 'Pages::view/$1', ['priority' => 1]);

 /*
  * --------------------------------------------------------------------

設定を確認しておきます。

$ php spark routes
CodeIgniter v4.1.5 Command Line Tool - Server Time: 2021-12-09 17:26:36 UTC+09:00

+--------+----------------------------+--------------------------------------------------------------+
| Method | Route                      | Handler                                                      |
+--------+----------------------------+--------------------------------------------------------------+
| GET    | /                          | \App\Controllers\Home::index                                 |
| GET    | news/create                | \App\Controllers\News::create                                |
| GET    | news/([^/]+)               | \App\Controllers\News::view/$1                               |
| GET    | news                       | \App\Controllers\News::index                                 |
| GET    | login                      | \Myth\Auth\Controllers\AuthController::login                 |
| GET    | logout                     | \Myth\Auth\Controllers\AuthController::logout                |
| GET    | register                   | \Myth\Auth\Controllers\AuthController::register              |
| GET    | activate-account           | \Myth\Auth\Controllers\AuthController::activateAccount       |
| GET    | resend-activate-account    | \Myth\Auth\Controllers\AuthController::resendActivateAccount |
| GET    | forgot                     | \Myth\Auth\Controllers\AuthController::forgotPassword        |
| GET    | reset-password             | \Myth\Auth\Controllers\AuthController::resetPassword         |
| GET    | (.*)                       | \App\Controllers\Pages::view/$1                              |
| POST   | news/create                | \App\Controllers\News::create                                |
| POST   | login                      | \Myth\Auth\Controllers\AuthController::attemptLogin          |
| POST   | register                   | \Myth\Auth\Controllers\AuthController::attemptRegister       |
| POST   | forgot                     | \Myth\Auth\Controllers\AuthController::attemptForgot         |
| POST   | reset-password             | \Myth\Auth\Controllers\AuthController::attemptReset          |
| CLI    | migrations/([^/]+)/([^/]+) | \CodeIgniter\Commands\MigrationsCommand::$1/$2               |
| CLI    | migrations/([^/]+)         | \CodeIgniter\Commands\MigrationsCommand::$1                  |
| CLI    | migrations                 | \CodeIgniter\Commands\MigrationsCommand::index               |
| CLI    | ci(.*)                     | \CodeIgniter\CLI\CommandRunner::index/$1                     |
+--------+----------------------------+--------------------------------------------------------------+

ログインページなどのコントローラが Myth\Auth 以下のものになっており、

| GET    | (.*)                       | \App\Controllers\Pages::view/$1

より上に設定されています。

DBマイグレーション

マイグレーションを実行して必要なテーブルを作成します。

$ php spark migrate -n 'Myth\Auth'
CodeIgniter v4.1.5 Command Line Tool - Server Time: 2021-12-09 16:56:06 UTC+09:00

Running all new migrations...
    Running: (Myth\Auth) 2017-11-20-223112_App\Database\Migrations\CreateAuthTables
Done migrations.

以下のテーブルが作成されました。

auth_activation_attempts
auth_groups
auth_groups_permissions
auth_groups_users
auth_logins
auth_permissions
auth_reset_attempts
auth_tokens
auth_users_permissions
users

ユーザーの作成

サンプルのユーザーを作成します。

$ php spark auth:create_user user1 user1@example.jp
CodeIgniter v4.1.5 Command Line Tool - Server Time: 2021-12-09 17:39:41 UTC+09:00

New user created: user1, #1

作成したユーザーのパスワードを変更します。

$ php spark auth:set_password user1 password
CodeIgniter v4.1.5 Command Line Tool - Server Time: 2021-12-09 17:56:23 UTC+09:00

Password successfully set for user with identity: user1

ログインページ

http://localhost/news/create にブラウザでアクセスします。

http://localhost/login にリダイレクトされました。

先ほど作成したユーザーでログインします。

ユーザー名:user1
パスワード:password

ログインが成功すると、記事作成ページにリダイレクトされます。

右下のアイコンからデバッグツールバーを表示させて、ユーザーの情報を確認できます。

http://localhost/logout にアクセスするとログアウトします。

Myth Authのカスタマイズ

Myth Authのファイルをカスタマイズしたい場合は、 publishコマンドを実行します。 Myth Authのファイルが app/ 以下にインストールされます。

$ php spark auth:publish
CodeIgniter v4.1.5 Command Line Tool - Server Time: 2021-12-09 16:36:40 UTC+09:00

Publish Migration? [y, n]: y
  Created: Database/Migrations/2017-11-20-223112_create_auth_tables.php
  Remember to run `spark migrate -all` to migrate the database.
Publish Models? [y, n]: y
  Created: Models/LoginModel.php
  Created: Models/UserModel.php
Publish Entities? [y, n]: y
  Created: Entities/User.php
Publish Controller? [y, n]: y
  Created: Controllers/AuthController.php
Publish Views? [y, n]: y
  Created: Views/Auth/_footer.php
  Created: Views/Auth/_header.php
  Created: Views/Auth/_message_block.php
  Created: Views/Auth/_navbar.php
  Created: Views/Auth/emails/activation.php
  Created: Views/Auth/emails/forgot.php
  Created: Views/Auth/forgot.php
  Created: Views/Auth/layout.php
  Created: Views/Auth/login.php
  Created: Views/Auth/register.php
  Created: Views/Auth/reset.php
Publish Filters? [y, n]: y
  Created: Filters/LoginFilter.php
  Created: Filters/PermissionFilter.php
  Created: Filters/RoleFilter.php
Publish Config file? [y, n]: y
  Created: Config/Auth.php
Publish Language file? [y, n]: y
  Created: Language/en/Auth.php

すべてに y と回答すると、 以下のファイルが作成されます。 必要なファイルをインストールし、変更してください。

app/Config/Auth.php
app/Controllers/AuthController.php
app/Database/Migrations/2017-11-20-223112_create_auth_tables.php
app/Entities/User.php
app/Filters/LoginFilter.php
app/Filters/PermissionFilter.php
app/Filters/RoleFilter.php
app/Language/en/Auth.php
app/Models/LoginModel.php
app/Models/UserModel.php
app/Views/Auth/_footer.php
app/Views/Auth/_header.php
app/Views/Auth/_message_block.php
app/Views/Auth/_navbar.php
app/Views/Auth/emails/activation.php
app/Views/Auth/emails/forgot.php
app/Views/Auth/forgot.php
app/Views/Auth/layout.php
app/Views/Auth/login.php
app/Views/Auth/register.php
app/Views/Auth/reset.php

参考

Tags: codeigniter, codeigniter4, database, auth