Webアプリ脆弱性とAPI認可
この章の到達目標
HTTPリクエスト、プログラム、DB構造、応答結果を読んで、どの処理が欠落しているか を特定し、修正方法を記述できるようにする。
2023年度のXSS、2024年度のWebスキミング、2025年度のCSP・権限昇格と直接つながる、最も出題が多いテーマである。
1. 用語一覧
| 優先 | 用語・技術 | 科目Bで押さえるポイント |
|---|---|---|
| A | IDOR / BOLA | オブジェクト単位の認可不備、他人のデータへのアクセス |
| A | BFLA | APIの機能単位の認可不備、管理機能の不正実行 |
| A | 水平・垂直権限昇格 | 同じ権限の他人へのアクセス/上位権限の取得 |
| A | SQLインジェクション | SQL構文の改変、プレースホルダーによる防御 |
| A | XSS(反射・格納・DOM型) | スクリプトの混入位置・実行経路・対策の違い |
| A | CSRF | 認証済みブラウザーに意図しない操作を実行させる |
| A | SSRF | サーバを経由した内部API・メタデータサービスへのアクセス |
| A | CSP | script-src、nonce、hash、許可オリジン、制約と回避条件 |
| A | Cookie属性 | HttpOnly、Secure、SameSite、Domain、Path |
| A | セッション管理 | セッション固定化、乗っ取り、ID再生成、有効期限 |
| A | 認証と認可 | ログイン成功と対象リソースへのアクセス許可の違い |
| A | サーバ側認可検証 | 所有者ID・所属組織・権限をサーバ側で照合 |
| A | 入力検証・出力エスケープ | 入力の妥当性と、HTMLなどの出力文脈に応じた処理 |
| B | JWT | 署名検証、iss、aud、exp、alg、鍵の管理 |
| B | CORS / SOP | ブラウザーのオリジン間アクセス制御と限界 |
| B | クリックジャッキング | frame-ancestors、X-Frame-Options |
| B | OSコマンドインジェクション | シェルのメタ文字、コマンド引数の扱い |
| B | パストラバーサル | ../ などによる意図しないファイル参照 |
| B | ファイルアップロード脆弱性 | MIME・実体検査、保存先、実行禁止 |
| B | Webスキミング | 決済画面改ざん、カード情報の外部送信 |
| B | XXE | XML外部実体の悪用、ファイル参照やSSRF |
| B | APIレート制限 | ブルートフォース、列挙、大量取得への対処 |
| B | マスアサインメント | リクエストの余分な属性による権限・状態変更 |
| B | HTTPリクエストスマグリング | プロキシとバックエンドのメッセージ解釈差 |
| B | キャッシュポイズニング | キャッシュキーの不一致、応答の汚染 |
| B | セキュアSDLC | 脅威分析、コードレビュー、セキュリティテスト |
| B | SAST / DAST / SCA | ソース解析・動的診断・依存ライブラリ検査 |
| C | GraphQL認可 | フィールド単位の認可、過剰なクエリ |
| C | プロトタイプ汚染 | JavaScriptオブジェクトの継承構造の悪用 |
| C | TOCTOU | 検査と利用の間に状態が変更される競合 |
| C | WAF / RASP | 攻撃検知・防御の仕組みと、業務認可不備への限界 |
2. 認証と認可を分けて考える
科目BのWeb問題で最も多い誤答は、認証(誰か)と認可(何をしてよいか)の混同 である。
- 認証:ログインに成功した、セッションが有効である、JWTの署名が正しい
- 認可:その利用者が このリソース に この操作 をしてよい
「ログイン済みであることは確認しているが、対象データの所有者かどうかは確認していない」というコードが、IDOR/BOLAの典型である。
2.1 IDOR / BOLA(オブジェクト単位の認可不備)
GET /api/orders/10235
Cookie: session=...(利用者Aのセッション)
サーバ側の処理が次のようになっていると、利用者Aは番号を変えるだけで他人の注文を閲覧できる。
# 欠落あり:ログインの確認のみ
order = db.query("SELECT * FROM orders WHERE id = ?", order_id)
return order
# 修正:所有者をサーバ側で照合する
order = db.query(
"SELECT * FROM orders WHERE id = ? AND user_id = ?",
order_id, session.user_id)
if order is None:
return 404
答案の型:「注文IDに対応する注文の所有者がログイン中の利用者と一致するかを、サーバ側で検証していない。」
推測しにくいID(UUID)にするのは補助策であって、認可検証の代わりにはならない。設問で「根本的な対策」を問われたら、必ずサーバ側の認可検証を書く。
2.2 BFLA(機能単位の認可不備)と垂直権限昇格
管理画面のメニューを一般利用者に 表示しない だけで、API側で役割を検証していない状態。
POST /api/admin/users/123/role {"role": "admin"}
- 水平権限昇格:同じ権限レベルの他人のデータにアクセスする(IDORと同じ構造)
- 垂直権限昇格:一般利用者が管理者の機能を実行する(BFLA)
対策は、すべてのAPIで、サーバ側で役割(ロール)を検証すること。画面の制御(ボタンを隠す)は対策にならない。
2.3 マスアサインメント
リクエストの属性をそのままモデルに代入すると、本来は利用者が変更できない属性まで書き換えられる。
PUT /api/users/me {"name": "Alice", "is_admin": true}
対策は、更新を許可する属性を明示的に列挙すること(許可リスト方式)。
3. インジェクション系
インジェクションの共通構造は、データとして扱うべき入力が、命令(構文)の一部として解釈される ことである。対策も共通で、データと構文を分離する仕組みを使う。
| 種類 | 解釈する処理系 | 根本対策 |
|---|---|---|
| SQLインジェクション | DB | プレースホルダー(バインド機構) |
| OSコマンドインジェクション | シェル | シェルを経由しないAPI、引数を配列で渡す |
| XSS | ブラウザー(HTML/JS) | 出力文脈に応じたエスケープ、CSP |
| XXE | XMLパーサー | 外部実体・DTDの無効化 |
| パストラバーサル | ファイルシステム | ファイル名を直接使わない、正規化後に基準ディレクトリ内か確認 |
3.1 SQLインジェクション
# 脆弱:文字列の連結で構文を組み立てている
sql = "SELECT * FROM users WHERE name = '" + name + "'"
# 修正:プレースホルダー
db.query("SELECT * FROM users WHERE name = ?", name)
- エスケープ処理は補助策。根本対策を問われたらプレースホルダーを書く。
- DBアカウントの権限を最小にすると、被害の範囲を限定できる(多層防御)。
- エラーメッセージにSQL文やDB構造を表示しない。
3.2 XSS
| 種類 | スクリプトの混入位置 | 実行経路 |
|---|---|---|
| 反射型 | リクエストのパラメータ | サーバがそのまま応答に埋め込む |
| 格納型 | DBなどに保存されたデータ | 閲覧した全利用者のブラウザーで実行される |
| DOM型 | クライアント側JSの処理 | innerHTML などでブラウザーが組み立てる(サーバを経由しないこともある) |
対策:
- 出力する文脈(HTML本文、属性値、JavaScript、URL)に応じてエスケープする。
- DOM型では
innerHTMLの代わりにtextContentを使う。 - CSPで、許可していないスクリプトの実行を防ぐ(多層防御)。
- Cookieに
HttpOnlyを付けると、スクリプトからのCookie読み取りを防げる。ただし、XSSそのものを防ぐわけではない(利用者になりすました操作はできてしまう)。
3.3 OSコマンドインジェクション・パストラバーサル
- OSコマンドインジェクションは、シェルのメタ文字(
;|&など)が解釈されることが成立条件。シェルを起動しない関数を使い、引数を配列で渡すのが根本対策。 - パストラバーサルは、ファイル名に
../を含めて基準ディレクトリの外を参照させる。ファイル名を利用者に指定させず、IDとの対応表で管理するのが最も確実。
3.4 XXE
XMLの外部実体参照を使って、サーバ上のファイルや内部ネットワークのURLを参照させる。XMLパーサーの設定で、DTDと外部実体の処理を無効にする。
4. ブラウザーの仕組みと攻撃・防御
4.1 同一オリジンポリシー(SOP)とCORS
- オリジン=スキーム+ホスト+ポート。
- SOPは、別オリジンのレスポンスをJavaScriptで 読む ことを制限する。リクエストの 送信 そのものは防がない。これがCSRFが成立する理由である。
- CORSは、サーバが応答ヘッダーで「このオリジンからの読み取りを許可する」と宣言する仕組み。SOPを 緩める ためのものであり、防御の仕組みではない。
設定ミスの典型:リクエストの Origin ヘッダーの値をそのまま Access-Control-Allow-Origin に返し、さらに Access-Control-Allow-Credentials: true を付けている。任意のサイトから、利用者の認証情報付きで応答を読まれる。
4.2 CSRF
成立条件:
- 利用者が対象サイトにログイン済み(Cookieが自動送信される)
- 状態を変更する処理が、推測可能なリクエストだけで実行できる
- サーバがリクエストの送信元を確認していない
対策:
- 推測できないトークン(CSRFトークン)を埋め込み、サーバで照合する。
- Cookieに
SameSite=LaxまたはStrictを付ける。 - 重要な操作では再認証を求める。
4.3 Cookie属性
| 属性 | 効果 | 付けない場合のリスク |
|---|---|---|
Secure | HTTPS通信でのみ送信 | 平文のHTTP通信で盗聴される |
HttpOnly | JavaScriptから読み取れない | XSSでCookieを読み取られる |
SameSite | 別サイトからのリクエストに付けるかを制御 | CSRF |
Domain | 送信先のドメイン範囲 | 広く指定するとサブドメインにも送信される |
Path | 送信先のパス範囲 | 分離の補助にすぎない(セキュリティ境界ではない) |
Domain 属性は 指定しない 方が送信範囲が狭くなる(発行したホストのみ)。サブドメインで第三者のサービスを使っている場合の問題で問われやすい。
4.4 セッション管理
| 攻撃 | 成立条件 | 対策 |
|---|---|---|
| セッション固定化 | ログイン前のセッションIDがログイン後も使われる | ログイン成功時にセッションIDを再生成する |
| セッションハイジャック | セッションIDの漏えい(XSS、盗聴、ログやURLへの記録) | HttpOnly、Secure、URLにIDを含めない |
| 推測 | セッションIDが連番や時刻に基づく | 暗号論的擬似乱数で十分な長さのIDを生成する |
有効期限(無操作時と絶対的な上限)と、ログアウト時のサーバ側での破棄も確認する。
4.5 CSP(Content Security Policy)
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
| ディレクティブ | 役割 |
|---|---|
script-src | 実行を許可するスクリプトの取得元 |
| nonce | 応答ごとに変わる値を持つ <script> だけを許可 |
| hash | 内容のハッシュ値が一致するスクリプトだけを許可 |
frame-ancestors | 自ページを埋め込めるページ(クリックジャッキング対策) |
report-uri / report-to | 違反を報告する送信先 |
CSPが効かなくなる条件(2025年度に関連する論点):
'unsafe-inline'を許可している- nonceが固定値、または推測できる
- 許可しているオリジンに、利用者が任意のファイルを置ける(CDNの共有領域、アップロード機能など)
- 許可しているオリジンに、応答の内容を呼出し側が指定できるエンドポイント(JSONPなど)がある
CSPは 多層防御 であり、XSSの根本対策(出力エスケープ)の代わりにはならない。
4.6 クリックジャッキング
透明にした対象サイトを別ページに重ね、利用者に意図しないクリックをさせる。Content-Security-Policy: frame-ancestors または X-Frame-Options で埋め込みを制限する。
5. サーバ側から内部へ向かう攻撃
5.1 SSRF
利用者が指定したURLにサーバがアクセスする機能(画像の取込み、Webhook、URLのプレビュー)を使い、外部からは届かない宛先にサーバ自身からアクセスさせる。
狙われる宛先:
- 社内API、管理画面(送信元がサーバなので、IPアドレス制限を通過してしまう)
- クラウドのメタデータサービス(一時的な認証情報を取得できる場合がある)
対策:
- 宛先を許可リストで制限する(拒否リストはリダイレクトやDNSの再解決で回避されやすい)。
- 名前解決した 後の IPアドレスで判定し、プライベートアドレスやリンクローカルアドレスを拒否する。
- メタデータサービスへのアクセスにトークンを必須にする方式(AWSのIMDSv2など)を使う。
- サーバからの外向き通信をFWで制限する。
5.2 ファイルアップロード
- 拡張子や
Content-Typeヘッダーは偽装できる。ファイルの実体(中身)を検査する。 - Webサーバの公開ディレクトリに保存しない。保存先ではスクリプトの実行を禁止する。
- ファイル名はサーバ側で生成し直す。
5.3 HTTPリクエストスマグリング・キャッシュポイズニング
- スマグリング:フロントのプロキシとバックエンドで、リクエストの区切り(
Content-LengthとTransfer-Encoding)の解釈が異なることを利用し、次のリクエストに別の内容を紛れ込ませる。 - キャッシュポイズニング:キャッシュキーに含まれないヘッダーで応答の内容が変わる場合に、細工した応答をキャッシュさせ、他の利用者に配信させる。
いずれも、複数の機器の間で解釈が一致していない ことが成立条件になる。構成図にリバースプロキシやCDNが出てきたら疑う。
6. JWTとAPI
| 確認項目 | 欠落した場合 |
|---|---|
| 署名の検証 | 内容を改ざんしたトークンを受け入れる |
alg の固定 | none や想定外のアルゴリズムへの差し替えを受け入れる |
iss(発行者) | 別の発行者のトークンを受け入れる |
aud(受信者) | 別のサービス向けのトークンを流用される |
exp(有効期限) | 漏えいしたトークンを無期限に使われる |
| 鍵の管理 | 共通鍵が漏えいすると、誰でも正規のトークンを作成できる |
JWTの中身(ペイロード)はBase64URLでエンコードされているだけで、暗号化されていない。機密情報を入れない。
APIレート制限:ログインの試行、IDの列挙、データの大量取得を防ぐ。利用者単位・IPアドレス単位・APIキー単位で制限し、超過した場合は 429 Too Many Requests を返す。
7. Webスキミング
決済画面に不正なスクリプトを埋め込み、入力されたカード情報を外部に送信させる攻撃。
| 侵入経路 | 対策 |
|---|---|
| サイトの改ざん(管理画面への不正ログイン、CMSの脆弱性) | 管理画面の接続元制限、MFA、改ざん検知 |
| 外部から読み込んでいるスクリプトの改ざん | SRI(Subresource Integrity)、読込み元の削減 |
| 送信先の追加 | CSPの connect-src / form-action で送信先を制限 |
カード情報を自社で扱わない方式(決済代行会社の画面への遷移、トークン化)を選ぶことも、根本的な対策として問われる。
8. 開発プロセスとツール
| 手法 | 対象 | 特徴 |
|---|---|---|
| 脅威分析(脅威モデリング) | 設計 | STRIDEなどで、設計段階で脅威を洗い出す |
| SAST | ソースコード | 実行せずに解析する。開発の早い段階で使える |
| DAST | 動作中のアプリ | 外部から実際にリクエストを送る。設定不備も見つかる |
| SCA | 依存ライブラリ | 既知の脆弱性があるバージョンを検出する |
| WAF | 通信 | 既知の攻撃パターンを遮断する。業務上の認可不備(IDORなど)は検知できない |
| RASP | 実行環境 | アプリの内部で攻撃を検知する |
WAFの限界は頻出である。「正規の利用者が、正規の形式のリクエストで、他人のIDを指定する」攻撃は、パターンでは区別できない。
9. 補強(C)
- GraphQL:1つのエンドポイントで多くのデータを取得できるため、フィールド単位の認可と、クエリの深さ・コストの制限が必要。
- プロトタイプ汚染:JSONの
__proto__などのキーを通じて、全オブジェクトが継承する属性を書き換える。キーの許可リスト、継承を持たないオブジェクトの利用で防ぐ。 - TOCTOU:残高の確認と引落としの間に別のリクエストが割り込むなど。DBのトランザクションや排他制御で防ぐ。
10. 読解のチェックリスト
ソースコードやリクエストを見たら、次の順で確認する。
- このAPIは 誰が 呼び出せるべきか(認証)
- どのデータ に対する操作か。所有者や所属組織を照合しているか(認可)
- 入力値は どの処理系 に渡るか(SQL、シェル、HTML、XML、ファイルパス、URL)
- 応答は どの文脈 に出力されるか
- ブラウザーが自動で送るもの(Cookie)に依存していないか
- サーバが 外部や内部へ 通信する機能はないか
~Yu Tokunaga