FuelPHP 1.7.2のブログチュートリアル②

昨日のブログチュートリアルは単にブログの記事表示や新規作成、削除のみでユーザ認証機能はありませんでした。

今日のチュートリアルではユーザ認証機能のあるページを作成します。

【注意】このチュートリアルはアプリ作成の最短の手順を示したものであり、セキュリティ上必要な設定や機能が省略されています。実際にアプリを運用する場合は、『はじめてのフレームワークとしてのFuelPHP第2版(3) 実践編』などを参考に必要なセキュリティ上の設定や機能をすべて実装されることをお薦めします。

FuelPHP 1.7.2のインストール設定

http://fuelphp.com/の「Download v1.7.2 now!」より、fuelphp-1.7.2.zipをダウンロードし展開します。

config.phpの設定

FuelPHPの設定ファイルfuel/app/config/config.phpを変更し、FuelPHPのORMパッケージとAuthパッケージを使えるようにします。

--- a/fuel/app/config/config.php
+++ b/fuel/app/config/config.php
@@ -258,7 +258,7 @@ return array(
        /**************************************************************************/
        /* Always Load                                                            */
        /**************************************************************************/
-       // 'always_load'  => array(
+       'always_load'  => array(

                /**
                 * These packages are loaded on Fuel's startup.
@@ -271,9 +271,10 @@ return array(
                 *     array('auth'     => PKGPATH.'auth/')
                 * );
                 */
-               // 'packages'  => array(
-               //      //'orm',
-               // ),
+               'packages'  => array(
+                       'orm',
+                       'auth',
+               ),

                /**
                 * These modules are always loaded on Fuel's startup. You can specify them
@@ -309,6 +310,6 @@ return array(
                 * If you don't want the lang in a group use null as groupname.
                 */
                // 'language'  => array(),
-       // ),
+       ),

 );

配列のキーalways_loadpackagesormが有効になるように、コメント記号//を削除し、ormの下にauthを追加します。

auth.phpの設定

Authパッケージの設定ファイルfuel/packages/auth/config/auth.phpをfuel/app/config/にコピーし、driverをOrmauthに変更します。

