Thanks Driven Life

日々是感謝

PHP における register_globals と $_FILES の関係について

2014 年も残すところあと3ヶ月となった今日この頃、みなさま元気に PHP 触ってますか。 今年8月には PHP 5.6 がリリース されている中、 なぜ PHP 5.4 で互換が切られた register_globals について語らないといけないかとかいろいろありますが、 いろいろあったので気になるところを調べてみました。

1. 先に結論

register_globals = On の時は $_FILES が持つ値もグローバル変数に展開される。つまり

$_FILES['foo']['tmp_name'] => $foo
$_FILES['foo']['name'] => $foo_name
$_FILES['foo']['type'] => $foo_type
$_FILES['foo']['size'] => $foo_size

こうなる

2. 経緯

  • いろいろあって PHP 5.3 使ってる
  • いろいろあって register_globalsOn にして使っている
  • いろいろあってがんばってる

3. 検証

例えばこんなページがあるとする

<html>
  <body>
    <pre>
      <?php var_dump($_FILES, array_keys($GLOBALS)); ?>
    </pre>
    <form action="index.php" method="POST" enctype="multipart/form-data">
      <input type="file" name="foo" />
      <input type="submit" />
    </form>
  </body>
</html>

このページへの初回アクセス時の画面にはこのように表示されているはず(form 系の部品は省略)

array(0) {
}
array(34) {
  [0]=>
  string(7) "GLOBALS"
  [1]=>
  string(9) "HTTP_HOST"
  [2]=>
  string(15) "HTTP_USER_AGENT"
  [3]=>
  string(11) "HTTP_ACCEPT"
  [4]=>
  string(20) "HTTP_ACCEPT_LANGUAGE"
  [5]=>
  string(20) "HTTP_ACCEPT_ENCODING"
  [6]=>

  (snip...)

  string(11) "REQUEST_URI"
  [24]=>
  string(11) "SCRIPT_NAME"
  [25]=>
  string(8) "PHP_SELF"
  [26]=>
  string(12) "REQUEST_TIME"
  [27]=>
  string(5) "_POST"
  [28]=>
  string(4) "_GET"
  [29]=>
  string(7) "_COOKIE"
  [30]=>
  string(7) "_SERVER"
  [31]=>
  string(4) "_ENV"
  [32]=>
  string(6) "_FILES"
  [33]=>
  string(8) "_REQUEST"
}

$GLOBALS にずらずら書かれてるのはまさに register_globals の効能ですね。みなさん大好きだと思います。

register_globalsをonとした場合、この機能により、HTMLフォームから投 稿される変数と同時に、あらゆる種類の変数がスクリプトに注入される ことになります。これは、PHPにおいては変数の初期化が不要であるということにも関係し、安全でないコードを書くことが極めて容易になるということを意味します。

PHP: グローバル変数の登録機能の使用法 - Manual

で、ここでてきとうにファイルをアップロードします。すると

array(1) {
  ["foo"]=>
  array(5) {
    ["name"]=>
    string(12) "IMG_2236.JPG"
    ["type"]=>
    string(10) "image/jpeg"
    ["tmp_name"]=>
    string(14) "/tmp/phplWW25a"
    ["error"]=>
    int(0)
    ["size"]=>
    int(1386839)
  }
}
array(40) {
  [0]=>
  string(7) "GLOBALS"
  [1]=>
  string(8) "foo_name"
  [2]=>
  string(8) "foo_type"
  [3]=>
  string(3) "foo"
  [4]=>
  string(8) "foo_size"
  [5]=>
  string(9) "HTTP_HOST"
  [6]=>

  (snip...)

  [39]=>
  string(8) "_REQUEST"
}

こうなります。$_FILES の部分は予想通りだとは思いますが、問題は $GLOBALS の方。

  [1]=>
  string(8) "foo_name"
  [2]=>
  string(8) "foo_type"
  [3]=>
  string(3) "foo"
  [4]=>
  string(8) "foo_size"

これです。お前らどこから出てきたんや…というと冒頭で述べたとおり $_FILES が持つキーと値がグローバル変数に割り当てられた、ということです。

<?php
var_dump($GLOBALS['foo'],$GLOBALS['foo_name'], $GLOBALS['foo_type'], $GLOBALS['foo_size']);
// string(14) "/tmp/phplWW25a"
// string(12) "IMG_2236.JPG"
// string(10) "image/jpeg"
// int(1386839)

これらは register_globals Off にすると消えます。

array(1) {
  ["foo"]=>
  array(5) {
    ["name"]=>
    string(12) "IMG_2236.JPG"
    ["type"]=>
    string(10) "image/jpeg"
    ["tmp_name"]=>
    string(14) "/tmp/phphqtnJx"
    ["error"]=>
    int(0)
    ["size"]=>
    int(1386839)
  }
}
array(8) {
  [0]=>
  string(7) "GLOBALS"
  [1]=>
  string(5) "_POST"
  [2]=>
  string(4) "_GET"
  [3]=>
  string(7) "_COOKIE"
  [4]=>
  string(6) "_FILES"
  [5]=>
  string(4) "_ENV"
  [6]=>
  string(8) "_REQUEST"
  [7]=>
  string(7) "_SERVER"
}

すっきりした

4. どうしてこうなった

register_globals = On にしていると、$_FILES のそれぞれの要素がグローバル変数になる っぽい のはわかりましたが、本当に register_globals の所為なのか?ってのが疑問でした。php.net の register_globals の項目にも $_FILES の項目にも載っていないので。(もしかしたら既に互換切られた機能なので削除されただけかもしれません)

しょうがないのでソースコードを読んだところ、それらしい箇所を発見しました。

php-src/rfc1867.c at php-5.3.29 · php/php-src · GitHub

ここらへんです。

ちゃんと追えてないのでここからは予想なんですが

$foo_key に該当する変数を登録する時

safe_php_register_variable_ex() に渡して、グローバル変数として登録するかどうかは register_globals に委ねてる

辿っていくと php_register_variable_ex() に到着してて、この関数の中で register_globals っぽい値をチェックしつつ何かしら実行するしないを判断しているっぽいのまではわかった

$_FILES['foo'][key] に該当する変数を登録する時

safe_php_register_variable_ex() に渡す前に一度 register_http_post_files_variable_ex() を経由しているので、register_globals の値がなんであれ グローバル変数として登録しないようにしている (ちょっと怪しい…)

っぽいことがわかりました。多分。

5. まとめ

register_globals $_FILES」とかでググったりすると

「ヘイ、register_globals Off にしたら $file が使えなくなったぜどういうことだ」
「おいおいそこは $_FILES['file']['tmp_name'] を素直に呼ぶこったなベイビー」

みたいなやりとりをする掲示板とかも見られました。

要するに register_globals 使ってる場合じゃないんですが、 もし万が一今回のケースのように register_globals から脱却を狙っているものの ファイルアップロードが係わってる場所なにかしら挙動がおかしいとか 「この変数まじどっからきたんや」みたいに悩むようなことがあれば、 その変数名と同じ名前を持つ <input type="file" ..> がないか、とかいろいろ調べてみるのも良いかもしれません。

追記

http://jp1.php.net/extract#refsect1-function.extract-notes

register_globals が on の状態で $_FILES に対して extract() を実行して EXTR_SKIP を指定すると、 その結果に驚くことでしょう。

「その結果に驚くことでしょう。 」じゃねーよ、みたいなツッコミいれたくなる php.net さん大好きです