ユーザーに画像を投稿してもらうWebアプリを作るとき、画像の受け取りから変換、保存まで、全部をアプリサーバーに任せたくなる。でも、そのサーバーにはログイン情報や業務用のデータベースへ接続する権限もある。外部から届く画像を処理する場所として、どこまで役割を持たせるかは考えておきたい。
その選択肢の一つが、ブラウザからAmazon S3へ画像を直接アップロードする構成。アプリサーバーは投稿する人の権限や保存先を管理し、画像本体はブラウザとS3の間でやり取りする。
この記事は、2026年11月1日時点のAmazon S3の汎用バケットとAWS Signature Version 4を前提にした設計の解説。特定のOS、SDK、Webフレームワークには依存しない。外部の保存先へ画像を預ける構成なので、端末内だけで完結する画像加工ツールとは用途が異なる。
Gyazoの事例から、処理する場所と権限を考える
2026年9月11日に発生したGyazoへの不正アクセスについて、運営元のHelpfeelは10月21日に調査結果を公表している。原因は、アップロードされた動画などの変換処理で、外部入力がサーバー上のコマンドとして解釈されてしまう脆弱性だった。攻撃者は変換処理用サーバー上でコマンドを実行し、そこから到達できたデータベースへ接続したと報告されている。Helpfeelの公式調査報告
ここから設計上の教訓として考えたいのは、外部のファイルを処理する場所から、何にアクセスできるのかという点。直接アップロードを取り入れるときも、保存経路の変更と合わせて、画像の解析・変換処理に与える権限を見直したい。
画像本体はブラウザからS3へ送る
アプリサーバーで受け取ってからS3へ送る構成では、画像本体が次の順番で移動する。
ブラウザ → アプリサーバー → S3
直接アップロードでは、この画像の転送経路を短くする。
ブラウザ → S3
ただし、誰でも自由にS3へ書き込める状態にはしない。画像を送る前にアプリサーバーが権限を確認して、指定した保存先へのアップロードを一時的に許可するURLを発行する。これが「署名付きURL」を使う方法だ。AWS:署名付きURLを使用したアップロード
sequenceDiagram
accTitle: 署名付きURLによる画像の直接アップロード
accDescr: アプリサーバーがアップロードを許可し、画像本体はブラウザからS3へ送る。サーバーは保存結果を確認して受信済みと記録する。
participant B as ブラウザ
participant A as アプリサーバー
participant S as S3
B->>A: ① アップロード許可を申請
Note right of A: ログイン・権限確認
A-->>B: ② 署名付きURLを返す
B->>S: ③ 画像本体を直接送信
B->>A: ④ 完了を通知
A->>S: ⑤ 存在・サイズを照会
S-->>A: 確認結果を返す
Note right of A: ⑥ DBへ「受信済み」と記録
図の⑤では、アプリサーバーからS3のHeadObjectを呼び出せる。このAPIは画像本体を返さず、存在やサイズなどの情報を確認するもの。アップロード完了の確認のために、画像をアプリサーバーへダウンロードする必要はない。AWS:HeadObject
署名付きURLは、操作と期限を絞った許可証
例えば、アプリサーバーが次の条件を持つURLを発行する。
| 項目 | この設計での例 |
|---|---|
| 保存先 | 非公開の画像保存用バケット |
| オブジェクトキー | サーバーが決めた、今回のアップロード専用のランダムな名前 |
| 許可する操作 | そのキーへのPUTによるアップロード |
| 有効期間 | 5分間 |
オブジェクトキーは、S3の中でファイルを識別する名前のこと。5分はこの記事での設計例で、S3の固定値や既定値ではない。
署名には、発行元が持つAWSの権限を使う。ブラウザに秘密アクセスキーを配らずに、決めた対象への操作を委ねられる。ただし、URLを持っている人が、その許可を使える。URLを公開ページや解析ツールのログへ不用意に残さず、発行元の権限も必要な保存先と操作に絞る。一時的な認証情報で発行した場合は、その認証情報の期限が先に切れると、URLも設定した期限より早く失効する。AWS:署名付きURLの権限と有効期間
アップロード用URLと閲覧用URLは、許可する操作が異なる。画像を送信できるURLを発行しても、それだけで閲覧を許可したことにはならない。
アプリサーバーには、権限と状態の管理を残す
画像本体を受け取らなくても、アプリサーバーの仕事はある。例えばプロフィール画像の登録なら、次のように役割を決められる。
1. 保存先を決め、アップロード待ちの記録を作る
開始時に、ログイン中のユーザーが画像を登録できるかを確認する。他人のプロフィールに画像を登録できないように、操作対象への権限も確かめる。
保存先のキーとアップロードIDはサーバー側で発行し、そのユーザーと結び付ける。ブラウザから届くユーザーIDや保存先を、そのまま採用する設計にはしない。元のファイル名は必要なら表示用の情報として保持し、保存先の識別子とは分けて扱う。
2. 完了通知を受けて、保存された事実を確認する
ブラウザが「終わりました」と通知してきたら、アップロードIDが本人のものか、まだ有効な処理かを確認する。そのうえで、サーバーが発行時に記録したキーを使ってS3を照会する。
ここで確認するのは、予定した場所にファイルがあり、サイズなどが受け入れ条件に合うこと。存在やサイズを確認できても、ファイルが安全な画像だと判定したことにはならない。 Content-Typeも送信者が指定できる情報なので、それだけで中身を信頼しない。OWASP:ファイル形式とContent-Typeの検証
そのため、受信の確認と、実際に利用・配信してよいという判断を分ける。処理が必要なアプリなら、例えば次の状態を持たせる。
| 状態 | 意味 |
|---|---|
| アップロード待ち | 許可を発行し、送信を待っている |
| 受信済み・確認中 | S3への保存を確認し、必要な検査・変換を進めている |
| 利用可能 | 受け入れ条件を満たし、投稿や表示に使用できる |
| 失敗・期限切れ | 利用せず、不要なファイルを後で片付ける |
これはアプリ側で設計する状態の例。S3へ保存された時点で、投稿への紐付けや一般公開まで自動的に完了させる必要はない。
3. 閲覧時にも権限を確認する
保存先は非公開を基本にし、アプリ側で「この人がこの画像を見てよいか」を判断する。許可した場合に、閲覧用の短期間の署名付きURLを発行すれば、画像の表示もブラウザとS3の間で行える。AWS:パブリックアクセスのブロック、AWS:署名付きURLによるアクセス
閲覧用の署名付きURLも、受け取った人が有効期間中に共有できる。秘密の画像では、URLの期限や配信先のキャッシュの扱いも、閲覧権限の設計に含める。
容量制限とブラウザ側のチェックをどう扱う?
画像を選ぶ画面で、拡張子やサイズを確認するのは便利だ。大きすぎるファイルを選んだユーザーへ、その場で案内できる。
ただ、攻撃者は画面のJavaScriptを通らずにアップロードできる。ブラウザ内で画像を圧縮・再生成する設計でも、保存先には元のファイルや別の内容が送られる可能性がある。アプリや保存先で強制する条件を、別に用意しておく。OWASP:アップロードの保護と容量制限
例えばS3では、署名付きPOSTのポリシーにcontent-length-rangeを設定して、受け入れるファイルサイズの範囲を制限する方法がある。これは、ここまで図で説明した署名付きPUTとは送信方式が異なり、ブラウザは指定されたフォームフィールドとファイルをPOSTする。AWS:POST Policy
「申請時に10 MiB以下と申告されたからURLを発行した」という確認だけでは、実際の送信サイズを制限したことにはならない。また、保存後に上限超過を見つけて削除する方式では、受信そのものは発生する。受信前に容量を制限したいのか、保存後の条件違反を検出したいのかを決めて、方式を選ぶ。
利用者ごとの保存容量や件数、許可を発行する頻度も管理する。途中で放置されたアップロードや、期限切れの記録とファイルを片付ける運用も必要になる。
署名付きURLは一回きりではない
署名付きURLは、有効期間内であれば再利用できる。通常のアップロードでは、同じキーへ再び送信すると既存の内容が置き換わる場合がある。そのため、毎回ランダムなキーを作ることに加え、確認した後に同じキーの中身を差し替えられないかも考える。AWS:署名付きURLの再利用と上書き
PUT方式なら、S3の条件付き書き込みでIf-None-Match: *を必須にして、既存のオブジェクトがある場合に書き込みを失敗させる方法がある。この条件をブラウザ側だけの約束にせず、署名やバケットポリシーで強制する設計にする。AWS:条件付き書き込み
これは、保存済みオブジェクトへの上書きを防ぐための仕組み。削除後の再作成まで含む永久的な使い捨てトークンにはならない。発行済みURLの期限が残っている間の削除・再送や、検査結果と配信対象が一致するかも含めて扱う。POST方式にもPUT方式と同じ条件をそのまま適用できるとは限らないため、容量制限と上書き対策を合わせて選びたい。
画像を解析・変換する環境を分離する
サムネイルや圧縮画像が必要なら、その画像をどこかで読み取る処理は残る。直接アップロード後にアプリ本体がS3から画像を取得して変換すれば、その処理は本体側の攻撃対象になる。
対策としては、画像処理専用のワーカーや隔離した実行環境を用意し、必要なファイルだけを読み書きさせる設計が考えられる。アプリ本体のDB認証情報を渡さず、DBへのネットワーク接続も制限する。変換ライブラリを更新し、処理する形式、メモリ、CPU、処理時間、展開後の画素数などにも上限を設ける。OWASP:画像の再生成と処理環境の制限
アプリへの通知は、例えば「アップロードIDに対応する変換が成功し、この出力ができた」という必要な情報に絞れる。ファイル処理側の権限と通信先を限定することで、問題が起きたときに触れられる範囲を小さくする。
画像を公開する場合には、閲覧する側への影響もある。必要な画像形式だけを許可し、特にスクリプトなどの能動的な内容を扱える形式の配信方法は慎重に決める。保存先を分ける構成と、利用者へ安全に配信する設計は、両方が必要になる。OWASP:悪意のあるファイルによる脅威
ブラウザから送信するためにCORSを設定する
WebアプリとS3のドメインが違う場合、ブラウザからの直接送信にはS3側のCORS設定も必要になる。自分のWebアプリのオリジン、利用するPUTやPOSTなどのメソッド、必要なヘッダーを許可する。AWS:S3のCORS
CORSはブラウザから別オリジンへアクセスするための仕組み。利用者の権限を確認するのはアプリ側の認可や、S3側の署名・ポリシーの役割になる。CORSを有効にしても、S3のアクセス制御は引き続き適用される。
送信時に失敗した場合の調べ方は、「S3直接アップロードでCORSエラー・403が出たときの調べ方」にまとめた。Network画面でOPTIONSとPUTを分け、CORS設定・署名・権限を順番に確認する。
署名付きURLの発行、保存結果の確認、閲覧許可はアプリサーバーへ、画像本体の転送と保存はS3へ、必要な画像処理は権限を絞った環境へ。それぞれの仕事と触れられる情報を決めておくと、画像を扱う機能を追加するときにも構成を見直しやすくなる。