HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

PHPの恐怖仕様

冒頭で宣言しますが、今回のネタはあくまでPHPの仕様に対する実験であり、実際に使っちゃうと脆弱性を盛り込んでしまうネタなので、充分ご注意ください。

PHPの日本語変数名と文字コード – FLAMA技術Blog

と同じことを宣言しておく。


さて、まずは「EUC-JP(またはUTF-8)で保存された」以下の2つのコードを見て、どういう結果になるかを想像してみて欲しい。(え!?先頭の2つのini_setに作為的なものを感じる?まぁそれは後で分かります)

<?php
ini_set('display_errors', 'off');
ini_set('error_reporting', 'off');

class Str {
    private $str = null;

    /**
     * 指定された文字列を保持するStrインスタンスの生成
     *
     * @param string $str
     * @return Str Strインスタンス
     */
    public function __construct( $str ) {
        $this->str = $str;
    }
    public function equals( $otherStr ) {
        if (  $otherStr == $this->str  ) { // XXX
            return true;
        } else {
            return false;
        }
    }
}

$strA = new Str('a');

var_dump($strA->equals('a'));
var_dump($strA->equals('b'));
<?php
ini_set('display_errors', 'off');
ini_set('error_reporting', 'off');

class Str {
    private $str = null;

    /**
     * 指定された文字列を保持するStrインスタンスの生成
     *
     * @param string $str
     * @return Str Strインスタンス
     */
    public function __construct( $str ) {
        $this->str = $str;
    }
    public function equals( $otherStr ) {
        if (  $otherStr == $this->str ) { // XXX
            return true;
        } else {
            return false;
        }
    }
}

$strA = new Str('a');

var_dump($strA->equals('a'));
var_dump($strA->equals('b'));

素直に読むと、どちらも以下のような結果になると想像できる。

bool(true)
bool(false)

ところが、実際には以下のようになる。

$ php test1.php
bool(true)
bool(false)
$ php test2.php
bool(false)
bool(false)
$ 

見た目は違いがないソースなので、なんだろうと思うだろうが、こういうときは基本に立ち戻り、diffコマンドで比較してみる。

$ diff test1.php test2.php | iconv -f EUC-JP -t CP932
18c18
<               if (  $otherStr == $this->str  ) { // XXX
---
>               if (  $otherStr == $this->str ) { // XXX

まぁ予想通りだろうが、if文の所に何か罠があるらしい。で、何が違うのかを、今度はodコマンドで見てみる。

$ grep XXX test1.php | od -c
0000000  \t  \t   i   f       (           $   o   t   h   e   r   S   t
0000020   r       =   =       $   t   h   i   s   -   >   s   t   r
0000040       )       {       /   /       X   X   X  \n
0000054
$ grep XXX test2.php | od -c
0000000  \t  \t   i   f       (           $   o   t   h   e   r   S   t
0000020   r       =   =       $   t   h   i   s   -   >   s   t   r 241
0000040 241   )       {       /   /       X   X   X  \n
0000054
$ 

test2.phpのほうの「$this->str」の後に何かがある。8進数で241なので、16進数だと0xa1。EUC-JPで「\xa1\xa1」は、いわゆる全角スペース。なので、パッと見は分からない。

ちなみに、なぜ「EUC-JP(またはUTF-8)」かというと、Shift_JISだと「"\x81\x40"」、分かりやすく書くと「"\x81@"」。なので、'@'の部分で構文エラーになる。

Parse error: syntax error, unexpected '@' in /home/hhelibex/test2.php on line 18

一方、EUC-JPの場合は「\xa1\xa1」、UTF-8の場合は「"\xe3\x80\x80」なので、変数名の一部として有効らしい。
冒頭に書いたサイトでも触れているが、

有効な変数名は文字またはアンダースコアから始まり、任意の数の文字、 数字、アンダースコアが続きます。正規表現によれば、これは次の ように表現することができます。 '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'

PHP: 基本的な事 - Manual

これは絶対に失敗仕様だと思うのだが。全角スペースなんか使われた日には、冒頭のサイトで言っている脆弱性うんぬん以前に、他のプログラマに対する危険な落とし穴ですよ、奥さん(誰(謎))。
また、あくまでもバイトデータとして扱うということなので、以下のようなコードが混在したら完全にアウトなわけだ。

<?php
class Hoge {
    public $varほげ = 'hogehoge';
}
<?php
require_once('./hoge.php');

$hoge = new Hoge();
var_dump($hoge->varほげ);

以下のようなNoticeとともに、ちゃんと仕様どおりに「NULL」が出力される(出るようにしていれば‥その上でちゃんとログをチェックすれば)。

Notice: Undefined property: Hoge::$varほげ in /home/hhelibex/hoge1.php on line 5

ちなみに、作為的に入れていた2つのini_setもこのようなNoticeを実験用に隠すため。

Notice: Undefined property: Str::$str  in /home/hhelibex/test2.php on line 18


ちなみに、この全角スペースの話‥‥実話です‥orz