「脆弱性がある」と言われても、アプリの中で何がどこで起きているかを知らなければ、想像のしようがありません。この章では、Webアプリの登場人物を4つに絞り、ボタンを1回押したときに何が起きるかを順番に追います。覚えることは多くありません。ただし、ひとつだけ「最重要の事実」があります。
この章の学習目標
- ブラウザ、サーバー、データベース、外部サービスの4つの役割を、経理の言葉で説明できるようになる
- ボタンを押してから画面が変わるまでに、リクエストとレスポンスがどう流れるかを図で説明できるようになる
- 「ブラウザ側は利用者が自由に書き換えられる。だから検証はサーバー側で行う」という原則を、理由とともに説明できるようになる
4つの登場人物
Webアプリは、どれほど複雑に見えても、登場人物は4つに整理できます。経理部の窓口業務に置き換えると、役割がはっきりします。
| 登場人物 | 役割 | 経理でいえば | あなたの環境では |
|---|---|---|---|
| ブラウザ | 画面を表示し、利用者の操作を受け付ける | 窓口。申請書を受け取る場所 | 利用者のChromeやSafari |
| サーバー | 依頼を受けて処理する。検証と権限の確認もここ | 担当者。申請書を証憑と照合し、処理する人 | Vercel上で動くNext.jsの処理 |
| データベース | データを保存し、読み書きに応じる | 金庫、または総勘定元帳 | Supabaseのテーブル |
| 外部サービス | 自分の外にあって、呼び出して使う | 銀行や取引先 | Supabase Auth(ログイン)、メール送信、決済、AIのAPI |
大切なのは、この4つが「別の場所」にあるということです。ブラウザは利用者のパソコンの中、サーバーとデータベースはあなたが契約したクラウドの中、外部サービスは他社の中にあります。場所が違えば、信用の度合いも違います。これが、このあとの話の土台になります。
1回の操作で何が起きるか
あなたの学習用ミニ銀行アプリで、利用者が「残高を見る」ボタンを押したとします。画面に残高が出るまでに、次のことが起きています。
- リクエスト。ブラウザがサーバーに「口座105の残高をください」という依頼を送ります。このとき、ログイン済みであることの証明(後述のトークン)を一緒に添えます。
- 確認。サーバーは、添えられた証明が本物か、そして口座105がその利用者のものかを確かめます。
- 読み書き。確認が通ったら、データベースから残高を読みます。振込なら、ここで残高を書き換えます。
- レスポンス。結果をブラウザに返し、ブラウザが画面に表示します。
経理の窓口で言えば、申請者が窓口に申請書を出し(リクエスト)、担当者が申請者の身分と権限を確かめ(確認)、元帳を参照または記帳し(読み書き)、控えを渡す(レスポンス)という一連の流れです。MDN Web Docsの「ウェブのしくみ」では、この依頼と応答のやりとりがHTTPという約束事に沿って行われることが説明されています。
ログインの仕組み:証明を添えて依頼する
①で「ログインの証明を添える」と書きました。「本人かどうか」を毎回IDとパスワードで確かめていたら、操作のたびに入力が必要になります。そこでWebアプリは、次のようにしています。
- 最初に、IDとパスワード(または外部サービスのログイン)で本人確認をします。
- 確認が通ったら、サーバーが「ログイン済みの証明」を発行します。これがセッションまたはトークンです。
- 以後の操作では、ブラウザがその証明を毎回リクエストに添えます。サーバーは証明を見て、誰からの依頼かを判断します。
社員証に似ています。入館時に一度だけ身分を確かめ、以後は社員証を提示すれば通れる。便利な反面、社員証を落とせば拾った人が通れてしまいます。トークンも同じで、漏れれば他人がその人として操作できます。だからトークンはブラウザの中で丁寧に扱われ、一定時間で期限が切れるように作ります。OWASPのSession Management Cheat Sheetが、この扱い方の基準を示しています。
ここで、認証と認可を分けておきます。IDとパスワードで「あなたは誰か」を確かめるのが認証、確かめたうえで「あなたに何が許されているか」を決めるのが認可です。社員証を持っていることと、他部署の伝票を承認してよいことは別です。図2-1の②は、この両方を見ています。
最重要の事実:ブラウザ側は、利用者が自由に書き換えられる
この章でひとつだけ覚えるなら、これです。ブラウザで動いている画面は、利用者のパソコンの中にあります。ブラウザには「開発者ツール」という標準機能があり、表示されている文字も、ボタンの有効・無効も、サーバーに送る内容も、利用者が書き換えられます。これは不正でも裏技でもなく、ブラウザがそういう道具だということです。
ですから、「ブラウザ側で残高不足をチェックしているから、残高以上の振込はできない」は、守りになりません。チェックを外してボタンを押すのは利用者側の自由です。「管理者以外にはメニューを表示していない」も同じで、表示していないだけなら、URLを直接打てば届きます。
窓口に出された申請書に「残高確認済み」「承認済み」と書いてあっても、担当者はそれを信じません。申請者が自分で書けるからです。確認は担当者が自分で行い、担当者の記録だけを正とします。ブラウザは申請者、サーバーは担当者です。検証はサーバー側で行うという原則は、この意味です。ブラウザ側のチェックは、利用者が入力を間違えないための親切であって、統制ではありません。
静的サイトと動的アプリ
あなたが公開しているものは、仕組みの面で2つに分かれます。
| 静的サイト | 動的アプリ | |
|---|---|---|
| 例 | Vercelに置いた教材サイト、用語集 | Supabaseを使う、ログイン付きのミニ銀行 |
| サーバーの仕事 | 用意済みのHTMLをそのまま返すだけ | 依頼ごとに処理を行い、データベースを読み書きする |
| 利用者ごとの違い | ない。誰が見ても同じ画面 | ある。ログインした人ごとに違う内容 |
| 秘密情報 | 原則として持たない | データベースの鍵、外部サービスのAPIキーを持つ |
| 第1章の論文の対象 | 含まれない | 含まれる |
静的サイトは、図2-1でいうと②③がなく、①と④だけで完結します。預かるデータがないので、守るべきものはサイトそのものと、Vercelやレンタルサーバーのアカウントです。一方、動的アプリは②③があり、そこに利用者のデータと秘密情報があります。第1章の論文が調べたのは、この動的アプリの側です。どちらを作っているかで、必要な守りの深さが変わります。この切り分けを第3章で行います。
用語の整理
- リクエスト
- ブラウザがサーバーに送る依頼。「このページをください」「この口座の残高をください」「この内容で振込をしてください」。
- レスポンス
- サーバーがブラウザに返す応答。成功、拒否、見つからない、といった結果と、表示する内容。
- API
- Application Programming Interface。プログラム同士が依頼をやりとりするための窓口と手順。自分のサーバーが用意する窓口も、外部サービスが用意する窓口も、どちらもAPIと呼びます。
- セッション
- ログインしてからログアウトするまでの「状態」。サーバーが覚えていて、ブラウザは番号札(セッションID)で紐づきます。
- トークン
- ログイン済みであることの証明として発行される文字列。持っている人が本人として扱われるため、漏れるとなりすましになります。
- 環境変数
- コードの外に置く設定値。データベースの接続情報やAPIキーのような秘密情報はここに置きます。Vercelでは設定画面から登録します。
- フロントエンド/バックエンド
- ブラウザ側で動く部分がフロントエンド、サーバー側で動く部分がバックエンド。信用できるのはバックエンドだけ、というのがこの章の結論です。
まとめ
- Webアプリの登場人物は、ブラウザ(窓口)、サーバー(担当者)、データベース(金庫)、外部サービス(取引先)の4つ。それぞれ別の場所にあり、信用の度合いが違う。
- 1回の操作は、リクエスト→サーバーでの確認→データベースの読み書き→レスポンスの順に進む。「確認」を飛ばすと、他人のデータに届く。
- ブラウザ側は利用者が自由に書き換えられる。検証と権限の確認はサーバー側で行う。ブラウザ側のチェックは親切であって統制ではない。
出典
- MDN Web Docs「ウェブのしくみ」(Mozilla)
- MDN Web Docs「HTTP の概要」(Mozilla)
- OWASP Cheat Sheet Series「Session Management」(OWASP)