こちらは、株式会社シーエー・アドバンス TENKAI Advent Calendar 202524日目の記事です。
はじめに
初めまして。
株式会社シーエー・アドバンス セキュリティチーム所属の@Shiranohaと申します。
普段は脆弱性診断チーム内支援チームメンバーとして、診断業務のお手伝いをしています。
百聞は一見に如かずという諺がありますが、セキュリティ分野においてその傾向は特に顕著であると感じています。
脆弱性そのものが概念的であり、テキスト学習のみではその脅威を想像しにくいからです。
AIを使えば、その一見に足る体験を気軽にできるのではないかと考え、当記事の執筆に至りました。
今回は任意ファイルアップロードの脆弱性を例に、AIで簡易的なやられサイトを作成し、その脆弱性を通じてサーバ側で任意のコードが実行できることを確かめたいと思います。
任意ファイルアップロードとは
開発者が意図しない形式のファイルをアップロードできてしまう状態を指します。
それ自体は拡張子・MIMEタイプ・ファイル内容を検証していない(又はバイパスされる)ことが根本的な原因ですが、公開ディレクトリ上に保存・ファイル名の無加工といった条件が重なると、以下のような攻撃に繋がる可能性があります。
- DoS攻撃
- マルウェア配布の踏み台
- 既存ファイルの改竄・破壊
- RCE
詳しくはOWASP Cheat Sheet Seriesをご参照ください。
https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
やりたいこと
任意のファイルをアップロードできる簡易的なやられサイトをAI主導で用意してもらい、実際に簡単なRCEを成立させることでこの脆弱性及び攻撃の脅威性を手軽に体験することを目的とします。
やられサイトをAIに作ってもらう
- 拡張子・MIMEタイプ及びファイル内容の検証不足
- ファイル名の無加工保存
- 実行可能ディレクトリへの保存
上記を含む設計のやられサイトを、目視で容易にトレースできる程度に単純な設計で生成してもらいます。
任意ファイルアップロードの脆弱性を持ったやられサイトを作成してください。
以下の設計上の不備を実装してください。
・拡張子・MIMEタイプ及びファイル内容の検証不足
・ファイル名の無加工保存
・実行可能ディレクトリへの保存
脆弱性の学習を目的としているため、コード内容は目視でも容易にトレースできる程度にシンプルな設計にしてください。
非常に簡素ですが、手軽に脅威を味わうなら十分でしょう。
攻撃
では早速、攻撃用のPHPファイルをアップロードします。
以下が実行されると、サーバ側のディレクトリにて勝手にフォルダが作成されるはずです。
<?php
// このスクリプト自身が置かれているディレクトリ
$base = __DIR__ . '/poc_dirs';
// ベースディレクトリを作成(なければ)
if (!is_dir($base)) {
mkdir($base, 0777, true);
}
// 一回アクセスされるごとに、まとめて複数ディレクトリを作る
for ($i = 0; $i < 5; $i++) {
$name = 'dir_' . date('Ymd_His') . '_' . $i;
$path = $base . '/' . $name;
mkdir($path, 0777, true);
}
echo 'poc_dirs 以下にディレクトリを5個作成しました。';
何となくKali Linuxのブラウザから攻撃してみます。
evil.phpをアップロードし、ファイルを押下します。

確認してみましょう。
ホストOS側に戻り、エクスプローラを確認します。
poc_dirs以下に5つの空フォルダが追加されていることを確認できました。
サーバ上で任意のPHPコードを実行できている状態ですので、実質的なRCEが成立しています。
何が起きたか
今回の場合、サーバ側がアップロードされたファイルをそのまま実行可能なスクリプトとして解釈してしまったため攻撃が成立しました。
1. 攻撃者がPHPファイルを用意する
2. アップロードフォームからその PHP ファイルを送信する
3. サーバ側がファイルの拡張子や内容を検証せず、公開ディレクトリにそのまま保存する
4. 攻撃者がブラウザで 公開ディレクトリ/<アップロードしたファイル名>.php にアクセスする
5. Webサーバがその .php を PHP として解釈し、ファイル中のコードが実行される
6. その結果、任意コマンド実行・ファイル操作などのRCEが成立する
特筆すべきは、ファイルが公開ディレクトリに保存されている点と、スクリプトとして実行できる状態のファイルアップロードを許してしまっている点です。
この2つの条件が揃うと、任意のコードを実行できてしまいます。
対策
今回のRCEを阻止する観点では、以下2点の対策が有効です。
1. 拡張子、及びMIME・ファイル内容の検証
2. スクリプトを経由したファイル閲覧
1番目についてはサイトの目的次第ですが、今回は画像ファイルのみアップロードを許可することにします。
以下は1番目の対策を実装したAI製コードです。
//許可する拡張子を定義
$allowedTypes = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
];
$message = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1) アップロード有無の確認
if (!isset($_FILES['file']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
$error = 'ファイルが選択されていません。';
} else {
$file = $_FILES['file'];
$tmp = $file['tmp_name'];
// 2) 拡張子(見た目)の取得とホワイトリストチェック
$ext = strtolower((string) pathinfo($file['name'], PATHINFO_EXTENSION));
if ($ext === '' || !isset($allowedTypes[$ext])) {
$error = '許可されていない拡張子です。';
} else {
// 3) MIME をサーバ側で推定(finfo優先、なければmime_content_type)
$detectedMime = '';
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if ($finfo !== false) {
$detectedMime = (string) finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
}
} elseif (function_exists('mime_content_type')) {
$detectedMime = (string) mime_content_type($file['tmp_name']);
}
// 4) MIME が取得できない場合は安全側に倒して拒否
if ($detectedMime === '') {
$error = 'MIME タイプを判定できませんでした。';
}
// 5) 拡張子に対応する許可MIMEと一致するかチェック
elseif ($detectedMime !== $allowedTypes[$ext]) {
$error = 'ファイルタイプが不正です。';
} else {
// 4-2) getimagesize で画像として解釈できることを確認
$info = @getimagesize($tmp);
if ($info === false) {
$error = '画像として解釈できません(破損している可能性があります)。';
} else {
// 5) 保存
// 最低限 basename() で保存先逸脱を抑止
$safeName = basename((string) $file['name']);
$targetPath = $uploadDir . $safeName;
if (move_uploaded_file($tmp, $targetPath)) {
$message = 'アップロードしました: ' . $safeName;
} else {
$error = 'アップロードに失敗しました。';
}
}
}
}
}
}
上記では$allowedTypesで定義した拡張子のみを許可します。
phpファイルのアップロードを試みても、拡張子検証の段階でしっかり弾かれます。

