Skip to content

So implementieren Sie Datei-Uploads in PHP|Vollständiges Leitfaden für Validierung, Speicherung und Sicherheit

Kategorie:PHP・Implementierung
Dieser Artikel ist derzeit nur auf Japanisch verfügbar. Übersetzte Versionen werden schrittweise veröffentlicht.

Bei der Implementierung von Datei-Upload-Funktionen in PHP ist es einfach, Code zu schreiben, der 「einfach funktioniert」, aber eine robuste Implementierung unter Berücksichtigung von Sicherheit, Validierung und Fehlerbehandlung ist überraschend komplex. Dieser Artikel erklärt systematisch von der Struktur von <code>$_FILES</code> über die Validierung von MIME-Typen, Extension-Whitelists, sichere Speicherorte und Desinfizierung von Dateinamen bis hin zu Implementierungsmethoden auf Produktionsebene.

Browser PHP-FPM /tmp uploads/ POST multipart/form-data tmp file write $_FILES populated move_uploaded_file()
Abbildung: Verarbeitungssequenz von $_FILES (POST → /tmp → move_uploaded_file)

Die Struktur von <code>$_FILES</code> verstehen

Beim Senden von Dateien in einem HTML-Formular mit <code>enctype="multipart/form-data"</code> speichert PHP die Upload-Informationen in der Superglobalen <code>$_FILES</code>. Wenn das Attribut <code>name</code> des Formulars <code>userfile</code> ist, wird <code>$_FILES['userfile']</code> ein assoziatives Array mit den folgenden 5 Schlüsseln.

// $_FILES の構造
[
    'name'     => 'photo.jpg',        // クライアント側のファイル名
    'type'     => 'image/jpeg',       // クライアントが申告するMIMEタイプ
    'tmp_name' => '/tmp/phpA1B2C3',   // サーバーの一時保存パス
    'error'    => 0,                  // エラーコード(0 = 成功)
    'size'     => 204800,             // ファイルサイズ(バイト)
]

Als wichtiger Punkt ist <code>type</code> ein vom Client gesendeter Wert und kann gefälscht werden. Überprüfen Sie für die MIME-Typ-Validierung unbedingt auf der Serverseite mit <code>finfo</code>.

Liste der Fehlercodes UPLOAD_ERR_* und Lösungen

<code>$_FILES['userfile']['error']</code> enthält einen der folgenden Konstantenwerte. Es ist wichtig, diesen Wert zu Beginn des Upload-Prozesses zu überprüfen und Fehler angemessen zu behandeln.

Konstante Wert Bedeutung Lösungsmethode
UPLOAD_ERR_OK 0 Upload erfolgreich Verarbeitung fortsetzen
UPLOAD_ERR_INI_SIZE 1 Limit von upload_max_filesize in php.ini überschritten Überprüfung der php.ini-Konfiguration
UPLOAD_ERR_FORM_SIZE 2 MAX_FILE_SIZE-Limit des HTML-Formulars überschritten Formulareinstellungen überprüfen
UPLOAD_ERR_PARTIAL 3 Datei nur teilweise übertragen Neu-Upload fördern
UPLOAD_ERR_NO_FILE 4 Keine Datei ausgewählt Zur Dateauswahl auffordern
UPLOAD_ERR_NO_TMP_DIR 6 Temporäres Verzeichnis existiert nicht Servereinstellungen überprüfen
UPLOAD_ERR_CANT_WRITE 7 Fehler beim Schreiben auf den Datenträger Datenträgerkapazität und Berechtigungen überprüfen
UPLOAD_ERR_EXTENSION 8 Upload durch PHP-Erweiterung gestoppt Konfiguration des Erweiterungsmoduls überprüfen
function getUploadErrorMessage(int $errorCode): string
{
    return match ($errorCode) {
        UPLOAD_ERR_INI_SIZE   => 'ファイルサイズがサーバーの上限を超えています。',
        UPLOAD_ERR_FORM_SIZE  => 'ファイルサイズがフォームの上限を超えています。',
        UPLOAD_ERR_PARTIAL    => 'ファイルが完全にアップロードされませんでした。再度お試しください。',
        UPLOAD_ERR_NO_FILE    => 'ファイルが選択されていません。',
        UPLOAD_ERR_NO_TMP_DIR => 'サーバーエラー: 一時ディレクトリが見つかりません。',
        UPLOAD_ERR_CANT_WRITE => 'サーバーエラー: ファイルの書き込みに失敗しました。',
        UPLOAD_ERR_EXTENSION  => 'サーバーエラー: 拡張機能によりアップロードが拒否されました。',
        default               => '不明なエラーが発生しました。',
    };
}

