@9ZT8pTBJNuqS

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

セッション関数を使わずにファイルアップロード機能を設置するためにはどうすればいいでしょうか?

解決したいこと

WordPressでキャッシュ機能を使う際にセッション関数は推奨されていないため別の方法で添付ファイルを保存したい

発生している問題・エラー

ファイルアップロードが可能な掲示板を作成しているのですが、コードを書いた後に $_SESSION が WordPress で非推奨だと知りどのように修正すべきか悩んでおります。

該当するソースコード

function bbs_answer_confirm()
{
    // 統一ユーザーIDを取得
    $user_id = $_COOKIE['user_id'] ?? null;

    if (!$user_id) {
        wp_send_json_error(['message' => 'ユーザーIDが見つかりません']);
        exit;
    }

    // トランジェントからデータ取得
    $transient_key = 'bbs_answer_' . $user_id;
    $answer_data = get_transient($transient_key);

    if (!$answer_data) {
        wp_send_json_error(['message' => '投稿データが見つかりません']);
        exit;
    }

    // データベースへの保存処理
    global $wpdb;

    // 親質問のIDを取得
    $unique_id = $answer_data['unique_id'];
    $sql = "SELECT * FROM {$wpdb->prefix}sortable WHERE unique_id = %s";
    $query = $wpdb->prepare($sql, $unique_id);
    $rows = $wpdb->get_results($query);

    if (empty($rows)) {
        wp_send_json_error(['message' => '親質問が見つかりません']);
        exit;
    }

    $parent_id = $rows[0]->id;

    // 回答をデータベースに挿入
    $sql = "INSERT INTO {$wpdb->prefix}sortable(parent_id, text, name, ip, user_id) VALUES(%d, %s, %s, %s, %s)";
    $query = $wpdb->prepare(
        $sql,
        $parent_id,
        $answer_data['text'],
        $answer_data['name'],
        $_SERVER['REMOTE_ADDR'],
        $user_id // 投稿者のユーザーIDも保存
    );

    $query_result = $wpdb->query($query);

    if ($query_result === false) {
        wp_send_json_error(['message' => '投稿に失敗しました: ' . $wpdb->last_error]);
        exit;
    }

    $new_post_id = $wpdb->insert_id;

    // 添付ファイルの処理
    if (!empty($answer_data['attach'])) {
        $upload_dir = wp_upload_dir();
        $filenames = [];

        // 新しい投稿のunique_idを取得
        $sql = "SELECT unique_id FROM {$wpdb->prefix}sortable WHERE id = %d";
        $query = $wpdb->prepare($sql, $new_post_id);
        $new_unique_id = $wpdb->get_var($query);

        foreach ($answer_data['attach'] as $i => $file_data) {
            $type = explode('/', $file_data['type']);
            $ext = $type[1] ?? 'tmp';

            if ($i == 3) {
                $n = 'usericon';
            } else {
                $n = $i + 1;
            }

            $filename = "{$new_unique_id}_{$n}.{$ext}";
            $filenames[$i] = $filename;
            $attach_path = $upload_dir['basedir'] . '/attach/' . $filename;

            file_put_contents($attach_path, $file_data['data']);
        }

        // ファイル名をデータベースに更新
        $sql = "UPDATE {$wpdb->prefix}sortable SET attach1=%s, attach2=%s, attach3=%s, usericon=%s WHERE id=%d";
        $query = $wpdb->prepare(
            $sql,
            $filenames[0] ?? '',
            $filenames[1] ?? '',
            $filenames[2] ?? '',
            $filenames[3] ?? '',
            $new_post_id
        );
        $wpdb->query($query);
    }

    // 成功時はトランジェントを削除
    delete_transient($transient_key);

    wp_send_json_success(['message' => '回答が投稿されました']);
    exit;
}
add_action('wp_ajax_bbs_answer_confirm', 'bbs_answer_confirm');
add_action('wp_ajax_nopriv_bbs_answer_confirm', 'bbs_answer_confirm');

