ci-phpunit-test v0.9.0をリリースしました

昨日(2015/11/18)、ci-phpunit-test v0.9.0をリリースしました。v1.0.0までもう少しです。

ci-phpunit-testとは?

CodeIgniter 3.0でPHPUnitをより簡単に使うためのブリッジツールです。

CodeIgniterには、Unit Testingクラスというのが標準で含まれているのですが、低機能すぎるという問題があり、あまりお薦めできません。

また、CodeIgniter 3.0からCodeIgniter本体のテストはPHPUnitで書かれており、CodeIgniter 4.0ではアプリのテストもPHPUnitになる予定です。

ということで、CodeIgniter 3.0でもPHPUnitでテストを楽に書きたいという人にお薦めのツールがこのci-phpunit-testになります。

私が知る限り日本にすでに7人のユーザがおり、実績も充分です(笑)

機能

現状の機能などをまとめてみました。

  • アプリケーションクラス(モデル、ライブラリやコントローラ)のオートローダ
  • コントローラの機能テスト
    • コントローラクラスの生成時にモックを注入可能
    • redirect()show_404()show_error()を簡単にテストできる
    • RESTコントローラを簡単にテストできる
  • モンキーパッチ機能(オプション)
    • exit()およびdie()を例外に変換
    • グローバル関数の動的な書き換え(制限あり)
    • ユーザ定義クラスのメソッドの書き換え(オブジェクトメソッドや静的メソッドも書き換え可能)
  • ヘルパーメソッド(ショートカットのためのメソッド)
    • PHPUnit Mockオブジェクトの生成とメソッド実行の検証を助けるメソッド
    • ReflectionHelper ... プライベートメソッド/プロパティへの簡単なアクセス
  • サンプルとして
    • データベースシーディングライブラリ
    • テストのためのSessionクラス(MY_Session.php)

いざまとめると、あまり大したことない感じですが、実際にテストを書くとその便利さに驚くはずです。たぶん。

使い方

以下の記事およびその中の「もっと知るには?」にあるリンク先をご覧下さい。

参考

Tags: codeigniter, phpunit, testing, release

本当は相性がいいNetBeansとCodeIgniter

最近、CodeIgniterとPhpStormというブログ記事を見かけますが、実は、NetBeansとCodeIgniterの組み合わせもかなりイケてます。

CodeIgniterプラグインのインストール

NetBeansにはCodeIgniterプラグインがあります。最新のNetBeans 8.1用のプラグインがすでにリリースされています。最新版はバージョン0.5.1です。

まず、上記より

  • org-nbphpcouncil-modules-php-ci-0.5.1.nbm
  • org-nbphpcouncil-modules-php-ci-repository-0.5.1.nbm

をダウンロードします。

そして「ツール」→「プラグイン」メニューを選択します。

「プラグイン」ウィンドウが表示されますので、「ダウンロード済み」タブから「プラグインの追加」ボタンを押し、ダウンロードしたプラグインファイルを選択し、下の「インストール」ボタンを押してインストールします。

CodeIgniterプラグインの設定

CodeIgniterプラグインをインストールすると、プロジェクトの「プロパティ」の「フレームワーク」に「CodeIgniter」が追加されます。

左の「プロジェクト」ウィンドウから、プロジェクト名を右クリックし、「プロパティ」メニューを選択します。

  1. 「Enabled」にチェックを入れます。
  2. 「Custom Library Paths」に追加で自動補完したいライブラリなどがあるフォルダを追加します。
  3. 「Version」を選択します。

そして「OK」ボタンを押します。

自動補完ファイルの生成

プラグインの設定が完了したら、左の「プロジェクト」ウィンドウから、プロジェクト名を右クリックし、「CodeIgniter」→「Generate Auto Composer File」を選択します。

これで自動補完用のファイルが、nbproject/autocomplete/__ci_auto_complete__.phpに作成されました。

自動補完

コントローラ内で$this->confと打つと、CI_Confgが候補として表示されています。

CodeIgniterに標準では存在しない$this->twigも補完できます。「Custom Library Paths」にapplicaton/librariesを追加したからです。

$this->と打つのが面倒くさい

NetBeansの「コード・テンプレート」を使いましょう。

「NetBeans」→「Preferences」メニューを選択します。「オプション」ウィンドウが表示されるので、「エディタ」から「コード・テンプレート」を選択します。

まず、「言語」に「PHP」を選択します。

そして「新規」ボタンを押し、「省略名」にtを「展開されるテキスト」に$this->を入力します。