MIME-Typ-Validierung (mit finfo)

<code>$_FILES['userfile']['type']</code>, das vom Client gesendet wird, kann gefälscht werden. Daher ist es notwendig, den MIME-Typ aus dem tatsächlichen Dateiinhalt auf der Serverseite zu bestimmen. Verwenden Sie <code>finfo_file()</code> von PHP (oder die Klasse <code>FileInfo</code>).

function validateMimeType(string $tmpPath, array $allowedMimes): bool
{
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $detectedMime = $finfo->file($tmpPath);

    return in_array($detectedMime, $allowedMimes, true);
}

// 使用例: 画像ファイルのみ許可
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!validateMimeType($_FILES['userfile']['tmp_name'], $allowedMimes)) {
    throw new RuntimeException('許可されていないファイル形式です。');
}

<code>finfo</code> liest die ersten Bytes der Datei (Magic Number), um den MIME-Typ zu bestimmen und kann so auch Angriffe mit gefälschten Erweiterungen abwehren. Textdateien (CSV, JSON usw.) können jedoch als <code>text/plain</code> bestimmt werden, daher wird empfohlen, diese mit einer Erweiterungsprüfung zu kombinieren.

Dateityp-Whitelist

Implementieren Sie zusammen mit der MIME-Typ-Validierung auch eine Whitelist-Überprüfung von Dateierweiterungen. Eine Whitelist (nur erlaubte Erweiterungen auflisten) ist sicherer als eine Blacklist (.php, .exe usw. verbieten).

function validateExtension(string $originalName, array $allowedExtensions): bool
{
    // pathinfo() で拡張子を取得し、小文字に正規化
    $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));

    return in_array($extension, $allowedExtensions, true);
}

// 使用例
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!validateExtension($_FILES['userfile']['name'], $allowedExtensions)) {
    throw new RuntimeException('許可されていない拡張子です。');
}

// MIMEタイプと拡張子の対応を明示的にマッピングする方法
$mimeToExtensions = [
    'image/jpeg' => ['jpg', 'jpeg'],
    'image/png'  => ['png'],
    'image/gif'  => ['gif'],
    'image/webp' => ['webp'],
];

$finfo = new finfo(FILEINFO_MIME_TYPE);
$detectedMime = $finfo->file($_FILES['userfile']['tmp_name']);
$extension = strtolower(pathinfo($_FILES['userfile']['name'], PATHINFO_EXTENSION));

if (
    !isset($mimeToExtensions[$detectedMime]) ||
    !in_array($extension, $mimeToExtensions[$detectedMime], true)
) {
    throw new RuntimeException('ファイル形式と拡張子が一致しません。');
}

Codebeispiel für Dateigröße-Limit

Die Größenprüfung auf PHP-Ebene wird mit <code>$_FILES['userfile']['size']</code> durchgeführt. Dateien, die jedoch <code>upload_max_filesize</code> in php.ini überschreiten, erreichen <code>$_FILES</code> nicht und führen zu einem <code>UPLOAD_ERR_INI_SIZE</code>-Fehler. Daher ist es wichtig, zunächst den Fehlercode zu überprüfen.

const MAX_UPLOAD_SIZE = 10 * 1024 * 1024; // 10 MiB

function validateFileSize(int $fileSize, int $maxSize = MAX_UPLOAD_SIZE): bool
{
    return $fileSize > 0 && $fileSize <= $maxSize;
}

// 使用例
if (!validateFileSize($_FILES['userfile']['size'])) {
    $maxMiB = MAX_UPLOAD_SIZE / (1024 * 1024);
    throw new RuntimeException("ファイルサイズが上限({$maxMiB} MiB)を超えています。");
}

