その「ファイルアップロード」、泥棒に勝手口を開けているのと同じですよ?
こんにちは。セキュリティの世界で泥臭い現場を駆け回っているエンジニアです。
皆さんが開発しているWebサービスで、「ユーザーがアイコン画像をアップロードする機能」や「ドキュメントを共有する機能」はありますか? 実はこれ、セキュリティの観点から見ると、家の中に「誰でも鍵を開けて入れる勝手口」をわざわざ作っているようなものなんです。
今日は、攻撃者がどうやってその勝手口から侵入してくるのか、そしてどうすれば「強固な防犯システム」を構築できるのか、一緒に紐解いていきましょう。
—
1. 攻撃者が使う「変装」のテクニック
攻撃者は、サーバーを騙すために「変装」をします。サーバーは「このファイルは画像だから安全だ」と信じ込んでしまいますが、中身は真っ黒な「Webシェル(サーバーを遠隔操作するためのプログラム)」ということがよくあるんです。
MIMEタイプ偽装:中身の嘘
サーバーは、ファイルが届いたときに「これは画像ファイル(image/jpeg)ですよ」というラベル(MIMEタイプ)を確認することがあります。でも、攻撃者はこのラベルを書き換えて「私は画像です!」と嘘をついて送り込んできます。
二重拡張子:名前のトリック
例えば、malicious.php.jpg という名前のファイルをアップロードさせたとします。
サーバーの設定が甘いと、「お、最後が .jpg だから画像だな!」と判断し、保存してしまいます。しかし、サーバーの実行環境が「.phpが含まれていたらプログラムとして動かす」という設定になっていると……プログラムが実行され、サーバーが乗っ取られてしまいます。
ヌルバイト攻撃:名前の切り捨て
古いシステムだと、ファイル名の途中に「ヌルバイト(%00)」という特殊な信号を入れることで、システムがその先を無視してしまう現象がありました。shell.php%00.jpg と送ると、サーバーは「.jpgだ!」と認識しつつ、実際にはshell.phpとして保存してしまう、といった具合です。
—
2. 実践的な防衛策:どうやって鍵をかける?
では、どうすれば防犯レベルを上げられるのでしょうか? 「拡張子を見るだけ」という甘い対策は捨てて、以下の「多層防御」を組み合わせてください。
① ファイル名は「自分たちで決める」
ユーザーが付けたファイル名をそのまま保存してはいけません。これが一番の悪手です。アップロードされたら、サーバー側で名前を完全に書き換えてしまいましょう。
// 良い例:ユーザーの入力は信頼せず、UUIDなどでランダムな名前を生成する
$extension = pathinfo($_FILES[‘uploaded_file’][‘name’], PATHINFO_EXTENSION);
$new_filename = bin2hex(random_bytes(16)) . ‘.’ . $extension;
// これなら「malicious.php.jpg」のようなトリックは通用しません
② 拡張子をホワイトリストで管理する
「許可しないリスト(ブラックリスト)」を作るのではなく、「許可するものだけを並べたリスト(ホワイトリスト)」を作りましょう。
$allowed_extensions = [‘jpg’, ‘jpeg’, ‘png’, ‘gif’];
$ext = strtolower(pathinfo($new_filename, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_extensions)) {
die(“許可されていないファイル形式です。”); // 門前払い!
}
③ ファイルの中身(マジックバイト)を検証する
拡張子が .jpg でも、中身がPHPコードだったら意味がありません。ファイルの内容をバイナリレベルでチェックしましょう。
// 画像ファイルかどうかをGDライブラリで判定する例
if (@is_array(getimagesize($_FILES[‘uploaded_file’][‘tmp_name’]))) {
// 画像として読み込めたので合格!
} else {
// 画像ではない何か(プログラムなど)なので拒否!
}
④ 保存場所には「実行権限を与えない」
これが最も強力な防犯装置です。アップロードされたファイルを保存するディレクトリ(例: /uploads)に対して、Webサーバーがプログラムを実行できないように設定します。
Apacheの設定例 (.htaccess):
このディレクトリ内では、PHPなどを実行させない設定
php_flag engine off
AddHandler cgi-script .php .phtml .php3
RemoveHandler .php .phtml .php3
※ nginxの場合は location ブロックで fastcgi_pass を無効化する設定を行います。
—
最後に:セキュリティは「疑うこと」から始まる
「使い勝手」を優先して制限を緩めると、その隙間から必ず攻撃者は入ってきます。
今回紹介した対策は、決して難しいことではありません。「ユーザーの入力値はすべて泥棒の手土産かもしれない」と考えて、厳しくチェックし、決して実行権限を与えない。この基本を徹底するだけで、皆さんのサービスは劇的に堅牢になります。
もし今、皆さんのシステムで「ファイル名をそのまま保存している」「拡張子のチェックが甘い」箇所があったら、今日のうちに見直してみてくださいね。セキュリティは、小さな積み重ねの先にある信頼です。
一歩ずつ、安全な開発を一緒に進めていきましょう!
コメント