ファイルのアップロードを制限する仕事で使える魔法のLAMP(40)

今回は、HTTPクライアントがアップロードしてくるファイルの扱いについて解説します。受け付けるファイルのサイズ制限や、受け取ったファイルを置くディレクトリなど、注意しなければならないことが意外に多くあります(編集部)

» 2012年01月30日 00時00分 公開
[山口晴広株式会社イメージズ・アンド・ワーズ]

POSTデータの最大サイズを制限する

 第37回から、PHPの実行時設定の中でも、初期設定のまま放置しない方が良い項目を紹介し、それぞれの設定変更法を解説しています。第38回(前々回)第39回(前回)で、HTTPクライアントが送信してくるリクエストデータに関係する設定項目について解説してきました。今回は、残った数少ない設定項目を片付け、リクエストデータの扱いに関する解説は今回でおしまいとします。

 今回最初に取り上げるのは「post_max_size」ディレクティブです。このディレクティブは、POSTメソッドで受け取るリクエストデータの最大サイズを指定するものです。WebブラウザなどのHTTPクライアントからデータを送信する方法には、GETメソッドPOSTメソッドの2つがありますが、これはPOSTメソッドに関係するものです。GETメソッドは、送信したい値をURLに付け加えて送るので、もともと大きな値は送信できません。データ量を制限する設定項目もありません。

 前回も解説した通り、PHPではPOSTメソッドおよびGETメソッドでリクエストデータを受信すると、自動的にそのデータを配列変数に格納します。変数を保存する場所はメモリ空間です。巨大なデータを受信すると、それだけメモリを消費します。これは入力フォームを1つも作らなかったページにも起こりうることです。POSTで大きなデータを送り付けるだけでメモリを消費させることができます。

 従って、「post_max_size」の設定値はあまり大きくするべきではありません。初期設定値は8Mbytesですが、ファイルを送信するならともかく、フォームの入力データを送信するようなアプリケーションで、このような大きなサイズのPOSTデータを送信することはまずないでしょう。もっと小さな値にしてもよいと思います。もちろん、大きなファイルを送信することを前提としたアプリケーションでは、設定値を大きくすることも考えるべきでしょう。

 結局のところ、第37回で紹介した「memory_limit」と同様に、アプリケーションの処理内容次第となります。ここでは普通にフォームのデータを送信する場合だけを想定し、64Kbytesとしておきます。このように小さい値に設定しても、ファイル送信の部分だけ「post_max_size」の設定値を大きくするといったことも可能です

 では、「post_max_size」を超える大きさのデータを送信したらどうなるでしょうか? 「post_max_size」を16、つまり16byteに設定して、試してみましょう。PHPが実行できるディレクトリに、print_post.phpという名前で以下に挙げるコードを保存してください。中身は単純、$POST変数を表示するだけのものです。

<?php
print_r($_POST);
?>

 post_max_sizeの設定は、データだけでなく、パラメータ名やパラメータ同士のの連結に使う文字もすべて合わせたデータのサイズを制限します。パラメータ名と値は「=」で連結し、複数のパラメータは「&」で連結しますが、その分も合わせたデータサイズを制限するのです。curlコマンドで2つのパラメータを送信してみます。

$ curl -X POST --data 'foo=bar&baz=1234' http://www3026ub.sakura.ne.jp/print_post.php
Array
(
    [foo] => bar
    [baz] => 1234
)

 結果を見ると、2つのパラメータとそれぞれのデータが送信できていることが分かります。この例で送信した文字「foo=bar&baz=1234」は16文字。ちょうど制限に収まっています。では、1文字追加して、次のように実行してみましょう。