// アップロード処理を関数にまとめた例
function processUpload(array $file): array
{
    // 1. エラーコード確認
    if ($file['error'] !== UPLOAD_ERR_OK) {
        throw new RuntimeException(getUploadErrorMessage($file['error']));
    }

    // 2. サイズチェック
    if (!validateFileSize($file['size'])) {
        throw new RuntimeException('ファイルサイズが上限を超えています。');
    }

    // 3. MIMEタイプチェック
    $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    if (!validateMimeType($file['tmp_name'], $allowedMimes)) {
        throw new RuntimeException('許可されていないファイル形式です。');
    }

    // 4. 拡張子チェック
    $allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
    if (!validateExtension($file['name'], $allowedExts)) {
        throw new RuntimeException('許可されていない拡張子です。');
    }

    return $file;
}

Sicherer Speicherort (außerhalb von public)

Hochgeladene Dateien sollten außerhalb des Dokumentstammverzeichnisses (public_html / public usw.) gespeichert werden. Wenn sie im public-Verzeichnis gespeichert werden, besteht die Gefahr des direkten Zugriffs über URLs oder der Ausführung als PHP-Dateien.

// 推奨ディレクトリ構成
// /var/www/
//   ├── public/         ← ドキュメントルート(外部からアクセス可能)
//   │   └── index.php
//   └── storage/        ← public の外(外部から直接アクセス不可)
//       └── uploads/

// 定数で保存先を明示
define('UPLOAD_DIR', dirname(__DIR__) . '/storage/uploads/');

// ディレクトリが存在しない場合は作成
if (!is_dir(UPLOAD_DIR)) {
    mkdir(UPLOAD_DIR, 0755, true);
}

// ダウンロード提供時は PHP 経由でストリーミング
function serveFile(string $filename): void
{
    $filePath = UPLOAD_DIR . basename($filename);

    if (!file_exists($filePath)) {
        http_response_code(404);
        exit;
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($filePath);

    header('Content-Type: ' . $mimeType);
    header('Content-Length: ' . filesize($filePath));
    header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
    readfile($filePath);
    exit;
}

ファイル名のサニタイズ(uniqid使用)

クライアントが送信したファイル名をそのまま使用するのは危険です。ディレクトリトラバーサル(../../etc/passwd のようなパス)や、特殊文字を含むファイル名によるOSコマンドインジェクションのリスクがあります。uniqid() を使ってユニークなファイル名を生成し、元のファイル名は別途データベースなどに記録する方法が安全です。

function generateSafeFilename(string $originalName): string
{
    // 拡張子のみ元のファイルから引き継ぐ
    $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));

    // uniqid() + mt_rand() でユニークな名前を生成
    // more_entropy=true で精度を上げる
    $uniqueName = uniqid('upload_', true);

    // さらにランダム性を加える場合は bin2hex(random_bytes(8)) を使う
    // $uniqueName = bin2hex(random_bytes(16));

    return $uniqueName . '.' . $extension;
}

// 使用例
$safeFilename = generateSafeFilename($_FILES['userfile']['name']);
$savePath = UPLOAD_DIR . $safeFilename;

// is_uploaded_file() で正規のアップロードか確認してから移動
if (!is_uploaded_file($_FILES['userfile']['tmp_name'])) {
    throw new RuntimeException('不正なファイルアップロードを検出しました。');
}

if (!move_uploaded_file($_FILES['userfile']['tmp_name'], $savePath)) {
    throw new RuntimeException('ファイルの保存に失敗しました。');
}

// 元のファイル名、保存名、MIMEタイプをDBに記録
// db_insert(['original_name' => $_FILES['userfile']['name'], 'saved_name' => $safeFilename, ...]);

php.ini 設定(upload_max_filesize / post_max_size)

Um Datei-Uploads in PHP zu akzeptieren, müssen Sie php.ini (oder lokale Einstellungen in .htaccess / php.ini) ordnungsgemäß konfigurieren. Das Wichtige ist nicht nur <code>upload_max_filesize</code>, sondern auch <code>post_max_size</code> entsprechend zu ändern.

; php.ini の設定
; ファイルアップロードを有効化
file_uploads = On

; 1ファイルあたりの上限(M = MiB 単位)
upload_max_filesize = 20M

; POSTリクエスト全体の上限
; upload_max_filesize より大きく設定する(フォームデータのオーバーヘッド分)
post_max_size = 25M