自分で試したこと

AIに聞いたところトランジェントを使用した方法を教えてくれた為そちらを実装してみたのですが、ファイルがアップロードされた場合の $answer_data['attach'] にバイナリ(file_get_contents)の大きなデータが含まれており失敗しているようです。

AIが教えてくれたコード

/* 回答タイトルとスタンプ画像なし(回答掲示板) */
function bbs_answer_confirm()
{
    // 統一ユーザーIDを取得
    $user_id = $_COOKIE['user_id'] ?? null;

    if (!$user_id) {
        wp_send_json(['error' => 'ユーザーIDが見つかりません']);
        exit;
    }

    // トランジェントからデータ取得
    $transient_key = 'bbs_answer_' . $user_id;
    $answer_data = get_transient($transient_key);

    if (!$answer_data) {
        wp_send_json(['error' => '投稿データが見つかりません']);
        exit;
    }

    // データベースへの保存処理
    global $wpdb;

    // 親質問のIDを取得
    $unique_id = $answer_data['unique_id'];
    $sql = "SELECT * FROM {$wpdb->prefix}sortable WHERE unique_id = %s";
    $query = $wpdb->prepare($sql, $unique_id);
    $rows = $wpdb->get_results($query);

    if (empty($rows)) {
        wp_send_json(['error' => '親質問が見つかりません']);
        exit;
    }

    $parent_id = $rows[0]->id;

    // 回答をデータベースに挿入
    $sql = "INSERT INTO {$wpdb->prefix}sortable(parent_id, text, name, ip, user_id) VALUES(%d, %s, %s, %s, %s)";
    $query = $wpdb->prepare(
        $sql,
        $parent_id,
        $answer_data['text'],
        $answer_data['name'],
        $_SERVER['REMOTE_ADDR'],
        $user_id // 投稿者のユーザーIDも保存
    );

    $query_result = $wpdb->query($query);

    if ($query_result === false) {
        wp_send_json(['error' => '投稿に失敗しました: ' . $wpdb->last_error]);
        exit;
    }

    $new_post_id = $wpdb->insert_id;

    // 添付ファイルの処理
    if (!empty($answer_data['attach'])) {
        $upload_dir = wp_upload_dir();
        $filenames = [];

        // 新しい投稿のunique_idを取得
        $sql = "SELECT unique_id FROM {$wpdb->prefix}sortable WHERE id = %d";
        $query = $wpdb->prepare($sql, $new_post_id);
        $new_unique_id = $wpdb->get_var($query);

        foreach ($answer_data['attach'] as $i => $file_data) {
            $type = explode('/', $file_data['type']);
            $ext = $type[1] ?? 'tmp';

            if ($i == 3) {
                $n = 'usericon';
            } else {
                $n = $i + 1;
            }

            $filename = "{$new_unique_id}_{$n}.{$ext}";
            $filenames[$i] = $filename;
            $attach_path = $upload_dir['basedir'] . '/attach/' . $filename;

            file_put_contents($attach_path, $file_data['data']);
        }

        // ファイル名をデータベースに更新
        $sql = "UPDATE {$wpdb->prefix}sortable SET attach1=%s, attach2=%s, attach3=%s, usericon=%s WHERE id=%d";
        $query = $wpdb->prepare(
            $sql,
            $filenames[0] ?? '',
            $filenames[1] ?? '',
            $filenames[2] ?? '',
            $filenames[3] ?? '',
            $new_post_id
        );
        $wpdb->query($query);
    }

    // 成功時はトランジェントを削除
    delete_transient($transient_key);

    wp_send_json(['error' => '']);
    exit;
}
0 likes

2Answer

WordPressはあまり触らないので質問への回答はしませんが、画像アップロード機能はセキュアである必要があります。

ちゃんと見てませんが少なくとも
$user_id = $_COOKIE['user_id'] ?? null;
は致命的問題である可能性が高いです。

