PHPの恐怖仕様
冒頭で宣言しますが、今回のネタはあくまでPHPの仕様に対する実験であり、実際に使っちゃうと脆弱性を盛り込んでしまうネタなので、充分ご注意ください。
PHPの日本語変数名と文字コード – FLAMA技術Blog
と同じことを宣言しておく。
さて、まずは「EUC-JP(またはUTF-8)で保存された」以下の2つのコードを見て、どういう結果になるかを想像してみて欲しい。(え!?先頭の2つのini_setに作為的なものを感じる?まぁそれは後で分かります)
- test1.php
<?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'));
- test2.php
<?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