awesome-hacks
Docs

GETとPOSTのセキュリティと使い分け

「セキュリティのためにPOSTを使う」という考え方の実態と誤解を整理し、HTTPメソッド選択の正しい基準と具体的なセキュリティ対策を理解する

最終更新:2026/06/16

TL;DR

  • 「GETだとキャッシュされるからPOSTにする」は誤った対策であることが多い。Cache-Control ヘッダーを正しく設定すれば GET でもキャッシュを防げる
  • GETとPOSTの選択基準は**セキュリティではなく HTTP の意味論(副作用の有無)**に基づくべき
  • センシティブな値(パスワード・トークン)を URL に含めること自体が問題であり、POST にすれば解決するわけではない
  • 実際に必要な対策は「適切なHTTPヘッダー」「HTTPS」「ロギングの管理」の組み合わせ

目的

Web開発では「GETはキャッシュされるからセキュリティ上危険、機密情報を扱う操作はPOSTにすべき」という考え方が広まっている。

この考え方には正しい部分もあるが、単純に GET を POST に置き換えるだけでは解決しない問題も多い。

このページでは、GETのキャッシュに関するセキュリティリスクの実態を整理した上で、HTTPメソッドを正しく選択するための基準と、本当に有効なセキュリティ対策を説明する。


前提知識

HTTP メソッドの基本的な意味

メソッド意味安全性冪等性
GETリソースの取得安全(副作用なし)冪等
POSTリソースの作成・処理の実行安全でない非冪等
PUTリソースの全体更新安全でない冪等
DELETEリソースの削除安全でない冪等
  • 安全(Safe): サーバーの状態を変更しない操作。何度呼んでもリソースに副作用がない
  • 冪等(Idempotent): 同じリクエストを何度送っても結果が変わらない

キャッシュが発生する場所

HTTPのキャッシュは複数の場所で発生する。

クライアント(ブラウザ)
  ↓
ブラウザキャッシュ(ローカル)
  ↓ HTTPS
プロキシ / CDN / リバースプロキシ
  ↓
サーバー
場所主体何をキャッシュするか
ブラウザキャッシュブラウザ自身URL単位でレスポンスを保存
プロキシキャッシュ企業・ISPのプロキシ複数ユーザーで共有されることがある
CDNCloudfront / Cloudflareなど地理的に分散したキャッシュ
サーバー側キャッシュRedis・Memcachedなどアプリ側で制御

HTTPS(TLS)の役割

HTTPS は通信経路を暗号化する。GET であれ POST であれ、HTTPS を使っていれば 通信内容(URLのパス・クエリ・ボディ)はすべて暗号化 される。

HTTPS 使用時の通信(暗号化される範囲):
  ホスト名以外のすべて(パス・クエリ・ヘッダー・ボディ)

HTTPS 非使用時:
  すべてが平文で流れる(GETもPOSTも関係なく危険)

GET のリスクと誤解

GETが危険とされる具体的な理由を一つひとつ確認する。

1. ブラウザキャッシュに残る

GETのレスポンスは、Cache-Control ヘッダーが適切に設定されていないとブラウザにキャッシュされる。

実態

  • これは POST でも同様に制御できる問題。Cache-Control: no-store を付ければ GET でもキャッシュされない
  • POST レスポンスも Cache-Control ヘッダー次第ではキャッシュされる(RFC7234)
HTTP/1.1 200 OK
Cache-Control: no-store, no-cache

このヘッダーを付ければ GET でもブラウザキャッシュに残らない。

2. ブラウザの履歴に URL が残る

GET のリクエストは URL が完全にブラウザ履歴に記録される。?token=abc123 のようなセンシティブな値が URL にある場合、共有端末では漏洩リスクになる。

実態

  • これは「センシティブな値をクエリパラメータに含めること」自体が問題
  • POST にしても履歴には残らないが、「URLにセンシティブな値を含まない」という設計が本質的な解決策

3. サーバーのアクセスログに URL が記録される

多くのWebサーバー(nginx・Apacheなど)はデフォルトで URL をアクセスログに出力する。

# nginx のデフォルトログ(access.log の例)
192.168.1.1 - - [16/Jun/2026:10:00:00 +0900] "GET /reset?token=abc123 HTTP/1.1" 200 512