一つ間違うだけでサーバが乗っ取られる機能なので、ちゃんとわかった人に作ってもらってください。

1Like

Comments

  1. @9ZT8pTBJNuqS

    Questioner

    アドバイスありがとうございます。
    AIに聞いたところ $user_id = $_COOKIE['user_id'] ?? null; が即サーバ乗っ取りにつながるほどの致命的セキュリティリスクではないが、実装次第で深刻な脆弱性(例:ユーザーなりすまし・任意操作・データ破壊)になる可能性があるとのことでした。
    ログインシステムは後々実装予定なので、対策①から③まで修正いたします。

    ❗ セキュリティ面での懸念点(致命的ではないが危険)
    ⚠ 1. 信頼できないユーザー入力をそのまま使っている
    $_COOKIE はクライアント(=ユーザー側)が自由に書き換え可能です。

    悪意あるユーザーが、他人の user_id を偽装すれば、**「他人の投稿を乗っ取る・編集する・いいねを消す」**といった挙動に発展しかねません。

    ✔ 対策①:必ず sanitize_text_field() や esc_sql() で無害化する

    $user_id = sanitize_text_field($_COOKIE['user_id'] ?? '');

    ✔ 対策②:UUIDであることを検証する

    if (!preg_match('/^[0-9a-f-]{36}$/', $user_id)) {
    wp_send_json_error(['message' => '不正なユーザーID']);
    exit;
    }

    ✔ 対策③:UUIDの発行を自サーバで制御

    if (!isset($_COOKIE['user_id'])) {
    $user_id = wp_generate_uuid4();
    setcookie('user_id', $user_id, ...);
    $_COOKIE['user_id'] = $user_id;
    }

    ✔ 対策④:ログイン済ユーザーなら WP の get_current_user_id() を使う
    ログインしているユーザーには Cookie ベースではなく WordPress 標準のユーザー識別を。

  2. コメントを見て思ったのですが「WordPressでキャッシュ機能を使う際にセッション関数は推奨されていない」の理由を全く理解していないですよね?
    繰り返しますが、ちゃんとわかった人に作ってもらってください。
    WebShellを置かれた後では取り返しがつかないです。

  3. @9ZT8pTBJNuqS

    Questioner

    回答ありがとうございます。
    AIに任せることですべて解消できると勘違いしておりましたもう一度勉強してみます。

  4. 余談となりますが、本件はいわゆるXY問題になっています。
    質問にあるキャッシュ機能は「コンテンツキャッシュ」のことだと思いますが、このキャッシュの設計が間違っている可能性が高いので、有識者に要件定義から見直してもらってください。

  5. @9ZT8pTBJNuqS

    Questioner

    回答ありがとうございます。
    有識者に見直して頂きます。

