REST のセキュリティ
REST Security Cheat Sheet
- 原典
- REST Security Cheat Sheet(OWASP Cheat Sheet Series)
- 原文
- GitHub 上の Markdown
- 底本
bac04fb5(2026-07-29 時点)- ライセンス
- CC BY-SA 4.0(原典と同一。訳文も同ライセンスで再配布できます)
はじめに
REST(REpresentational State Transfer)は、Roy Fielding の博士論文 Architectural Styles and the Design of Network-based Software Architectures で初めて記述されたアーキテクチャの様式である。
これは Fielding が HTTP/1.1 と URI の仕様を書く中で発展し、分散したハイパーメディアのアプリケーションの開発に適していることが示されてきた。 REST はより広く適用できるものであるが、最もよく使われるのは HTTP を介したサービスとの通信の文脈である。
REST における情報の主要な抽象はリソースである。 REST API のリソースは URI(通常は HTTP の URL)によって識別される。 REST の構成要素はコネクタを用いて、リソースの現在または意図された状態を捉える表現によってリソースに対する動作を行い、その表現を転送する。
主要なコネクタの種類はクライアントとサーバーであり、副次的なコネクタにはキャッシュ、リゾルバ、トンネルが含まれる。
REST API は状態を持たない。 状態を持つ API は REST のアーキテクチャ様式に従っていない。 REST という略語における「状態」は、API がアクセスするリソースの状態を指し、API が呼ばれるセッションの状態を指すのではない。 状態を持つ API を作る正当な理由もありうるが、セッションの管理は複雑であり、安全に行うのが難しいことを理解しておくことが重要である。
状態を持つサービスはこのチートシートの範囲外である。 状態をクライアントからバックエンドへ渡すことは、技術的にはサービスを状態のないものにするが、再送やなりすましの攻撃を受けやすいアンチパターンであり、これも避けるべきである。
REST API で処理の流れを実装するには、通常リソースの作成、読み取り、更新、削除を行う。 たとえば EC サイトは、空の買い物かごを作る、かごに商品を加える、かごを精算する、といったメソッドを提供しうる。 これらの REST の呼び出しはそれぞれ状態を持たず、エンドポイントは呼び出し側が要求された操作を行う認可を持つかを確認するべきである。
REST のアプリケーションのもう一つの重要な特徴は、サービス間の不必要な差異を取り除くことを目指して、標準の HTTP メソッドとエラーコードを使うことである。
さらにもう一つの重要な特徴は HATEOAS(Hypermedia As The Engine of Application State)の使用である。 これは REST のアプリケーションに自己記述的な性質を与え、事前の知識なしに開発者が REST のサービスと対話することを容易にする。
HTTPS
安全な REST のサービスは HTTPS のエンドポイントのみを提供しなければならない。 これは転送中の認証の資格情報(パスワード、API キー、JSON Web Token など)を保護する。 またクライアントがそのサービスを認証できるようにし、転送されるデータの完全性を保証する。
さらに詳しくはトランスポート層セキュリティを参照する。
高い権限を持つ Web サービスには、相互に認証されたクライアント側の証明書を使って追加の保護を与えることを検討する。
アクセス制御
非公開の REST のサービスは、各 API のエンドポイントでアクセス制御を行わなければならない。 一体型のアプリケーションにおける Web サービスは、利用者の認証、認可のロジック、セッション管理によってこれを実装する。 これは RESTful な様式に従う複数のマイクロサービスから構成される現代のアーキテクチャにとって、いくつもの難点を持つ。
- 遅延を最小にしサービス間の結合を減らすため、アクセス制御の判断は REST のエンドポイントがローカルに行うべきである
- 利用者の認証は、アクセストークンを発行するアイデンティティプロバイダ(IdP)に集約するべきである
JWT
セキュリティトークンの形式として JSON Web Token(JWT)を使う方向に収束しつつあるように見える。 JWT は、アクセス制御の判断に使えるクレームの集合を含む JSON のデータ構造である。 JWT の完全性の保護には、暗号による署名またはメッセージ認証コード(MAC)を使える。
- JWT が署名または MAC によって完全性を保護されている状態にする。保護されていない JWT(
{"alg":"none"})を許可しない。- こちらを参照する。
- 一般に、JWT の完全性の保護には MAC より署名を優先するべきである。
完全性の保護に MAC を使う場合、JWT を検証できるすべてのサービスが、同じ鍵を用いて新しい JWT を作ることもできる。 これは同じ鍵を使うすべてのサービスが互いを信頼しなければならないことを意味する。 またその帰結として、いずれか一つのサービスの侵害が、同じ鍵を共有する他のすべてのサービスの侵害にもなる。 さらに詳しくはこちらを参照する。
relying party(トークンの利用側)は、JWT の完全性と含まれるクレームを検証することでその JWT を検証する。
- relying party は、自身の設定またはコードに固定したロジックに基づいて JWT の完全性を検証しなければならない。検証のアルゴリズムを選ぶために JWT のヘッダの情報に依存してはならない。こちらとこちらを参照する。
一部のクレームは標準化されており、アクセス制御に使う JWT に含まれるべきである。 少なくとも次の標準のクレームを検証するべきである。
iss(発行者)。これは信頼された発行者か。署名鍵の想定される所有者か。aud(対象者)。relying party はこの JWT の対象者に含まれるか。exp(有効期限)。現在時刻はこのトークンの有効期間の終わりより前か。nbf(有効開始時刻)。現在時刻はこのトークンの有効期間の始まりより後か。
JWT は認証された実体(利用者など)の詳細を含むため、JWT と利用者のセッションの現在の状態のあいだにずれが生じうる。
たとえば明示的なログアウトや無操作によるタイムアウトによって、有効期限より早くセッションが終了した場合である。
明示的なセッション終了の事象が起きたときは、サーバーが発行した一意な識別子(jti クレーム。任意で aud と組み合わせる)を API の拒否リストへ登録し、そのトークンの有効期限まで、その JWT をあらゆるリクエストに対して無効にするべきである。
さらに詳しくは JSON Web Token を参照する。
API キー
アクセス制御のない公開の REST のサービスは、大量に利用されて帯域や計算資源の請求が膨らむリスクを負う。 API キーはこのリスクを緩和するのに使える。 また API で収益を得るために組織がしばしば使うものでもある。 高頻度の呼び出しを遮断するのではなく、購入したアクセスの計画に応じてクライアントにアクセスを与える。
API キーはサービス妨害攻撃の影響を減らせる。 ただしサードパーティのクライアントへ発行される場合、比較的容易に漏洩する。
- 保護されたエンドポイントへのすべてのリクエストで API キーを必須にする。
- リクエストが速すぎる場合は
429 Too Many Requestsの HTTP 応答コードを返す。 - クライアントが利用規約に違反したら API キーを失効させる。
- 機微な、重要な、あるいは価値の高いリソースの保護を API キーだけに依存しない。
HTTP メソッドを制限する
- 許可する HTTP メソッド(
GET、POST、PUTなど)の許可リストを適用する。 - 許可リストに一致しないリクエストは、すべて HTTP 応答コード
405 Method not allowedで拒否する。 - 呼び出し側が、そのリソースの集合、動作、レコードに対して、その HTTP メソッドを使う認可を持つ状態にする。
とくに Java EE では、これを適切に実装するのが難しいことがある。 このよくある設定の誤りの説明は Bypassing Web Authentication and Authorization with HTTP Verb Tampering を参照する。
API の順序を外れた実行の防止
現代の REST API はしばしば、一連のエンドポイント(作成 → 検証 → 承認 → 確定など)によって業務の流れを実装する。 バックエンドが流れの状態遷移を明示的に検証しないと、攻撃者はエンドポイントを順序を外れて呼び出し、意図された対策を回避しうる。
問題
順序を外れた API の実行は、攻撃者が次のことを行うときに生じる。
- 後段のエンドポイントを直接呼び出して、必要な段階を飛ばす
- 流れの境界をまたいでトークンを再送または再利用する
- フロントエンドが正しい順序を強制しているという前提を突く
各エンドポイントが個別に認証され認可されうるため、従来のアクセス制御の確認ではこれらの問題を検出できないことが多い。
例
ある精算の流れが次の順序を想定しているとする。
POST /checkout/create
POST /checkout/pay
POST /checkout/confirm
バックエンドが流れの状態遷移を検証しなければ、攻撃者は支払いを完了せずに次を直接呼び出せる。
POST /checkout/confirm
防止の指針
- すべてのリクエストについて、サーバー側で流れの状態の検証を強制する
- 有限の状態や状態機械を用いて流れを明示的にモデル化する
- トークンや識別子を、流れの特定の段階に結びつける
- 順序の強制をフロントエンドのロジックに頼らない
- 不正な、あるいは順序を外れた遷移は明確なエラー応答で拒否する
検査のチェックリスト
- エンドポイントを順序を外れて呼び出せるか。
- 各エンドポイントは現在の流れの状態を検証しているか。
- トークンは流れの各段階をまたいで再利用できるか。
- 不正な状態遷移は一貫して拒否されるか。
入力の検証
- 入力のパラメータやオブジェクトを信用しない。
- 入力を検証する。長さ、範囲、形式、型。
- API のパラメータに数値、真偽値、日付、時刻、固定のデータ範囲のような強い型を使い、暗黙の入力検証を得る。
- 文字列の入力を正規表現で制約する。
- 想定外の、あるいは不正な内容を拒否する。
- 使用している言語の検証やサニタイズのライブラリやフレームワークを活用する。
- 適切なリクエストサイズの上限を定め、それを超えるリクエストは HTTP 応答ステータス 413 Request Entity Too Large で拒否する。
- 入力検証の失敗を記録することを検討する。毎秒何百回も入力検証に失敗している者は、良からぬことを企てていると想定する。
- 網羅的な説明は入力値の検証のチートシートを参照する。
- 受け取るメッセージの解析には安全なパーサを使う。XML を使う場合は、XXE や類似の攻撃に脆弱でないパーサを使う。
コンテンツ種別を検証する
REST のリクエストや応答の本体は、ヘッダで示された意図されたコンテンツ種別に一致するべきである。 そうでなければ利用側や提供側で解釈の誤りを生じ、コードの注入や実行につながりうる。
- API が対応するすべてのコンテンツ種別を文書化する。
リクエストのコンテンツ種別を検証する
- 想定外の、あるいは欠けているコンテンツ種別のヘッダを含むリクエストは、HTTP 応答ステータス
406 Unacceptableまたは415 Unsupported Media Typeで拒否する。ただしContent-Length: 0のリクエストではContent-typeヘッダは任意である。 - XML のコンテンツ種別では、XML のパーサを適切に堅牢にする。XML 外部実体参照の防止を参照する。
- コンテンツ種別を明示的に定義し、意図しないコンテンツ種別を偶発的に露出させることを避ける。たとえば Jersey(Java)では
@consumes("application/json"); @produces("application/json")とする。これは XXE 攻撃の経路などを避ける。
安全な応答のコンテンツ種別を送る
REST のサービスが複数の応答の種別(application/xml や application/json など)を許し、クライアントがリクエストの Accept ヘッダで希望する応答種別の優先順を指定するのは一般的である。
Acceptヘッダを応答のContent-typeヘッダへ単純に写してはならない。Acceptヘッダが許容される種別のいずれかを明確に含まない場合は、リクエストを拒否する(望ましくは406 Not Acceptableの応答で)。
応答にスクリプトのコード(JavaScript など)を含むサービスは、ヘッダインジェクション攻撃への防御にとくに注意しなければならない。
- 応答の本体の内容に一致する、意図したコンテンツ種別のヘッダを送る状態にする。たとえば
application/javascriptではなくapplication/jsonとする。
管理用のエンドポイント
- 管理用のエンドポイントをインターネットへ露出させることを避ける。
- 管理用のエンドポイントがインターネットからアクセスできる必要がある場合は、利用者が強固な認証の仕組み(多要素など)を使わなければならない状態にする。
- 管理用のエンドポイントは異なる HTTP のポートやホストで公開し、望ましくは別の NIC と制限されたサブネットに置く。
- ファイアウォールの規則やアクセス制御リストを用いて、これらのエンドポイントへのアクセスを制限する。
エラー処理
- 一般的なエラーメッセージで応答する。失敗の詳細を不必要に露出させることを避ける。
- 技術的な詳細(コールスタックやその他の内部の手がかりなど)をクライアントへ渡さない。
監査のログ
- 安全性に関わる事象の前後に監査のログを書く。
- 攻撃を検知するため、トークンの検証のエラーを記録することを検討する。
- ログのデータを事前にサニタイズし、ログインジェクション攻撃に対処する。
セキュリティのヘッダ
ブラウザに特定の振る舞いを指示するために HTTP の応答で返せる安全性に関わるヘッダはいくつもある。 ただし一部のヘッダは HTML の応答とともに使うことを意図しており、HTML を返さない API では安全上の利点がほとんど、あるいはまったくないことがある。 その API がブラウザ以外のクライアント(モバイルアプリ、サーバー間の呼び出し、コマンドラインの道具など)からのみ利用される場合、これらのヘッダの多くはブラウザ向けの指示なので効果を持たない。
ブラウザのクライアントから利用されうるすべての API の応答には、次のヘッダを含めるべきである。
| ヘッダ | 理由 |
|---|---|
Cache-Control: no-store | ブラウザが行うキャッシュを指示するヘッダ。no-store は、(私的でも共有でも)いかなる種類のキャッシュもこのヘッダを含む応答を保存すべきでないことを示す。ブラウザは API が呼ばれるたびに最新の応答を取得する新しいリクエストを行わなければならない。no-store の値を持つこのヘッダは、機微な情報がキャッシュされたり保存されたりすることを防ぐ。 |
Content-Security-Policy: frame-ancestors 'none' | 応答が <frame>、<iframe>、<embed>、<object> の要素の中でフレーム化されうるかを指定するヘッダ。API の応答がこれらの要素の中でフレーム化される必要はない。frame-ancestors 'none' を与えれば、どのドメインも API の呼び出しが返した応答をフレーム化できなくなる。このヘッダはドラッグアンドドロップ型のクリックジャッキング攻撃から守る。 |
Content-Type | 応答のコンテンツ種別を指定するヘッダ。API の呼び出しが返す内容の種別に応じて指定しなければならない。指定がない、あるいは誤って指定されていると、ブラウザが応答のコンテンツ種別を推測しようとしうる。これは MIME スニッフィング攻撃につながりうる。API の応答が JSON であれば、よく使われるコンテンツ種別の値は application/json である。 |
Strict-Transport-Security | そのドメインへは HTTPS のみでアクセスすべきであり、以後 HTTP でアクセスしようとした場合は自動的に HTTPS へ変換すべきことをブラウザに指示するヘッダ。このヘッダは API の呼び出しが HTTPS で行われることを確かにし、偽装された証明書から守る。 |
X-Content-Type-Options: nosniff | ファイルの内容に基づいて MIME タイプを判定しようとするのではなく、Content-Type ヘッダで宣言された MIME タイプを常に使うようブラウザに指示するヘッダ。nosniff の値を持つこのヘッダは、ブラウザが MIME スニッフィングを行い、応答を不適切に HTML として解釈することを防ぐ。 |
X-Frame-Options: DENY | Content-Security-Policy: frame-ancestors 'none'(前述)に置き換えられた旧来のヘッダ。CSP レベル 2 に対応しない古いブラウザとの互換性のため、なお推奨される。DENY を与えれば、どのドメインも応答をフレーム化できなくなる。 |
以下のヘッダは、応答が HTML として描画される場合にのみ追加の安全性を与えることを意図している。 したがって API が応答で HTML を決して返さないなら、これらのヘッダは必要ないかもしれない。 ただしこれらのヘッダの働きや、API が返す(あるいは将来返しうる)情報の種類について少しでも不確かさがあるなら、多層防御の方針の一部として含めることが推奨される。
| ヘッダ | 例 | 理由 |
|---|---|---|
| Content-Security-Policy | Content-Security-Policy: default-src 'none' | CSP の機能の大半は HTML として描画されるページにのみ影響する。 |
| Permissions-Policy | Permissions-Policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=() | 以前は Feature-Policy という名前であった。ブラウザがこのヘッダに従う場合、ディレクティブによってブラウザの機能を制御する。例では、許可される多数のディレクティブ名について、空の許可リストで機能を無効にしている。このヘッダを適用するときは、ディレクティブが最新であり自分の必要に合うことを確認する。ブラウザの機能の制御方法の詳しい説明はこの記事を参照する。 |
| Referrer-Policy | Referrer-Policy: no-referrer | HTML でない応答は追加のリクエストを引き起こすべきではない。 |
CORS
Cross-Origin Resource Sharing(CORS)は、どのドメインをまたぐリクエストを許すかを柔軟に指定する W3C の標準である。 適切な CORS のヘッダを返すことで、REST API はどのドメイン(オリジン)が JavaScript から REST のサービスを呼べるのかをブラウザに伝える。
- ドメインをまたぐ呼び出しに対応しない、あるいは想定しない場合は CORS のヘッダを無効にする。
- ドメインをまたぐ呼び出しのオリジンを設定するときは、可能なかぎり限定的に、必要なかぎり一般的にする。
HTTP リクエスト内の機微な情報
RESTful な Web サービスは、資格情報の漏出を防ぐよう注意すべきである。 パスワード、セキュリティトークン、API キーは URL に現れるべきではない。 Web サーバーのログに残りうるためであり、それによってそれら自体が価値を持ってしまう。
POSTとPUTのリクエストでは、機微なデータはリクエストの本体またはリクエストのヘッダで転送するべきである。GETのリクエストでは、機微なデータは HTTP ヘッダで転送するべきである。
よい例
https://example.com/resourceCollection/[ID]/action
https://twitter.com/vanderaj/lists
よくない例
https://example.com/controller/123/action?apiKey=a53f435643de32
apiKey が URL に入っているためである。
HTTP の応答コード
HTTP はステータスコードを定義している。
REST API を設計するとき、成功に 200、エラーに 404 を使うだけで済ませない。
応答には常に意味的に適切なステータスコードを使う。
安全性に関わる REST API のステータスコードを網羅的でない形で選んで示す。 正しいコードを返すために活用する。
| コード | メッセージ | 説明 |
|---|---|---|
| 200 | OK | REST API の動作が成功したときの応答。HTTP メソッドは GET、POST、PUT、PATCH、DELETE のいずれでもよい。 |
| 201 | Created | リクエストが履行されリソースが作成された。作成されたリソースの URI が Location ヘッダで返される。 |
| 202 | Accepted | リクエストは処理のために受け付けられたが、処理はまだ完了していない。 |
| 301 | Moved Permanently | 恒久的なリダイレクト。 |
| 304 | Not Modified | クライアントがサーバーと同じリソースの複製を持っているときに返される、キャッシュに関する応答。 |
| 307 | Temporary Redirect | リソースの一時的なリダイレクト。 |
| 400 | Bad Request | メッセージ本体の形式の誤りなど、リクエストの形式が不正である。 |
| 401 | Unauthorized | 認証の ID やパスワードが誤っているか与えられていない。 |
| 403 | Forbidden | 認証は成功したが、認証された利用者が要求されたリソースへの権限を持たない場合に使う。 |
| 404 | Not Found | 存在しないリソースが要求された場合。 |
| 405 | Method Not Acceptable | 想定外の HTTP メソッドに対するエラー。たとえば REST API が HTTP GET を想定しているのに HTTP PUT が使われた場合。 |
| 406 | Unacceptable | クライアントが Accept ヘッダで示したコンテンツ種別が、サーバーの API で対応されていない。 |
| 413 | Payload too large | リクエストのサイズが与えられた上限を超えたことを示すのに使う。ファイルのアップロードなど。 |
| 415 | Unsupported Media Type | 要求されたコンテンツ種別が REST のサービスで対応されていない。 |
| 429 | Too Many Requests | サービス妨害攻撃が検知されうる場合、あるいは流量の制限によってリクエストが拒否された場合に使う。 |
| 500 | Internal Server Error | 想定外の状態によってサーバーがリクエストを履行できなかった。応答が、詳細なエラーメッセージやスタックトレースのように攻撃者を助ける内部の情報を露出させるべきでないことに注意する。 |
| 501 | Not Implemented | REST のサービスが要求された操作をまだ実装していない。 |
| 503 | Service Unavailable | REST のサービスが一時的にリクエストを処理できない。後で再試行すべきことをクライアントに伝えるために使う。 |