$ curl -X POST --data 'foo=bar&baz=12345' http://www3026ub.sakura.ne.jp/print_post.php
Array
(
)

 post_max_sizeの設定値を超えてしまい、リクエストデータを確認しようにも、存在しないということになってしまいました。PHPでは、post_max_sizeの設定で制限した値を超えたデータを送信すると、リクエストデータすべてが無視されるためです。制限の範囲内にあるパラメータだけが処理されるというようなことはないので、大きなデータを受信するようなアプリケーションを実装する場合はこの点に留意する必要があります。

 第38回で、脆弱性に対応したPHP 5.3.9が入手可能になったことを紹介しました。そして、5.3.9にアップデートできないときに、リクエストデータの最大値を制限するという方法を紹介しました。今回紹介したpost_max_sizeが、そのための設定です。

 PHP 5.3.9では、「max_input_vars」という、パラメータの最大数を制限するディレクティブも導入されました。こちらも設定しておきましょう。フォームを使った一般的なアプリケーションを動かすことを想定して、100としておきます。

アップロードファイルをどこで受け付けるか?

 リクエストデータはファイルを含んでいることもあります。Webブラウザから、ファイルを選択して送信するときを考えれば分かるでしょう。このようにしてファイルを受信すると、ファイルそのものは変数には格納せず、ファイル名やファイルサイズなどの情報を変数に格納します。ファイルそのものはサーバ上のファイルシステムに保存するのです。ファイルは一般にデータサイズが大きいため、メモリには置かずに、受信したらそのままファイルシステムに書き込むようになっているのです。

 このようにファイルアップロード機能を利用するときに注意すべきディレクティブがいくつかあります。まず、「file_uploads」ディレクティブは、ファイルアップロード機能を有効にするかどうかを設定します。初期設定値はOnですが、使わないことが分かっているならOffにしてもよいでしょう。ただし、このディレクティブは、ディレクトリによって設定を使い分けることができません。サーバ全体でファイルアップロードを受け付けることがないということでもない限り、Onのままにしておく方が良いでしょう。

 次に注意したいのが「upload_tmp_dir」ディレクティブです。これは、HTTPクライアントがアップロードしてきたファイルを格納するディレクトリを指定するものです。初期設定値は「値なし(no value)」。この場合は、標準的な一時ディレクトリを使う事になります。Linuxならば/tmpです。

 ここで、皆さんにお聞きしたいことが1つあります。/tmpの実体についてです。皆さんのLinuxサーバでは、/tmpの実体はディスクでしょうか。ディスクなら、どれくらいの容量のデータまで記録できるでしょうか。連載の解説で使わせていただいているさくらインターネットのVPSdfを実行してみました。標準OSであるCentOSでの実行結果です。

$ df -Th
Filesystem    Type    Size  Used Avail Use% Mounted on
/dev/hda2     ext3     17G  3.1G   13G  20% /
/dev/hda1     ext3     99M   36M   58M  39% /boot
tmpfs        tmpfs    753M     0  753M   0% /dev/shm
/dev/hdb1     ext3     30G  3.9G   25G  14% /home

 /tmpはディスクであり、/(ルートディレクトリ)直下にあることが分かります。このような場合、/の記録容量に注意しなければなりません。限界に近づいているときに、ファイルをアップロードしてしまうと、OSの動作に支障をきたすことが考えられるのです。念のため、upload_tmp_dirの設定は、/home以下に作成したディレクトリなどに変更しておきたいところです。別ディスクである/homeであれば、仮に記録容量の限界に達してもOSの動作に悪影響を及ぼすことはありません。

 ということで、/home/uploadsというディレクトリをアップロードディレクトリにすることにしましょう。次のようにApache HTTP Serverの実行権限(ここではdaemon)で書き込み可能になるようにディレクトリを作成します。

$ sudo mkdir /home/uploads
$ sudo chgrp daemon /home/uploads
$ sudo chmod g+w /home/uploads

 もう1つ、別の環境を調べてみましょう。