--- fuel/packages/auth/config/auth.php  2014-07-22 18:23:20.000000000 +0900
+++ fuel/app/config/auth.php    2014-07-23 20:07:04.125328123 +0900
@@ -22,7 +22,7 @@
  */

 return array(
-   'driver' => 'Simpleauth',
+   'driver' => 'Ormauth',
    'verify_multiple_logins' => false,
    'salt' => 'put_your_salt_here',
    'iterations' => 10000,

データベースの準備

MySQL にデータベースを作成します。

> CREATE DATABASE `fuel_blog` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

fuel/app/config/development/db.phpを変更し、FuelPHPからデータベースにアクセスできるようにします。

return array(
    'default' => array(
        'connection'  => array(
            'dsn'        => 'mysql:host=localhost;dbname=fuel_blog',
            'username'   => 'root',
            'password'   => '',
        ),
    ),
);

ブログの作成

FuelPHPのoil generateコマンドによりコードを自動生成します。

$ php oil generate admin post title:varchar[50] body:text

以下のファイルが生成されました。

【注意】生成された全てのコードに目を通し、問題がないか確認することをお薦めします。

fuel/app/classes/controller/admin.php
fuel/app/classes/controller/admin/post.php
fuel/app/classes/controller/base.php
fuel/app/classes/model/post.php
fuel/app/migrations/001_create_posts.php
fuel/app/views/admin/dashboard.php
fuel/app/views/admin/login.php
fuel/app/views/admin/post/_form.php
fuel/app/views/admin/post/create.php
fuel/app/views/admin/post/edit.php
fuel/app/views/admin/post/index.php
fuel/app/views/admin/post/view.php
fuel/app/views/admin/template.php
fuel/app/views/template.php

マイグレーションを実行し、データベースにテーブルを作成します。

$ php oil refine migrate

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

CREATE TABLE IF NOT EXISTS `posts` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(50) NOT NULL,
  `body` text NOT NULL,
  `created_at` int(11) DEFAULT NULL,
  `updated_at` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

マイグレーションで、Authパッケージで必要なテーブルを作成します。

$ php oil refine migrate --packages=auth

Webサーバの起動

PHP 5.4以降のビルトインWebサーバを起動します。

$ php oil server

ブラウザからhttp://localhost:8000/admin/postにアクセスすると、ログインページにリダイレクトされます。

デフォルトの管理者ユーザでログインできます。

  ユーザ名:admin
パスワード:admin

http://localhost:8000/admin/postにアクセスすると、記事の一覧が表示され、記事の追加、削除、編集ができます。

スクリーンショット

次回:FuelPHP 1.7.2のブログチュートリアル③

Tags: fuelphp, database

FuelPHP 1.7.2のブログチュートリアル①

以前のブログチュートリアルが相当古くなっていますので、最新のFuelPHP 1.7.2でのチュートリアルに更新します。

【注意】このチュートリアルはアプリ作成の最短の手順を示したものであり、セキュリティ上必要な設定や機能が省略されています。実際にアプリを運用する場合は、『はじめてのフレームワークとしてのFuelPHP第2版(3) 実践編』などを参考に必要なセキュリティ上の設定や機能をすべて実装されることをお薦めします。

FuelPHP 1.7.2のインストール設定

http://fuelphp.com/の「Download v1.7.2 now!」より、fuelphp-1.7.2.zipをダウンロードし展開します。

FuelPHPの設定ファイルfuel/app/config/config.phpを変更し、FuelPHPのORMパッケージを使えるようにします。

--- a/fuel/app/config/config.php
+++ b/fuel/app/config/config.php
@@ -258,7 +258,7 @@ return array(
        /**************************************************************************/
        /* Always Load                                                            */
        /**************************************************************************/
-       // 'always_load'  => array(
+       'always_load'  => array(

                /**
                 * These packages are loaded on Fuel's startup.
@@ -271,9 +271,9 @@ return array(
                 *     array('auth'     => PKGPATH.'auth/')
                 * );
                 */
-               // 'packages'  => array(
-               //      //'orm',
-               // ),
+               'packages'  => array(
+                       'orm',
+               ),

                /**
                 * These modules are always loaded on Fuel's startup. You can specify them
@@ -309,6 +309,6 @@ return array(
                 * If you don't want the lang in a group use null as groupname.
                 */
                // 'language'  => array(),
-       // ),
+       ),

 );

配列のキーalways_loadpackagesormが有効になるように、コメント記号//を削除します。

データベースの準備

MySQL にデータベースを作成します。

> CREATE DATABASE `fuel_blog` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

fuel/app/config/development/db.phpを変更し、FuelPHPからデータベースにアクセスできるようにします。

return array(
    'default' => array(
        'connection'  => array(
            'dsn'        => 'mysql:host=localhost;dbname=fuel_blog',
            'username'   => 'root',
            'password'   => '',
        ),
    ),
);

ブログの作成

FuelPHPのoil generateコマンドによりコードを自動生成します。

$ php oil generate scaffold post title:varchar[50] body:text

以下のファイルが生成されました。

【注意】生成された全てのコードに目を通し、問題がないか確認することをお薦めします。

fuel/app/classes/controller/post.php
fuel/app/classes/model/post.php
fuel/app/migrations/001_create_posts.php
fuel/app/views/post/_form.php
fuel/app/views/post/create.php
fuel/app/views/post/edit.php
fuel/app/views/post/index.php
fuel/app/views/post/view.php
fuel/app/views/template.php

マイグレーションを実行し、データベースにテーブルを作成します。

$ php oil refine migrate

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

CREATE TABLE IF NOT EXISTS `posts` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(50) NOT NULL,
  `body` text NOT NULL,
  `created_at` int(11) DEFAULT NULL,
  `updated_at` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

Webサーバの起動

PHP 5.4以降のビルトインWebサーバを起動します。

$ php oil server

ブラウザからhttp://localhost:8000/postにアクセスすると、記事の一覧が表示され、記事の追加、削除、編集ができます。

スクリーンショット

次回:FuelPHP 1.7.2のブログチュートリアル②

Tags: fuelphp, database

PHPにおけるオブジェクトインジェクション脆弱性について

SQLインジェクションはかなり有名になりましたが、オブジェクトインジェクションはまだあまり聞かないので、まとめておきます。

Dependency Injection(DI)とは関係ありません。

オブジェクトインジェクション脆弱性とは?

SQLインジェクションが外部からSQL文を注入する攻撃であるのと同じように、オブジェクトインジェクションとは外部からオブジェクトを注入する攻撃です。

外部からオブジェクトを注入できれば、そのオブジェクトの機能によりさまざまな攻撃ができる可能性があります。最悪の場合、任意のコードを実行できる脆弱性になります。

PHPの場合、この攻撃が可能なのは、unserialize()関数を悪用できる場合です。

攻撃の方法

unserialize()関数に外部から任意のデータを渡すコードがあった場合、攻撃者は自由にシリアライズされたデータを送信することで、生成されるオブジェクトをコントロールすることができます。

しかし、自由にオブジェクト(クラス)を作成できるわけではないので、それだけでは任意のコードを実行することはできません。

シリアライズされたオブジェクトをunserialize()するためには、そのオブジェクトのクラスが定義済みかオートロードできる必要があるためです。

また、オブジェクトが生成されても攻撃に使えるメソッドが実行されなければ、実際に攻撃を成功させることはできません。

ただし、一部のマジックメソッドは明示的にコールされていなくても実行されます。unserialize()によるオブジェクトの生成では、__construct()メソッドは実行されませんが、__wakeup()メソッドが実行されます。また、そのオブジェクトの消滅時(PHPの終了時)には__destruct()メソッドが実行されます。また、__toString()メソッドなども場合により実行される可能性があります。

つまり、攻撃者は攻撃に使えるクラスを探し、そのオブジェクトを注入する必要があります。

例えば、以下のような、Example1クラスがある場合、かつ、unserialize($_GET['data'])というコードがあれば、このクラスのオブジェクトを注入することで任意のファイルを削除するという攻撃が可能になります。

class Example1
{
   public $cache_file;

   function __construct()
   {
      // some PHP code...
   }

   function __destruct()
   {
      $file = "/var/www/cache/tmp/{$this->cache_file}";
      if (file_exists($file)) @unlink($file);
   }
}

https://www.owasp.org/index.php/PHP_Object_Injection

脆弱性の事例

JVN#69986880 OpenPNE において任意の PHP コードが実行される脆弱性

ログイン画面にて利用可能な「次回から自動ログイン」機能にこの脆弱性があった事例です。

Cookieの値をそのままunserialize()してしまっています。

 /**
  * get remember login cookie
  *
  * @return array
  */
  protected function getRememberLoginCookie()
  {
    $key = md5(sfContext::getInstance()->getRequest()->getHost());
    if ($value = sfContext::getInstance()->getRequest()->getCookie($key))
    {
      $value = unserialize(base64_decode($value));

      return $value;
    }
  }

https://redmine.openpne.jp/projects/op3/repository/revisions/8fbdf778cd6133103bbf08a261928893a8c2eb0d/entry/lib/user/opSecurityUser.class.php#L150

JVN#94791545 FuelPHP において任意のコードが実行される脆弱性

HTTPクライアント機能を提供するRequest_Curlクラスでauto_formatが有効な場合(FuelPHP 1.7.1までのデフォルト)、取得したページのMIMEタイプに従って自動的にデータ変換処理が実行されるため、シリアライズされたデータの場合、自動的にunserialize()されるため攻撃可能であるという脆弱性です。

    // do we have auto formatting enabled and can we format this mime type?
    if ($this->auto_format and array_key_exists($mime, static::$auto_detect_formats))
    {
        $body = \Format::forge($body, static::$auto_detect_formats[$mime])->to_array();
    }

https://github.com/fuel/core/blob/1.8/develop/classes/request/driver.php#L360

    private function _from_serialize($string)
    {
        return unserialize(trim($string));
    }

https://github.com/fuel/core/blob/1.8/develop/classes/format.php#L530

そもそもRequest_Curlクラスを使っているユーザがかなり少ない上に、FuelPHPから攻撃者のサイトにHTTPリクエストさせる必要があるため、この脆弱性の影響を受けるユーザは非常に少ないと考えられます。ただし、このクラスでWeb APIにアクセスしており、そのAPI提供元サイトがクラックされた場合は、この攻撃が可能になります。

また、auto_formatを有効にした場合は、FuelPHP 1.7.2以降もこの脆弱性が存在しますので相応の対策をする必要があります。

私の調べた範囲では、FuelPHP自体にはこの脆弱性により任意のコードを実行できるようなクラスはありませんでした(ただし、ファイルを削除することは可能でした。また、ユーザの作成したクラスに脆弱なものが存在する可能性もあります)。

次のCakePHPの事例のように複雑な組み合わせで攻撃できる可能性があるため、ちょっと見ただけで問題ないとは簡単には言えません。もし、FuelPHPのクラスで任意のコードを実行する方法を発見された方がいましたら、お教え願いたいです。

JVNDB-2011-003903 CakePHP の _validatePost 関数における内部 Cake キャッシュを変更される脆弱性

CakePHPのSecurityComponentにこの脆弱性があった事例です。実際に任意のコードが実行できたようで非常にうまく攻撃可能になった事例です。

かなり複雑な事例です。解説は以下のブログが詳しいです。

その他の事例

(16:01 追記)

調べると、去年から今年にかけていろいろと報告されています。

対策

攻撃手法は複雑になりがちですが、対策は割と簡単で、unserialize()関数に外部からの入力を渡さないことです。

serialize()がどうしても必要な場合以外はserialize()を使わないというのもいいと思います。serialize()json_encode()に変更できないか検討してみましょう。

関連

参考

Tags: php, security, fuelphp, cakephp