ファイルのアップロードを制限する:仕事で使える魔法のLAMP(40)
今回は、HTTPクライアントがアップロードしてくるファイルの扱いについて解説します。受け付けるファイルのサイズ制限や、受け取ったファイルを置くディレクトリなど、注意しなければならないことが意外に多くあります(編集部)
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の実体はディスクでしょうか。ディスクなら、どれくらいの容量のデータまで記録できるでしょうか。連載の解説で使わせていただいているさくらインターネットのVPSでdfを実行してみました。標準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
次回は、エラー表示やログに関係する設定について解説します。
- CMakeでMySQLをビルドしてみる
- MySQLのビルドに欠かせないCMakeを準備する
- いよいよMySQL編、ソースからビルドすべきか?
- PHPでセッションを利用するための設定
- クライアントがアクセスできる範囲を制限する
- エラーメッセージをどう扱うか?
- ファイルのアップロードを制限する
- リクエストデータを受け取る変数の扱い
- マジッククオート機能には頼らない
- 安全を考えてPHPの実行時設定を調整する
- Apacheの設定ファイルでPHPの設定を変える
- PHPの設定ファイルを作って配置してみる
- PHPスクリプトを実行できるようにする準備
- PHPエクステンション組み込みの仕上げ
- 単純なデータを管理するDBMを使えるようにする
- エクステンションの組み込み状況を確認する
- PHPでデータベースを使う準備をする
- XMLを処理できるようにする
- エクステンションを有効にしてビルドに挑戦!
- PHPテスト失敗の原因を追究する
- 早速PHPをビルド! そしてテスト!
- PHP編に突入! まずはソースをダウンロード
- 設定ファイルを作成してApacheを動作させる
- 設定ファイルや公開ドキュメントの配置を考える
- 1つのサーバに複数の仮想サーバ?
- Apacheの設定ファイルを記述する前に
- サードパーティのApacheモジュールをビルドする
- 認証DBにアクセスするライブラリを組み込む
- Apache同梱ソフトウェアに引数を渡してビルド
- OpenSSLをビルドしてApacheで利用する
- proxyやsslのモジュールを使ってみる
- ライブラリが足りなくてビルドできないときは?
- Apache HTTP Serverのビルドを始めよう
- configureでソフトウェア固有の設定を変更してみる
- configureの設定を変更してみる
- 配布パッケージの中身と、configureの役目を知る
- ダウンロードファイルが真正なものであるかを確認
- Makefileをいろいろ書き換えながらビルドしてみよう
- makeを使ってソフトウェアをビルドしてみよう
- ダイナミックリンクとスタティックリンク
- 「ビルド」という作業は何を指しているのか
- 公開鍵認証でsshを安全に使う
- sshを便利にする公開鍵暗号
- アクセス制限の設定とCentOSのアップデート
- サーバに接続して、一般ユーザーのアカウントを作る
- LAMP環境、自分で作りませんか?
Copyright © ITmedia, Inc. All Rights Reserved.