本当は危ないCodeIgniter4の自動ルーティング
CodeIgniter Advent Calendar 2021
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の指定には必ず最後に*
を含める
Discussion