CVE-2025-3783について

セキュリティ

 公開されているPHPの脆弱性を確認していたところ、危険なタイプのファイルの無制限アップロードに関するCVEが発行されていたので、実験し対策について考えてみました。
 このように脆弱なサイトは作って公開してはいけませんし、脆弱性があるサイトに悪意のあるファイルをアップロードすることも絶対にしてはいけません。

危険なタイプのファイルの無制限アップロードとは

 どんなファイルでもアップロードできる状態のことで、悪意がある者が不正な動作を行う実行ファイルをアップロードすることによって、サーバに予想外のプログラムを実行させたり、第三者にこのファイルを実行させる危険があります。
 本ケースは、Senior Walter の Web-based Pharmacy Product Management Systemが対象になっていますが、ファイルアップロードを行うシステムを作る人は必ず確認しておかないといけない内容だと思います。

危険な状態の再現

①実験環境の構築

 ローカル環境で、画像をアップロードさせる脆弱なシステムを作って再現しました。
  ※絶対に公開領域に作らないでください

ローカルWebサーバ上に配置したファイル、フォルダ

#ファイル名 /www/003939/index.html パーミッション 664
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
</head>
<body>
<form action="upload_picture.php" method="post" enctype="multipart/form-data">
<h1>危険な画像アップロードサイト</h1>
  アップロードする画像ファイルを選択してください
  <hr>
  <input type="file" name="uploadedfile"><input type="submit" value="Upload">
</form>
</body>
</html>
# /www/003939/upload_picture.php パーミッション 755
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$target = "pictures/" . basename($_FILES['uploadedfile']['name']);

if (move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target)) {
    echo "画像のアップロードに成功しました。";
} else {
    echo "画像のアップロード中にエラーが発生しました。もう一度お試しください。<br>";
    if (isset($_FILES['uploadedfile']['error'])) {
        echo "アップロードエラーコード: " . $_FILES['uploadedfile']['error'] . "<br>";
    } else {
        echo "ファイル情報が送信されていません。<br>";
    }
    if (isset($_FILES['uploadedfile'])) {
        echo "<pre>";
        print_r($_FILES['uploadedfile']);
        echo "</pre>";
    }
}
?>

アップロードフォルダ
# /www/CVE003939/pictures パーミッション 777

②アクセスした様子

③実験

 この危険な画像アップロードサイトは、画像ファイルをアップロードすることを想定しているので、なんの対策もしていません。
 次のようなファイルをアップロードされると、非常に危険です。
  ※windowsで次のファイルを作るだけでMicrosoftDefenderが大騒ぎして警告をだしたり、ファイルを勝手に削除したりしますw

# 危険なファイル malicious.php
<?php
system($_GET['cmd']);
?>

 ここで作成したmalicious.phpを、危険な画像アップロードサイトにアップロードすると、サーバ上に配置することができてしまします。

 アップロードしたmalicious.phpにアクセスしても、500サーバエラーを返します。

しかし、不正なクエリを渡すと、対象サーバ上でphpを実行することができてしまいます。

※対象サーバ上でphpを実行した結果が返ってきた様子

問題点の列挙

 今回のプログラムにおける問題点は

  • アップロードできるファイルの種類が制限されていない
  • 画像ファイル以外のファイルがアップロードできること
  • アップロードファイル名をユーザが入力できること
  • アップロードファイルの保存場所に実行権限がついていること

と考えます。
 ①アップロードできるファイルの種類が制限されていない
  これは、ファイル名の拡張子が”jpg”かどうかで判断します。

$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($ext !== 'jpg' && $ext !== 'jpeg') {
    echo "アップロードできるのは JPG ファイルのみです。";
    exit;
}

 ②画像ファイル以外のファイルがアップロードできること

  これは、拡張子の偽装によるファイルアップロードを制限するものです。
  この方法はやろうと思えばいくらでも偽装できてしまうので、利用する環境によってどの程度まで調べるか変わってくると思います。
  Yo*t*beに動画ファイルに偽装させたファイルをアップロードして、ファイルサーバとして使用していた記事がありましたね。
  あれはどうやって防げばいいのかわかりませんが、とりあえずMIMEでファイルの種類を判別させています。

$mime = mime_content_type($file['tmp_name']);
if ($mime !== 'image/jpeg') {
    echo "ファイル形式が正しくありません。JPEG画像のみアップロード可能です。";
    exit;
}

 ③アップロードファイル名をユーザが入力できること

  ファイル名をユーザに入力させるのは危険です。ファイル名に”../”等を入れてパストラバーサル攻撃に使われる恐れがあります。
  正規表現で”../”を防いでも、よくわからない外国語のUNIコードを混ぜたりして回避される危険があるので、ファイル名は独自のルールか乱数にするのがいいと思います。
  乱数はシノニムの危険があるので、その対策は各自で実装をお願いします。

$random_name = uniqid('img_', true) . '.' . $ext;

  ④アップロードファイルの保存場所に実行権限がついていること

   アップロードフォルダのパーミッションは最低限にしてください。
   環境によって多少変わると思いますが、実行権限は不要なので、664か666あたりになると思います。

修正後のプログラム

 問題点を修正したphpファイルは次のようになりました。

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

// アップロードファイルの情報を取得
$file = $_FILES['uploadedfile'] ?? null;

if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
    echo "ファイルのアップロード中にエラーが発生しました。<br>";
    echo "アップロードエラーコード: " . ($file['error'] ?? '不明') . "<br>";
    if ($file) {
        echo "<pre>";
        print_r($file);
        echo "</pre>";
    }
    exit;
}

// ここで拡張子チェック
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($ext !== 'jpg' && $ext !== 'jpeg') {
    echo "アップロードできるのは JPG ファイルのみです。";
    exit;
}

// さらにMIMEタイプ(本当に画像か)チェック
$mime = mime_content_type($file['tmp_name']);
if ($mime !== 'image/jpeg') {
    echo "ファイル形式が正しくありません。JPEG画像のみアップロード可能です。";
    exit;
}

// ランダムなファイル名を生成(ユニークIDを利用)
$random_name = uniqid('img_', true) . '.' . $ext;

// 保存先パス
$target = "pictures/" . $random_name;

// ファイルを移動
if (move_uploaded_file($file['tmp_name'], $target)) {
    echo "画像のアップロードに成功しました。ファイル名 $random_name";

} else {
    echo "画像のアップロード中にエラーが発生しました。もう一度お試しください。<br>";
}
?>

実行結果

①malicious.phpをアップロード

②malicious.php.jpgをアップロード
 ※拡張子をjpgに偽装したファイル

③jpgファイルをアップロード

 ※ファイル名が乱数化されている

④アップロードフォルダのパーミッションを664に変更

 エラーが表示され、エクスプロイトコードを入力しても実行されません。

最後に

 今回は危険なファイルの無制限アップロードについて実験を行い、対策について考えてみました。
 ファイルアップロードは攻撃者から狙われやすい部分ですので、実装には十分な注意を払いたいと思います。
 すでにアップロードフォルダに危険なファイルがアップロードされていることも考えられるので、こういったCVEが公開されたときは、他山の石だと思って自分が今までに公開したプログラムも見直したいと思いました。(なかなかできないけど やらねば!)