拡張子をpng等の適当なものに変更しても、MIME検証で以下の様に阻止されます。

(本来は、仮に MIME 検証を突破された場合でも、getimagesize によるファイル内容検証で弾かれる点まで示したかったため、PNGのマジックバイトのみを含んだ簡素な偽PNGも用意していました。
しかし実際には、finfo による MIME 検証の段階で拒否されてしまったため、今回は割愛します。)
少なくとも、容易に危険なファイルをアップロードすることはできなくなりました。
しかし脅威は依然として健在です。仮に上記の検証を回避されたとしましょう。今回は保存ディレクトリ内に直接phpファイルを保存することで、その状況を疑似的に再現します。


当然と言えば当然ですが実行できてしまいます。公開ディレクトリ内に直接保存しているためです。
そこで2番目のスクリプト実装、及び保存先を非公開ディレクトリに変更します。
今回はApacheを使用しているため、htdocsの外にファイルを保存し、それをスクリプト経由でプレビューする形式をとります。
if (isset($_GET['view'])) {
$target = basename((string) $_GET['view']);
$ext = strtolower((string) pathinfo($target, PATHINFO_EXTENSION));
if (!isset($allowedTypes[$ext])) {
http_response_code(400);
echo '許可されていないファイルです。';
exit;
}
$targetPath = $uploadDir . $target;
if (!is_file($targetPath)) {
http_response_code(404);
echo 'ファイルが見つかりません。';
exit;
}
header('Content-Type: ' . $allowedTypes[$ext]);
header('Content-Disposition: inline; filename="' . $target . '"');
readfile($targetPath);
exit;
}
仮にphpファイルが保存されていても非公開ディレクトリのため、ブラウザからは実行できません。
更に上記のプレビュースクリプトにより、適切なエラーハンドリングもなされています。

まとめ
気付けばAIテーマ記事というより学習記録になってしまいました…。
ただ、今回あらためて実感したのは、AIを使う価値は「手を動かすための敷居を下げてくれること」にある、という点です。
文章や図だけでは掴みにくかった任意ファイルアップロードやRCEも、一度自分の環境で再現してみると「なぜ危険なのか」「どこを塞ぐべきか」が自然と見えてきました。
例え簡素な物でも、実際に体験できる点に大きな価値があると感じています。
AIは理解を代行してくれる存在ではありませんが、学習の初速を上げ、試行錯誤を回しやすくしてくれる道具としては非常に有用だと思います。
当記事が少しでも、私と同じセキュリティ初学者の方の参考になれば幸いです。
それでは、ここまでお読みいただきありがとうございました。
素敵なクリスマスをお過ごしください!
本記事で扱う内容は、学習目的で構築したローカル環境・検証環境のみを対象としています。
許可なく第三者のシステムや公開環境に対して同様の行為を行うことは、法律や利用規約に違反する可能性があります。
実施する場合は、必ず自身が管理・許可された環境内に限定してください。
🌺沖縄から「21世紀を代表する会社」を創る仲間を募集
シーエー・アドバンスは、「沖縄のインターネット産業の未来を創る」をビジョンに掲げ、サイバーエージェントグループの一員として、ABEMAやAmebaを支える社内システムを開発しています。 勤務地は沖縄・那覇市 おもろまち。 Next.js 、React、Ruby on Rails、AWS などモダンな技術で、東京・ベトナムのエンジニアと協業。若手が裁量権を持って挑戦できる環境です。
失敗を恐れず挑戦できる文化。一緒にアドバンス(進歩)しませんか?