「OK」ボタンを押して完了します。

これで、PHPエディタでtを押した後に[tab]キーを押せば、$this->に展開されます。

Tags: netbeans, codeigniter

PHPにおけるHostヘッダインジェクション攻撃が可能な脆弱性

Googleで「Hostヘッダインジェクション」を検索しても「HTTPヘッダインジェクション」しか出てこないので、この記事を書くことにしました。

ちなみに、「Hostヘッダインジェクション」自体はPHPに固有というわけではなく、あらゆる環境で起こり得ます。

Hostヘッダインジェクションとは?

HTTPリクエストの「Hostヘッダ」の値を攻撃者が操作する攻撃です。

例えば、以下のようなコードがあった場合、$_SERVER['HTTP_HOST']の値を操作できれば、リンク先を自由に変更できます。

<a href="http://<?php echo $_SERVER['HTTP_HOST']; ?>/?token=secret">

例えば、PHPビルトインWebサーバの場合ですが、telnetしてアクセスしてみましょう。

$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1         <-- ここからリクエストヘッダ
Host: evil.example.jp  <-- ここで不正な値を注入

HTTP/1.1 200 OK        <-- ここからレスポンスヘッダ
Host: evil.example.jp
Connection: close
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Content-type: text/html

<a href="http://evil.example.jp/?token=secret">

Connection closed by foreign host.

HTTPリクエストでのHostヘッダを「Host: evil.example.jp」と指定していますので、ホスト名は「evil.example.jp」になっており、その値がそのまま出力されることがわかります。

つまり、このような環境でサーバのURLをHostヘッダの値から生成している場合は、この脆弱性になります。

$_SERVER['HTTP_HOST']と$_SERVER['SERVER_NAME']の仕様

$_SERVER['HTTP_HOST']がダメなら$_SERVER['SERVER_NAME']を使えばいいじゃないと思うかも知れませんので、そちらも検証しておきます。

test.php

<?php
echo $_SERVER['HTTP_HOST']."\n", $_SERVER['SERVER_NAME']."\n";

上記のファイルをApacheのドキュメントルートに置きました。

$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /test.php HTTP/1.1
Host: evil.example.jp

HTTP/1.1 200 OK
Date: Fri, 06 Nov 2015 00:42:26 GMT
Server: Apache/2.4.12 (Unix) OpenSSL/1.0.1m PHP/5.5.24 mod_perl/2.0.8-dev Perl/v5.16.3
X-Powered-By: PHP/5.5.24
Content-Length: 31
Content-Type: text/html

evil.example.jp
evil.example.jp
Connection closed by foreign host.

$_SERVER['HTTP_HOST']$_SERVER['SERVER_NAME']も同じ値を返しました。

タグを含む場合はどうでしょう?

$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /test.php HTTP/1.1
Host: <s>evil

HTTP/1.1 200 OK
Date: Fri, 06 Nov 2015 01:02:19 GMT
Server: Apache/2.4.12 (Unix) OpenSSL/1.0.1m PHP/5.5.24 mod_perl/2.0.8-dev Perl/v5.16.3
X-Powered-By: PHP/5.5.24
Content-Length: 21
Content-Type: text/html

<s>evil
&lt;s&gt;evil
Connection closed by foreign host.

$_SERVER['SERVER_NAME']は何故かHTMLエスケープされています。

また、Apacheの返すSERVER_NAMEUseCanonicalNameの設定によることが知られています。

#
# UseCanonicalName: Determines how Apache constructs self-referencing
# URLs and the SERVER_NAME and SERVER_PORT variables.
# When set "Off", Apache will use the Hostname and Port supplied
# by the client.  When set "On", Apache will use the value of the
# ServerName directive.
#
UseCanonicalName Off

UseCanonicalNameOffの場合は、HTTPクライアントから提供された値が使われます。

Onの場合は、Apacheの設定ファイルのServerNameの設定値が使われます。

つまり、この値は環境に依存して変わってしまいますので、あまり信用しない方がよいでしょう。というのは、サーバ環境が変わると脆弱になってしまうアプリというはやはり避けた方が安全だからです。

「失敗する可能性があるものは失敗する」というマーフィーの法則が思い出されます。

CodeIgniter 3.0.3における脆弱性修正

CodeIgniterは最新の3.0.3で脆弱性修正として以下の修正をコミットしています。

diff --git a/application/config/config.php b/application/config/config.php
index 479d591..4f8f814 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -11,10 +11,16 @@ defined('BASEPATH') OR exit('No direct script access allowed');
 |
 |      http://example.com/
 |