$ df -Th
Filesystem    Type    Size  Used Avail Use% Mounted on
/dev/sda1     ext3     30G  2.9G   26G  11% /
tmpfs        tmpfs    512M     0  512M   0% /dev/shm
none         tmpfs    512M     0  512M   0% /tmp

 こちらの環境は、/tmpがディスクではなく、tmpfsというものになっています。ここに作成したファイルはメモリ上に保存されます。いわゆるRAMディスクのようなものです。このようなケースでは「upload_tmp_dir」を/tmpのままにしておくと、大きなファイルを受け付けたときに、メモリをかなり消費してしまいます。必ず、別の場所、しかもディスク上を指定しましょう。ここで挙げた例では、OSのディレクトリ構成は/のみになっているので、/以下にアップロードファイルを受け付けるディレクトリを作るほかありません。モニタリングシステムなどによるディスク容量の監視が必要でしょう。

 そしてもう1つ注意があります。upload_tmp_dirの設定値として、Webサーバが公開しているディレクトリの配下を指定することは絶対にいけません。アップロードしたファイルが外部からアクセスできるようになってしまうことがあります。Webサーバの公開ディレクトリが、PHP実行可能な状態ならば、PHPスクリプトファイルを送り込んでそれを実行させるということも不可能ではありません。

アップロードファイルの最大サイズ

 アップロード対象のファイル1つ当たりの最大ファイルサイズを設定する、「upload_max_filesize」というディレクティブもあります。post_max_sizeと同じように、アプリケーションの処理内容やディスク容量なども合わせて考え、問題の起きない範囲でもっとも小さい値に設定しましょう。

 このディレクティブを設定するときは、1つ覚えておいてほしいことがあります。post_max_sizeより小さくしなければならないのです。HTTPクライアントがファイルをアップロードするときは、POSTメソッドで送信しますので、post_max_sizeによる制限を受けるということです。

 ファイルのアップロードという行為は、ほかのリクエストデータ同様に、無関係なPHPファイルに対して実行しても成功することがあります。いつものようにcurlを使えば簡単に試せます。確認してみましょう。まず、次のようにして80Mbytesのファイルを作成します。

$ dd if=/dev/zero of=80Mfile.dat bs=8192 count=10240
10240+0 records in
10240+0 records out
83886080 bytes (84 MB) copied, 0.186417 seconds, 450 MB/s

 curlでファイル送信をするには「--form パラメータ名=@ファイル名」とします。

$ curl -X POST --form foo=80Mfile.dat http://www3026ub.sakura.ne.jp/phpinfo.php

 これまでの連載で何度か使用した、phpinfo()を表示するPHPスクリプトに対し、80Mbytesのファイルを送信したことになります。送信中に/home/uploadsの中身を見ると、次のようにファイルをアップロードしている様子を確認できます。

$ ls -l /home/uploads
total 8204
-rw------- 1 daemon daemon 8388608 Jan 26 17:26 phpaidfT2

 このファイルはPHPスクリプトが終了すると同時に消去されますが、外部から自由にファイルを作成できる状況であるということが、よく分かるでしょう。サーバ全体では、「upload_max_filesize」の値は最小の「1」にしておき、ファイルアップロードを許可するアプリケーションごとに適切な値をそれぞれ指定するのが良いでしょう。なお、設定値を0にすると制限がなくなってしまうようです。

 最後にディレクティブ「max_file_uploads」を紹介します。これは、一度に送信できるファイル数の上限を決めるディレクティブです。初期設定値は20。これを変更する必要はあまりないでしょう。

まとめ

 今回の解説を踏まえて、php.iniを編集すると次の通りになります。

memory_limit = 32M
max_execution_time = 30
expose_php = Off
magic_quotes_gpc = Off
variables_order = GPCS
register_long_arrays = Off
register_argc_argv = Off
post_max_size = 64K
max_input_vars = 100
upload_tmp_dir = /home/uploads
upload_max_filesize = 1

 次回は、エラー表示やログに関係する設定について解説します。

著者紹介

株式会社イメージズ・アンド・ワーズ
代表取締役
山口晴広(やまぐち はるひろ)



「仕事で使える魔法のLAMP」バックナンバー

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。