ファイルのアップロードでディスクへの格納に、お客様の名前のディレクトリを作成して格納するって事になっている。
このお客様の名前というのは、フォームの名前欄で、どんな文字でも入力できるので、わしなら絶対に採用しない仕様。
とりあえず、phpの関数しか使っていないし、ヤバそうな文字は予めエスケープしたり、文字置換したりしているからいいかと思っていたが、バックアップでファイルをリアルタイムでコピーするって話になってさあ大変。
最初は php の move_uploaded_file() が成功したら、バックアップディスクにもファイルをcopy() でコピーするコードを書いた。
しかし、ファイルコピーが終了するまで、phpスクリプトが終了しないので、完了時間が数秒延びてしまう。
ディレクトリが入力フリーダムな状況で、その文字列をシェル渡しにするなんぞ、絶対にやりたく無かったが、仕方がないので、exec() でバックグラウンド起動し、スクリプトはすぐに終了するようにした。
シェル渡しにすると何が問題になるのか?
たとえば、名前欄に「野泉雄嗣」と入れて、次のコマンドが実行される様な事を期待していたとする。
copy /var/www/upload/野泉雄嗣/filename.lzh /backupdisk/野泉雄嗣/filename.lzh
では名前欄に「; rm -rf /;」って入れたらどうなるか?
copy /var/www/upload/; rm -rf /;/filename.lzh /backupdisk/; rm -rf /;/filename.lzh
で、これがシェルに渡ると5つのコマンドと解釈される。
1. copy /var/www/upload/
2. rm -rf /
3. /filename.lzh /backupdisk/
4. rm -rf /
5. /filename.lzh
1,3,5はエラーで終わるが、2と4が実行されると、apacheに書き込み権のあるディレクトリのファイルが全部削除される。
php の copy() はパス名にこういうヤバい文字が含まれていても、うまい事握ってくれるが、exec()でやるとなると、この辺は自前で何とかしないといけない。
変なコマンドを実行されない為には、次の様にヤバい文字をエスケープすれば良い。
copy /var/www/upload/;\ rm\ -rf\ /\;/filename.lzh /backupdisk/\;\ rm\ -rf\ /\;/filename.lzh
escapeshellcmd()とか、ヤバい文字をエスケープしてくれる関数があるが、これはマルチバイト非対応で、漢字文字列があると全部削除される。
つまり「野泉雄嗣」の様な漢字のディレクトリが作られる事を想定しているプログラムには使えない。
とりあえず、escapeshellcmd()でエスケープする文字に空白とかも追加して
$str = addcslashes($str,’ \'”#&;`|*?~<>^()[]{}$\\’.”\x0A\xFF”);
とやったが、これってUTF-8の漢字の一部に該当するコードがあっても置換される予感がするなあ…。前に組んだmb_replace_string()って自作関数使った方が良さそう。
しかし、そもそも論で言えば、「何で人間が見て分かる形にする必要があるの?」ってのがある。
システム化というのは、「人間が見て判断する」という非効率でヒューマンエラーが発生しやすい状況を改善する事が目的である。
効率化を追求した結果、これ以上効率化が無理だからシステム化するってなら分かるが、非効率な手順は一切変えず、それを無理矢理システム化するってのは、システム化に対する幻想がある気がするな。
友人が電気店で見たある客がMacを指差しながら店員に「これ買ったらすごい絵が描ける様になるんですよね!?」って言ってたって話を思い出したな 🙂