この場合、ログにトークンが平文で残る。

実態

  • POST でもボディをログに出力するよう設定されている場合は同様に残る
  • 逆に GET でも、URL にセンシティブな値を含めなければ問題にならない
  • APMツールやミドルウェアがボディを自動記録するケースもあり、POST でも安全とは言いきれない

4. Referer(リファラー)ヘッダーに含まれる

ページ内に外部サイトへのリンクがある場合、ブラウザはリンク元の URL を Referer ヘッダーに付けてリクエストを送る。

GET https://external-site.example.com/something
Referer: https://myapp.example.com/search?query=secret_value

実態

  • Referrer-Policy ヘッダーで制御可能
  • POST でも、フォーム submit 後の遷移先ページで document.referrer にリクエスト元が残ることがある

POST にすれば解決するのか

リスクGETでの状況POSTにした場合正しい解決策
ブラウザキャッシュキャッシュされやすいデフォルトではキャッシュされにくいCache-Control: no-store
ブラウザ履歴URLが残る残らないURLにセンシティブな値を入れない
サーバーログURLが記録されるボディは通常記録されない(設定次第)ログ設定を見直す / URLに機密値を含めない
Refererクエリが漏洩する漏洩しないReferrer-Policy: no-referrer
通信盗聴HTTPS必須HTTPS必須(POSTでも同じ)HTTPS を必ず使う

POSTは「ブラウザ履歴に残らない」「通常ログにボディが出ない」という点で一定の優位性はある。しかし POST に変えるだけでは解決しないリスクも多く、POST で安心してしまうことで本来すべき対策が抜け落ちるリスクがある。


HTTPメソッドの選択基準

GETとPOSTはセキュリティのためではなく、操作の意味で選ぶのが正しい。HTTP仕様(RFC7231)での定義に基づく。

GETを使うべき場合

  • データの取得(副作用がない操作)
  • 冪等な操作(何度実行しても結果が変わらない)
  • ブックマーク・共有ができる必要がある
  • キャッシュを活かしてパフォーマンスを上げたい
GET /users/123           → ユーザー情報の取得
GET /search?q=typescript → 検索
GET /products?page=2     → 一覧取得

POSTを使うべき場合

  • リソースの作成(新規登録、送信など)
  • 副作用がある操作(決済、メール送信など)
  • 冪等でない操作(同じリクエストを2回送ったら2件作成されてしまうもの)
  • リクエストボディが大きい(URLの文字数制限を超える場合)
POST /users              → ユーザーの新規作成
POST /orders             → 注文の確定
POST /messages           → メッセージの送信

「セキュリティのためにPOST」の誤用例

// ❌ 誤用:検索なのにPOSTにしている(キャッシュを避けたいだけ)
POST /api/search
Content-Type: application/json
{ "query": "typescript" }

// ✓ 正しい:検索はGETで、Cache-Controlで制御する
GET /api/search?q=typescript
Cache-Control: no-store  // センシティブな検索結果ならここで制御

本当に必要なセキュリティ対策

1. Cache-Control ヘッダーの設定

キャッシュを防ぎたい場合は、メソッドに関わらず明示的にヘッダーを付ける。

Cache-Control: no-store
ディレクティブ意味
no-storeキャッシュに一切保存しない(最も強い)
no-cache必ずサーバーに再検証してから使う
privateブラウザのみキャッシュ可。プロキシには保存しない
public共有キャッシュ(CDN等)に保存を許可

センシティブなページ・APIレスポンスには no-store を付けることが基本。

2. センシティブな値を URL に含めない

メソッドに関わらず、パスワード・トークン・個人情報はURLに含めない。

// ❌ NG:トークンがURLに露出する
GET /reset-password?token=abc123

// ✓ OK:トークンをリクエストボディやヘッダーに含める
POST /reset-password
{ "token": "abc123", "newPassword": "..." }

// ✓ OK:認証トークンはAuthorizationヘッダーで送る
GET /me
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

3. HTTPS を必ず使う

GET・POST どちらであっても、HTTP(非暗号化)で通信している場合は通信内容がすべて平文で流れる。TLSは選択肢ではなく必須。