-| If this is not set then CodeIgniter will try guess the protocol, domain
-| and path to your installation. However, you should always configure this
-| explicitly and never rely on auto-guessing, especially in production
-| environments.
+| WARNING: You MUST set this value!
+|
+| If it is not set, then CodeIgniter will try guess the protocol and path
+| your installation, but due to security concerns the hostname will be set
+| to $_SERVER['SERVER_ADDR'] if available, or localhost otherwise.
+| The auto-detection mechanism exists only for convenience during
+| development and MUST NOT be used in production!
+|
+| If you need to allow multiple domains, remember that this file is still
+| a PHP script and you can easily do that on your own.
 |
 */
 $config['base_url'] = '';
diff --git a/system/core/Config.php b/system/core/Config.php
index feea7c8..0264776 100644
--- a/system/core/Config.php
+++ b/system/core/Config.php
@@ -88,11 +88,9 @@ class CI_Config {
                // Set the base_url automatically if none was provided
                if (empty($this->config['base_url']))
                {
-                       // The regular expression is only a basic validation for a valid "Host" header.
-                       // It's not exhaustive, only checks for valid characters.
-                       if (isset($_SERVER['HTTP_HOST']) && preg_match('/^((\[[0-9a-f:]+\])|(\d{1,3}(\.\d{1,3}){3})|[a-z0-9\-\.]+)(:\d+)?$/i', $_SERVER['HTTP_HOST']))
+                       if (isset($_SERVER['SERVER_ADDR']))
                        {
-                               $base_url = (is_https() ? 'https' : 'http').'://'.$_SERVER['HTTP_HOST']
+                               $base_url = (is_https() ? 'https' : 'http').'://'.$_SERVER['SERVER_ADDR']
                                        .substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME'])));
                        }
                        else

$_SERVER['HTTP_HOST']を使っていたものを$_SERVER['SERVER_ADDR']に変更しています。

もともと自動判定は使うべきでないと警告していたわけですが、今回、$_SERVER['HTTP_HOST']を使ったサーバのURLの自動判定は脆弱性と判断し廃止しました。

結論

結論としては、「未検証の$_SERVER['HTTP_HOST']$_SERVER['SERVER_NAME']は使ってはいけない」ということになります。

そのまま出力した場合は、Hostヘッダインジェクションによる攻撃が可能になる場合があります。

対策は、アプリの設定としてサーバのホスト名を手動で設定するか、ホワイトリストで検証することです。例えば、以下のサンプルがCodeIgniterのユーザガイドでは示されています。

$allowed_domains = array('domain1.tld', 'domain2.tld');
$default_domain  = 'domain1.tld';

if (in_array($_SERVER['HTTP_HOST'], $allowed_domains, TRUE))
{
        $domain = $_SERVER['HTTP_HOST'];
}
else
{
        $domain = $default_domain;
}

if ( ! empty($_SERVER['HTTPS']))
{
        $config['base_url'] = 'https://'.$domain;
}
else
{
        $config['base_url'] = 'http://'.$domain;
}

http://www.codeigniter.com/user_guide/installation/upgrade_303.html より。

アプリのコードにホスト名をハードコードしたくない場合は、独自に環境変数を定義してそこから取得するようにするという方法も考えられます。

なお、Hostヘッダインジェクションを使った攻撃シナリオとしては、HTTPキャッシュを汚染する方法やメールに記載するURLを変更する攻撃シナリオが考えられています。

この脆弱性を利用して実際に攻撃するのはそんなに簡単ではないように思いますが、よい攻撃シナリオを思いついた人はシェアしてください!

(2015-11-10 追記)

過去に実際にあった事例として、以下がありました。

(2015-11-11 追記)

なお、Internet ExplorerにはHostヘッダを書き換えることができる脆弱性が存在するようです。

IEでは罠ページ上で302などでリダイレクトしつつLocationヘッダに%2Fなどを含めると、それらをデコードした値をHostヘッダとして送信するということで、これらを利用するとHostヘッダをHTML上にそのまま出力しているサイトではXSSが可能ということになります。
Host:リクエストヘッダによるXSS - 葉っぱ日記 より)

まとめ

  • 未検証の$_SERVER['HTTP_HOST']$_SERVER['SERVER_NAME']は使ってはいけません。
  • 使えばHostヘッダインジェクションによる攻撃が可能になる場合があります。

参考

Tags: http, security, php, codeigniter