A Day in Serenity (Reloaded)

【改訂版】本当は危ないCodeIgniter4の自動ルーティング

【警告】 自動ルーティングは大変危険なので無効に設定を変更するか、CodeIgniter 4.2から追加された新しい「自動ルーティング(改善)」を使うことを強く推奨します。 【警告】

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

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

この分離が新たなリスクを生み出しました。

今日は、この自動ルーティングの危険性について再度解説します。

動作確認環境

  • CodeIgniter 4.2.0-dev (f801829)
  • PHP 8.0.15
  • 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

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

フィルター設定の確認

CodeIgniter 4.2.0 のリリースにはまだしばらく時間がかかると思いますが、4.2.0 から、spark routes コマンドで全てのルートとフィルターを確認できるようになります。

spark routes コマンドを実行して確認してみましょう。

$ php spark routes
CodeIgniter v4.1.8 Command Line Tool - Server Time: 2022年02月07日 20:27:59 UTC+09:00
+--------+------------------+------------------------------------------+----------------+---------------+
| Method | Route | Handler | Before Filters | After Filters |
+--------+------------------+------------------------------------------+----------------+---------------+
| GET | / | \App\Controllers\Home::index | | toolbar |
| CLI | ci(.*) | \CodeIgniter\CLI\CommandRunner::index/1ドル | | |
| auto | blog | \App\Controllers\Blog::index | auth | toolbar |
| auto | blog/index[/...] | \App\Controllers\Blog::index | | toolbar |
| auto | / | \App\Controllers\Home::index | | toolbar |
| auto | home | \App\Controllers\Home::index | | toolbar |
| auto | home/index[/...] | \App\Controllers\Home::index | | toolbar |
+--------+------------------+------------------------------------------+----------------+---------------+

Method auto は自動ルーティングのルートです。

これで一目瞭然ですが、auth フィルターが適用されるルートは blog だけです。blog/index[/...] にはフィルターが適用されません。

正しいフィルター設定

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

app/Config/Filters.php

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

または、

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

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

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

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

正しいフィルター設定をした後にルートを確認しておきましょう。

$ php spark routes
CodeIgniter v4.1.8 Command Line Tool - Server Time: 2022年02月07日 20:33:30 UTC+09:00
+--------+------------------+------------------------------------------+----------------+---------------+
| Method | Route | Handler | Before Filters | After Filters |
+--------+------------------+------------------------------------------+----------------+---------------+
| GET | / | \App\Controllers\Home::index | | toolbar |
| CLI | ci(.*) | \CodeIgniter\CLI\CommandRunner::index/1ドル | | |
| auto | blog | \App\Controllers\Blog::index | auth | toolbar |
| auto | blog/index[/...] | \App\Controllers\Blog::index | auth | toolbar |
| auto | / | \App\Controllers\Home::index | | toolbar |
| auto | home | \App\Controllers\Home::index | | toolbar |
| auto | home/index[/...] | \App\Controllers\Home::index | | toolbar |
+--------+------------------+------------------------------------------+----------------+---------------+

blogblog/index[/...] 共に auth フィルターが適用されるようになっています。

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

フィルター設定の確認

spark routes コマンドを実行して確認してみましょう。

$ php spark routes
CodeIgniter v4.1.8 Command Line Tool - Server Time: 2022年02月07日 20:37:49 UTC+09:00
+--------+------------------+------------------------------------------+----------------+---------------+
| Method | Route | Handler | Before Filters | After Filters |
+--------+------------------+------------------------------------------+----------------+---------------+
| GET | / | \App\Controllers\Home::index | | toolbar |
| GET | blog | \App\Controllers\Blog::index | auth | auth toolbar |
| CLI | ci(.*) | \CodeIgniter\CLI\CommandRunner::index/1ドル | | |
| auto | blog | \App\Controllers\Blog::index | | toolbar |
| auto | blog/index[/...] | \App\Controllers\Blog::index | | toolbar |
| auto | / | \App\Controllers\Home::index | | toolbar |
| auto | home | \App\Controllers\Home::index | | toolbar |
| auto | home/index[/...] | \App\Controllers\Home::index | | toolbar |
+--------+------------------+------------------------------------------+----------------+---------------+

autoblogblog/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 へのルートしかなくなり、このルートにフィルターが指定されます。

$ php spark routes
CodeIgniter v4.1.8 Command Line Tool - Server Time: 2022年02月07日 20:44:51 UTC+09:00
+--------+--------+------------------------------------------+----------------+---------------+
| Method | Route | Handler | Before Filters | After Filters |
+--------+--------+------------------------------------------+----------------+---------------+
| GET | / | \App\Controllers\Home::index | | toolbar |
| GET | blog | \App\Controllers\Blog::index | auth | auth toolbar |
| CLI | ci(.*) | \CodeIgniter\CLI\CommandRunner::index/1ドル | | |
+--------+--------+------------------------------------------+----------------+---------------+

PHP8のアトリビュートでのルート設定

手動でルートを設定する場合、設定ファイルとコントローラが別々のため、ルートを設定したかどうかわかりづらいです。

CodeIgniter4 Attribute Routes を使うと、以下のようにコントローラにPHP8のアトリビュートを記載することで、ルートを設定することが可能です。

use Kenjis\CI4\AttributeRoutes\Route;
class SomeController extends BaseController
{
 #[Route('path', methods: ['get'])]
 public function index()
 {
 ...
 }
}

具体的な使用例は 『CodeIgniter徹底入門』のサンプルアプリケーション (CodeIgniter v4.1版) を参照してください。

まとめ

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

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

また、

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

参考

Date: 2022年02月08日

Tags: codeigniter, codeigniter4, security

A Day in Serenity (Reloaded) PHP, CodeIgniter, FuelPHP, Linux or something

Links

著書

[フレーム] [フレーム] [フレーム] [フレーム]

はじめてのフレームワークとしてのFuelPHP第2版(1) 環境構築編 はじめてのフレームワークとしてのFuelPHP第2版(2) 入門編

はじめてのフレームワークとしてのFuelPHP第2版(3) 実践編 徹底攻略PHP5技術者認定[上級]試験問題集 [PJ0-200]対応

FuelPHPに関する無料電子書籍

FuelPHP Advent Calendar 2013 FuelPHP Advent Calendar 2012

AltStyle によって変換されたページ (->オリジナル) /