// HTTP(危険)
http://example.com/api/users    // URLもボディも平文

// HTTPS(安全)
https://example.com/api/users   // URLもボディも暗号化される

4. Referrer-Policy ヘッダーの設定

外部リンクがある場合、リファラー経由のURL漏洩を防ぐ。

Referrer-Policy: no-referrer-when-downgrade
意味
no-referrerRefererを一切送らない
no-referrer-when-downgradeHTTPS→HTTPの遷移時は送らない(デフォルト)
strict-originオリジンのみ送る(パスを送らない)
strict-origin-when-cross-origin同一オリジンはフル URL、クロスオリジンはオリジンのみ

5. サーバーログの管理

認証トークンや個人情報がアクセスログに残らないよう、ログの出力設定を見直す。

# nginx でクエリストリングを除いたログを出力する例
log_format masked '$remote_addr - $remote_user [$time_local] '
                  '"$request_method $uri" $status $body_bytes_sent';

access_log /var/log/nginx/access.log masked;

GETとPOSTの選択フロー

この操作は何か?
  ↓
リソースを取得するだけ(副作用なし)?
  Yes → GET を使う
        ↓
        センシティブなデータをレスポンスに含む?
          Yes → Cache-Control: no-store を付ける
          No  → キャッシュを活用してもよい
  ↓
  No(データを作成・変更・削除する / 副作用がある)→ POST/PUT/DELETE を使う
        ↓
        リクエストにセンシティブな値を含む?
          Yes → URLやクエリに含めず、ボディまたはヘッダーで送る
          No  → 通常の設計でよい

GETが適切な具体例

以下は「センシティブそうに見えるがGETが正解」なケース。

検索API

GET /api/articles?category=security&page=1
Cache-Control: no-store  // 個人化された検索結果の場合はキャッシュを防ぐ
Authorization: Bearer {jwt}  // 認証はヘッダーで

ユーザー自身の情報取得

GET /api/me
Authorization: Bearer {jwt}
Cache-Control: private, no-store

これらはデータを「取得するだけ」なのでGETが正しい。認証はAuthorizationヘッダーで行い、キャッシュはヘッダーで制御する。


よくある落とし穴

「POSTなら安全」という過信

POST にしても Cache-Control ヘッダーを付けなければ、一部のキャッシュサーバーはレスポンスをキャッシュする場合がある。また、ボディをログに記録するAPMツールや開発用ミドルウェアを本番に残してしまうと POST でも情報漏洩する。

CSRF との関係

POST は CSRF 攻撃を防ぐわけではない。状態を変更する操作(POST/PUT/DELETE)に対しては、CSRFトークンまたは SameSite Cookie の設定が別途必要。

GETを「読み取り専用(副作用なし)」に徹底することで、CSRF対策を POST 系のみに絞りやすくなるという副次的なメリットはある。

ブラウザバックの二重送信

POSTで決済・注文などを実行した後、ブラウザバックをしてリロードすると「フォームを再送しますか?」というダイアログが表示される。GET に変えれば問題はなくなるが、副作用のある操作を GET で実装することはできない。

正しい対処は PRG パターン(Post/Redirect/Get) を使うことで、POST 後に GET のページへリダイレクトし、リロードしても再送信されない構造にする。

POST /orders    → 注文処理(副作用あり)
↓ 302 Redirect
GET /orders/123 → 注文完了ページ(キャッシュ可)

まとめ

観点正しい考え方
GETはキャッシュされるCache-Control で制御できる。POST に変えるだけでは解決しない
センシティブな操作にはPOST副作用があるならPOSTが正しい。ただしキャッシュ対策は別途必要
URLに機密情報を含めたくないメソッドに関わらず、URLに機密値を含めない設計が基本
通信を安全にしたいHTTPS(TLS)を使う。これはGET/POSTに関係なく必須
ログに残したくないログ出力の設定を見直す。POSTでも記録されうる

HTTPメソッドの選択はセキュリティではなく操作の意味で決める。その上で、必要なセキュリティ対策(ヘッダー設定・HTTPS・ロギング管理)を別途適切に組み合わせるのが正しいアプローチ。


関連