; 最大実行時間(大きなファイルのアップロードに対応)
max_execution_time = 300

; 最大入力時間(アップロードの読み込み時間)
max_input_time = 300

; メモリ上限(post_max_size より大きくする)
memory_limit = 128M

Sie können überprüfen, ob der Konfigurationswert angewendet wurde, indem Sie <code>phpinfo()</code> oder <code>ini_get()</code> verwenden.

// 現在の設定値を確認
echo ini_get('upload_max_filesize');  // 例: "20M"
echo ini_get('post_max_size');        // 例: "25M"

// バイト単位に変換するユーティリティ
function convertToBytes(string $value): int
{
    $value = trim($value);
    $last = strtolower($value[-1]);
    $num = (int) $value;

    return match ($last) {
        'g' => $num * 1024 * 1024 * 1024,
        'm' => $num * 1024 * 1024,
        'k' => $num * 1024,
        default => $num,
    };
}

Zusammenfassung: Checkliste für sicheres Upload-Handling

  • Überprüfung von Fehlercodes mit UPLOAD_ERR_*
  • MIME-Typ wird auf der Serverseite mit <code>finfo</code> bestimmt
  • Validierung der Dateierweiterung mit Whitelist-Methode
  • Dateigröße mit <code>$_FILES['userfile']['size']</code> überprüfen
  • Das Speicherziel ist außerhalb des Verzeichnisses public konfiguriert
  • Überprüfung von legitimen Uploads mit <code>is_uploaded_file()</code>
  • Datei wird mit <code>move_uploaded_file()</code> verschoben
  • Generierung von Dateinamen mit <code>uniqid()</code> oder <code>random_bytes()</code>
  • <code>upload_max_filesize</code> und <code>post_max_size</code> in php.ini ordnungsgemäß konfiguriert

Testdatei zur Verwendung in diesem Artikel

  • <a href="/ja/files/threshold/" class="text-primary-600 dark:text-primary-400 hover:underline">Grenzwert-Testdateiliste</a> — Ideal zur Überprüfung der Werte um die <code>upload_max_filesize</code>-Einstellung
  • <a href="/ja/files/threshold/10mb/" class="text-primary-600 dark:text-primary-400 hover:underline">10 MB Grenzwert-Testsatz</a> — 3 Dateien: genau am Limit, davor und danach
  • <a href="/ja/files/broken/" class="text-primary-600 dark:text-primary-400 hover:underline">Liste beschädigter und ungültiger Dateien</a> — Testen Sie die Validierung mit gefälschtem MIME-Typ und gefälschter Dateierweiterung
  • <a href="/ja/files/images/png/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG-Testbilderliste</a> — Zur MIME-Typ-Validierung beim Bild-Upload

Verwandte Artikel

  • <a href="/ja/blog/how-to-test-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">So testen Sie die Datei-Upload-Grenze korrekt</a>
  • <a href="/ja/blog/file-validation-checklist/" class="text-primary-600 dark:text-primary-400 hover:underline">Checkliste zur Implementierung der Dateivalidierung in Webformularen</a>
  • <a href="/ja/blog/multipart-form-data-overhead/" class="text-primary-600 dark:text-primary-400 hover:underline">Den Overhead von multipart/form-data genau berechnen</a>
  • <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Zusammenfassung der Datei-Upload-Limits für AWS S3 und CloudFront</a>

Häufig gestellte Fragen

Was ist der Standardwert für das Dateigrößen-Upload-Limit in PHP?

Der Standardwert für upload_max_filesize in php.ini ist 2MB. post_max_size ist auch auf 8MB eingestellt.

Wie lade ich mehrere Dateien gleichzeitig in PHP hoch?

Fügen Sie das Attribut <code>multiple</code> zum <code>input</code>-Tag hinzu und setzen Sie das Attribut <code>name</code> in Array-Format (<code>files[]</code>). Sie können es als Array mit <code>$_FILES['files']</code> empfangen.

Worauf sollte man bei der Sicherheit beim Datei-Upload in PHP achten?

Es ist wichtig, die Dateitypen mit einer Whitelist zu validieren, den MIME-Typ mit <code>finfo_file()</code> zu bestätigen und Uploads außerhalb des Document Root zu speichern.