Comments

  1. @9ZT8pTBJNuqS

    Questioner

    回答ありがとうございます。
    該当するソースコードの1つ前にセッション関数を使ったコードを書いておりそちらと間違えておりました申し訳りません。
    AIに再度聞いたところトランジェントにファイル本体(バイナリ)を保存していること自体が致命的とのことでした。

    ❗主な原因
    WordPressの set_transient() は内部的に オプションテーブル(wp_options)にシリアライズして保存します。
    → ファイルの バイナリデータ をそのまま保存すると、データ量が大きすぎる(5MBなど)場合、失敗する

    PHPの serialize() がファイルバイナリをうまく処理できず、取得時に失敗する(= get_transient() が false)

  2. 該当するソースコードの1つ前にセッション関数を使ったコードを書いておりそちらと間違えて

    間違えたのは別に構わないのですが、相当するソースコードを提示してもらわないと私を含めて他者が答えにくいです。

    ファイルの バイナリデータ をそのまま保存

    データベースに画像データを直接保存するという意味合いでしたら、確実にDBへのパフォーマンスに影響します。止めるべき

    普通は投稿が確定されるまでは一時的な場所に保存し、確定してから移動させます。

  3. @9ZT8pTBJNuqS

    Questioner

    アドバイスありがとうございます、file_get_contents 関数を含んだコードは下記になります。
    投稿が確定されるまでは一時的な場所に保存する方法に修正致します。
    chatgpt が書いたコードをそのまま使うのではなく一度調べて考えてから活用するようにしてみます。

    /* 回答タイトルとスタンプ画像なし(回答掲示板) */
    function bbs_answer_confirm()
    {
    $user_id = $_COOKIE['user_id'] ?? null;

    if (!$user_id) {
        wp_send_json(['error' => 'ユーザーIDが見つかりません']);
        exit;
    }
    
    $tmp_dir = sys_get_temp_dir();
    $file_path = $tmp_dir . "/bbs_answer_{$user_id}.json";
    
    if (!file_exists($file_path)) {
        wp_send_json(['error' => '投稿データが見つかりません']);
        exit;
    }
    
    $answer_data = json_decode(file_get_contents($file_path), true);
    
    if (!$answer_data || empty($answer_data['unique_id'])) {
        wp_send_json(['error' => 'データ形式が不正です']);
        exit;
    }
    
    global $wpdb;
    $unique_id = $answer_data['unique_id'];
    
    // 親投稿の取得
    $sql = "SELECT * FROM {$wpdb->prefix}sortable WHERE unique_id = %s";
    $query = $wpdb->prepare($sql, $unique_id);
    $rows = $wpdb->get_results($query);
    
    if (empty($rows)) {
        wp_send_json(['error' => '親質問が見つかりません']);
        exit;
    }
    
    $parent_id = $rows[0]->id;
    
    // 投稿挿入
    $sql = "INSERT INTO {$wpdb->prefix}sortable (parent_id, text, name, ip, user_id) VALUES (%d, %s, %s, %s, %s)";
    $query = $wpdb->prepare(
        $sql,
        $parent_id,
        $answer_data['text'] ?? '',
        $answer_data['name'] ?? '',
        $_SERVER['REMOTE_ADDR'],
        $user_id
    );
    $result = $wpdb->query($query);
    
    if ($result === false) {
        wp_send_json(['error' => '投稿に失敗しました: ' . $wpdb->last_error]);
        exit;
    }
    
    $new_id = $wpdb->insert_id;
    
    // unique_id の再取得
    $new_unique_id = $wpdb->get_var($wpdb->prepare(
        "SELECT unique_id FROM {$wpdb->prefix}sortable WHERE id = %d",
        $new_id
    ));
    
    // 添付ファイル処理
    if (!empty($answer_data['attach'])) {
        $upload_dir = wp_upload_dir();
        $filenames = [];
    
        foreach ($answer_data['attach'] as $i => $file_data) {
            $type = explode('/', $file_data['type']);
            $ext = $type[1] ?? 'tmp';
    
            $n = ($i == 3) ? 'usericon' : $i + 1;
            $filename = "{$new_unique_id}_{$n}.{$ext}";
            $filenames[$i] = $filename;
    
            file_put_contents($upload_dir['basedir'] . '/attach/' . $filename, $file_data['data']);
        }
    
        // 添付ファイル情報をDB更新
        $sql = "UPDATE {$wpdb->prefix}sortable SET attach1=%s, attach2=%s, attach3=%s, usericon=%s WHERE id=%d";
        $query = $wpdb->prepare(
            $sql,
            $filenames[0] ?? '',
            $filenames[1] ?? '',
            $filenames[2] ?? '',
            $filenames[3] ?? '',
            $new_id
        );
        $wpdb->query($query);
    }
    
    // 一時ファイル削除
    unlink($file_path);
    
    wp_send_json_success(['message' => '回答が投稿されました']);
    exit;
    

    }
    add_action('wp_ajax_bbs_answer_confirm', 'bbs_answer_confirm');
    add_action('wp_ajax_nopriv_bbs_answer_confirm', 'bbs_answer_confirm');

Your answer might help someone💌