この本は、単なる攻撃手法のカタログではなく、「なぜ脆弱性が生まれるのか」という原理と、「どうすれば根本的に防げるのか」という設計思想を学べる、Webセキュリティのバイブルです。
エンジニアが実務で即座に活かせるよう、章ごとの詳細な解説と、具体的な対策の比較表を交えて構成しました。
はじめに:なぜ今、この本が必要なのか
Webアプリケーションの進化に伴い、クライアントサイド(JavaScript)の重要性が増しました。第2版では、PHP中心の解説に加え、JavaScript、Web API、CORS(Cross-Origin Resource Sharing) といった現代的なトピックが大幅に加筆されています。
「脆弱性とは、アプリケーションのバグの一種であり、悪用可能なものを指す」
本書のスタンスは一貫しています。セキュリティは魔法ではなく、正しいエンジニアリングの結果であるということです。
第1章:Webアプリケーションの脆弱性とは
ここでは、Webアプリが抱えるリスクの全体像を把握します。重要なのは、脆弱性が「受動的攻撃」と「能動的攻撃」に分類されるという理解です。
攻撃タイプの分類とリスク
| 攻撃タイプ | 特徴 | 代表的な脆弱性 | エンジニアへの影響 |
| 能動的攻撃 | 攻撃者がサーバーに直接悪意あるデータを送信する | SQLインジェクション、OSコマンドインジェクション | サーバー内のデータ漏洩、改ざん、システム乗っ取り |
| 受動的攻撃 | 罠を仕掛け、利用者に踏ませることで攻撃を成立させる | XSS(クロスサイトスクリプティング)、CSRF(クロスサイトリクエストフォージェリ) | ユーザー情報の窃取、なりすまし操作、マルウェア感染 |
エンジニアの心得:
サーバーを守るだけでは不十分です。「ユーザーを守る」ためには、受動的攻撃への対策(XSS対策など)が不可欠であることを理解しましょう。
第2章:Webアプリケーションの技術基盤
HTTPプロトコルとセッション管理の仕組みを深掘りします。セキュリティホールは、この「状態を持たない(ステートレス)」HTTPを、無理やりステートフルに見せる過程(セッション管理)で生まれがちです。
セッションIDの取り扱い
セッションIDは「免許証」のようなものです。これが盗まれる(セッションハイジャック)と、本人になりすまされます。
「セッションIDは、推測不可能であり、かつ盗聴されないように管理しなければならない」
クッキー(Cookie)とセッション(Session)の違い
| 項目 | クッキー (Cookie) | セッション (Session) |
| 保存場所 | クライアント(ブラウザ)側 | サーバー側(メモリ、DB、Redisなど) |
| 実体 | キーと値の小さなテキストデータ | サーバー上の変数やオブジェクト |
| 容量制限 | 小さい(数KB程度) | サーバーのリソースが許す限り大きい |
| 役割の例 | 「会員証の番号」を持ち歩く | 「会員情報(名前、権限、カートの中身)」を店で管理する |
| セキュリティ | 盗まれやすく、改ざんされやすい | サーバーにあるため直接は見えないが、IDがバレると乗っ取られる |
実装のチェックポイント:
- Cookieの属性:
HttpOnly(JSからのアクセス禁止)、Secure(HTTPSのみ送信)を必ず付与する。 - 生成ロジック: 自前で乱数を作らず、言語やフレームワーク標準のセッション管理機構を使う。
第3章:Webセキュリティの基礎~入力と出力~
多くの脆弱性は「入力」と「出力」の不適切な取り扱いに起因します。ここで「境界(Boundary)」という概念が重要になります。
入力処理と出力処理の役割分担
| 処理フェーズ | 目的 | 具体的なアクション | セキュリティ上の効果 |
| 入力バリデーション | アプリ仕様に合致するか確認する | 文字種チェック、数値範囲チェック、形式チェック | データの整合性を保つ(※脆弱性対策の主役ではない) |
| 出力エスケープ | データを「文字」として扱わせる | HTMLエスケープ、SQLプレースホルダ利用 | 脆弱性対策の要(本丸) |
重要な知見:
「入力チェックでSQLインジェクションを防ぐ」という考えは捨ててください。入力チェックはあくまでデータ品質のためであり、セキュリティ対策の責任は「出力処理(または実行処理)」が担うべきです。
例えば、ユーザー名に <b>Bold</b> という文字列が入力されたとします。
- HTMLで表示する場合:
<b>はタグとして機能するため、太字になります(XSSのリスク)。エスケープが必要です。 - Excelに出力する場合:
<b>は単なる文字なので、エスケープは不要です。 - シェルスクリプトに渡す場合:
<はリダイレクト記号として解釈されるかもしれません。別のエスケープが必要です。
結論: 入力時に全てを無害化しようとすると、必要な文字まで消してしまう可能性があります(例:プログラミング質問サイトでコードが投稿できないなど)。「使う場所(コンテキスト)」が決まった瞬間(=出力時)に、その場所に合わせた無害化をするのが最も確実なのです。
第4章:Webアプリケーションの脆弱性(サーバーサイド編)
本書の核となる部分です。主要な脆弱性のメカニズムと対策を解説します。
1. SQLインジェクション
攻撃者がSQL文を不正に操作し、DBを操作する攻撃です。
SQLインジェクション:具体例とメカニズム
脆弱なコード例(PHP風の疑似コード): 文字列連結でSQLを組み立てているのが諸悪の根源です。
$user_id = $_GET['id']; // ユーザーからの入力
// 脆弱なSQL生成
$sql = "SELECT * FROM users WHERE id = '" . $user_id . "'";攻撃リクエスト(入力例): 攻撃者は、id パラメータとして 1' OR '1'='1 という文字列を送ります。
完成してしまうSQL:
SELECT * FROM users WHERE id = '1' OR '1'='1'id = '1'が偽でも、'1'='1'は常に真(True)です。ORでつながれているため、条件全体が「真」となり、テーブル内の全ユーザー情報が出力されてしまいます。- さらに悪質な場合、
; DROP TABLE users; --などを注入され、データが消されることもあります。
対策の比較:
| 手法 | 推奨度 | 解説 |
| プレースホルダ(静的) | ◎ | SQL文のコンパイルとデータバインディングを分離する。最も確実。多くのORMが標準で対応。 |
| エスケープ処理 | △ | ライブラリのバグや実装漏れのリスクがあるため、プレースホルダが使えない場合のみ。 |
| WAF | 〇 | 防御の層を厚くする意味では有効だが、根本対策ではない。 |
安全なコード例(PDOを使用したプリペアドステートメント)
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function findUser(username: string) {
// ✅ 安全なコード例 (Prisma)
// 入力値は自動的にエスケープ・パラメータ化され、SQL構文として解釈されることはない
const user = await prisma.user.findFirst({
where: {
username: username, // ここに "' OR '1'='1" が入っても単なる文字列として検索される
},
});
return user;
}2. クロスサイト・スクリプティング(XSS)
攻撃者が作成したスクリプトを、被害者のブラウザ上で実行させる攻撃です。
XSS発生のメカニズム(概念図):
sequenceDiagram
participant Attacker as 攻撃者
participant Victim as 被害者
participant Server as Webサーバー
Attacker->>Server: 1. 悪意あるスクリプトを含むデータを保存<br>(例: 掲示板への書き込み)
Note right of Server: データベースにスクリプトが保存される
Victim->>Server: 2. ページを閲覧
Server-->>Victim: 3. 保存されたスクリプトを含むHTMLを返す
Note left of Victim: 4. ブラウザでスクリプトが実行される<br>(Cookie奪取など)実用的な対策:
- HTMLコンテキスト:
<>&"'を実体参照(<など)に変換する。 - 属性値: 必ず
"で囲み、その中でエスケープする。 - URL:
javascript:スキームが入らないように検証する(http/httpsのみ許可)。 - JavaScript内: 原則として動的な値をJSの中に埋め込まない。必要な場合は
data-*属性経由でDOM操作を用いて渡す。
React/Vue/Next.js等のモダンFWがデフォルトでエスケープするため、「危険なレンダリング関数(
dangerouslySetInnerHTMLなど)」の使用箇所のみを厳重監視するスタイルへ。
3. クロスサイト・リクエスト・フォージェリ(CSRF)
認証済みのユーザーに、意図しない操作(送金、退会、投稿など)を行わせる攻撃です。
CSRF発生のメカニズム(概念図):
sequenceDiagram
participant Victim as 被害者(ブラウザ)
participant Target as 銀行サイト(Target)
participant Trap as 罠サイト(Attacker)
Note over Victim, Target: 被害者は銀行にログイン中<br>(Cookieを持っている)
Victim->>Trap: 1. 攻撃者が用意した罠サイトを閲覧
Note right of Trap: 罠ページ内の隠しフォームや<br>JavaScriptが自動実行される
Trap-->>Victim: 2. 「銀行へ送金リクエストしろ」という指令(HTML/JS)
Victim->>Target: 3. 自動的に送金リクエスト送信<br>(POST /transfer?amount=10000&to=attacker)
Note right of Victim: ブラウザは自動的に<br>銀行のCookieを付与する
Target->>Target: 4. 正しいCookieがあるため<br>本人からの依頼と誤認して送金処理実行
Target-->>Victim: 5. 送金完了画面を表示
Note over Victim: 「あれ?いつの間にか送金されてる?」XSSとCSRFの違い:
| 特徴 | XSS | CSRF |
| 攻撃の実行場所 | ユーザーのブラウザ上でスクリプトが動く | ユーザーのブラウザから勝手にリクエストが飛ぶ |
| 主な被害 | 情報漏洩、Cookie窃取 | 状態の変更(購入、退会、投稿) |
| 根本対策 | 適切なエスケープ処理 | ワンタイムトークンの埋め込み |
CSRF対策の鉄則:
重要な処理(POSTリクエストなど)を行うフォームには、必ず推測不可能なトークン(Token)を埋め込み、サーバー側で検証してください。
トークンに加え、SameSite属性やCORSプリフライト、Originヘッダ検証による多層防御が標準化。フレームワークが自動でやることが増えた。
第5章:認証と認可の脆弱性
ログイン機能に関する脆弱性です。特に「パスワードのハッシュ化」と「セッション管理」が焦点です。
- パスワード保存: 平文は論外。
MD5やSHA-1も既に危険。ソルト(Salt)付きのPBKDF2、bcrypt、Argon2などのストレッチング可能なアルゴリズムを使用する。 - セッション固定攻撃: ログイン成功時に、必ずセッションIDを再発行(
session_regenerate_id(true))する。
2026年1月現在はPasskeys (WebAuthn/FIDO2) が主流。パスワードそのものを無くす方向へ。多要素認証(MFA)は必須。
第6章:Web APIとJavaScriptのセキュリティ
本章は、現代のフロントエンド開発において最も誤解されやすく、かつ危険な「ブラウザのセキュリティモデル」を扱います。まずは、この世界の「絶対的な掟(SOP)」と「特例の許可(CORS)」の関係性を正しく理解することから始めましょう。
1. 同一生成元ポリシー(Same-Origin Policy: SOP)とCORSの関係
ブラウザセキュリティは、「デフォルトで拒否する」という強い原則(SOP)の上に成り立っています。CORSはそれを制限するためではなく、解除するために存在します。
| 用語 | 役割のイメージ | 説明 |
| 同一生成元ポリシー (SOP) | 鉄壁の盾(デフォルト) | 異なるオリジン(他サイト)からのデータ読み取りをブラウザが強制的にブロックする仕組み。これがなければ、ログイン済みの状態で悪意あるサイトを見ただけで情報が盗まれてしまう。 |
| CORS | 特例の通行証 | SOPのブロックを安全に解除するための仕組み。サーバーが「このオリジン(相手)ならデータを見せてもいい」と明示的に許可を出すこと。 |
オリジン(Origin)とは?
「スキーム(http/https)」「ホスト(example.com)」「ポート(80/443)」の3つセットのこと。これらがすべて一致する場合のみ「同一」とみなされます。
2. CORS(Cross-Origin Resource Sharing)の仕組み
CORSは、ブラウザとサーバーの間で行われる「許可証の確認作業」です。
役割分担:誰が何をするのか?
| 担当 | 役割 | 具体的なアクション |
| バックエンド (Server) | 許可証の発行者 | レスポンスヘッダに Access-Control-Allow-Origin: https://frontend.com を付与して返す。設定の実権はここにある。 |
| ブラウザ (Client) | 検問(ガードマン) | サーバーから返ってきた「許可証」を確認する。許可がない、または宛先が違う場合、JavaScriptへのデータ受け渡しを拒否する。 |
| フロントエンド (JS) | 申請者 | リクエストを送るのみ。セキュリティ設定(許可/拒否)を操作することはできない。 |
通信のフロー(プリフライトリクエスト)
ブラウザは安全のため、いきなり本番のリクエストを送らず、先に「予備調査(プリフライト)」を行うことがあります。
sequenceDiagram
participant Browser as ブラウザ(Origin A)
participant Server as サーバー(Origin B)
Note over Browser: Origin A から Origin B へ<br>データを送りたい(JSONなど)
Browser->>Server: 1. プリフライトリクエスト (OPTIONSメソッド)<br>"Origin: https://origin-a.com"<br>"このオリジンからの通信はOK?"
Server-->>Browser: 2. レスポンス<br>"Access-Control-Allow-Origin: https://origin-a.com"<br>"OKだよ"
Note over Browser: 許可が得られたので初めて<br>本番リクエストを送る
Browser->>Server: 3. 実際のリクエスト (POST /api/data)
Server-->>Browser: 4. データ返却3. 設定ミスが招く「恐怖のシナリオ」
もしバックエンドエンジニアが「CORSエラーがうるさいから全許可しちゃえ」と設定した場合、SOPという盾が消滅します。これにより、CSRFのような攻撃で情報が漏洩します。
危険な設定例: Access-Control-Allow-Origin: * (認証が必要なAPIで設定した場合)
攻撃のシーケンス(CORS全許可の場合)
攻撃者は、ブラウザが持つ「クッキー自動送信」の性質と、CORSの「読み取り許可」を組み合わせて攻撃します。
sequenceDiagram
autonumber
actor Victim as ユーザー (被害者のブラウザ)
participant EvilSite as 攻撃者のサイト<br>(evil.com)
participant BankAPI as 銀行のサーバー<br>(bank.com)
participant AttackerDB as 攻撃者の集計サーバー
Note over Victim, BankAPI: 【前提】ユーザーは銀行にログイン中で、<br>ブラウザ内に「銀行のCookie」を持っている
Victim->>EvilSite: 1. 罠サイト(evil.com)にアクセスしてしまう
rect rgb(255, 240, 240)
Note right of EvilSite: ページ内の悪意あるJSが裏で動く
EvilSite->>BankAPI: 2. 銀行APIへ「残高照会」をリクエスト
Note left of BankAPI: ブラウザ仕様により<br>「銀行のCookie」が勝手に付与される
BankAPI->>BankAPI: 3. Cookieを見て「本人」と判断
BankAPI-->>EvilSite: 4. 残高データ(JSON)を返却
Note over Victim, EvilSite: 【防御壁崩壊】<br>CORS全許可のため、ブラウザは<br>返ってきたデータをJSに渡してしまう
EvilSite->>EvilSite: 5. JSがレスポンス(残高)を読み取る
end
EvilSite->>AttackerDB: 6. 読み取ったデータを攻撃者へ転送
Note right of AttackerDB: 攻撃完了:情報漏洩4. CORS設定のベストプラクティス
エンジニアが実装時に守るべき鉄則です。
- 安易なワイルドカード(
*)の禁止- 公開API(天気予報データなど)以外では絶対に使用してはいけません。
- オリジンの動的判定
- リクエストヘッダの
Originを読み取り、あらかじめ登録された「許可リスト(ホワイトリスト)」にある場合のみ、そのオリジンをAccess-Control-Allow-Originにセットして返します。
- リクエストヘッダの
- 認証情報を含む場合の制約
- CookieやBasic認証ヘッダを含む通信(
withCredentials: true)を行う場合、仕様上*は使えません。必ず具体的なオリジンを指定する必要があります。
- CookieやBasic認証ヘッダを含む通信(
5. JSONのセキュリティ(JSONハイジャック対策)
APIが返すJSONデータ自体にも防御が必要です。
- Content-Typeの厳格化: 必ず
Content-Type: application/jsonを指定し、クライアント側もそれを検証する。 - スニッフィング防止: レスポンスヘッダに
X-Content-Type-Options: nosniffを付与する。これにより、ブラウザが勝手に「これはHTMLだ」と誤解釈してスクリプトを実行してしまう事故を防ぎます。
Content-Type: application/json
X-Content-Type-Options: nosniffContent-Type: application/json の意味
- 問題: HTMLの
<form>タグは、仕様上application/jsonヘッダを送れない。 - リスク: ヘッダ確認がないと、攻撃者は
<form>(text/plain等)を使って無理やりJSONに見せかけたデータを送り、処理を実行させてしまう(CSRF)。 - 対策: 「ヘッダが
application/jsonでなければ拒否」と実装する。
これにより攻撃者は<form>を使えなくなり、JS経由での攻撃もブラウザの検問(CORSプリフライト)でブロックされる。
X-Content-Type-Options: nosniff の意味
これはブラウザの「お節介機能」を止める設定です。
- 問題: 昔のブラウザ(特にIE)は、サーバーが「これは画像です(Content-Type: image/jpeg)」と言っても、ファイルの中身を見て「あれ?これHTMLタグが入ってるぞ。HTMLとして実行してあげよう!」と勝手に解釈(スニッフィング)することがありました。
- リスク: 画像アップロード機能に、画像に見せかけた悪意あるスクリプト(偽装ファイル)をアップロードされると、閲覧したユーザーのブラウザでスクリプトが実行されてしまいます(XSS)。
- 対策:
nosniffを設定すると、ブラウザに対して「Content-Typeヘッダを絶対視しろ。中身を勝手に推測するな」と命令できます。
これにより、ブラウザがJSONをHTMLとして誤解釈して実行してしまうのを防ぎます。
第7章:脆弱性診断手法
開発者自身が脆弱性を見つけるための手法です。
- 自動診断ツール: OWASP ZAPなどのプロキシツールを使用し、リクエスト/レスポンスをキャプチャして診断する。
- 手動診断: 重要なビジネスロジックに関しては、ツールの自動スキャンだけでなく、診断員(またはセキュリティ知識のあるエンジニア)によるチェックが必要。
「ツールはあくまで補助。仕様上の論理的な脆弱性は人間が見つける必要がある」
第8章:Webサイトの安全性を高めるために
アプリケーションのコードだけでなく、WebサーバーやPHPなどのプラットフォーム(ミドルウェア)の設定、およびネットワークレベルでの防御について解説しています。
プラットフォームの安全化
| 対策レイヤー | 主なアクション | 目的 |
| Webサーバー | 不要なモジュール無効化、バナー情報の隠蔽 | 攻撃者にOSやバージョン情報を与えない |
| PHP設定 | display_errors = Off (本番環境) | エラーメッセージからの情報漏洩(パス構造など)を防ぐ |
| ファイル権限 | Webサーバーが書き込めるディレクトリを最小限にする | 万が一侵入された際の改ざん・マルウェア設置リスクを減らす |
通信の保護と多層防御
- HTTPS(SSL/TLS): 今や必須です。盗聴防止だけでなく、通信相手が本物である証明にもなります。
- WAF (Web Application Firewall):
- アプリの前段に設置し、SQLインジェクションやXSSの特徴を持つ通信を遮断します。
- 注意: WAFはあくまで「保険」です。根本対策(コード修正)の代わりにはなりませんが、脆弱性が発見されてから修正されるまでの時間稼ぎとして有効です。
「インフラの設定ミス一つで、堅牢なアプリケーションコードも無意味になることがある」
第9章:安全なWebアプリケーションのための開発マネジメント
セキュリティは「技術」だけでなく「プロセス」の問題でもあります。開発ライフサイクル全体でどうセキュリティを担保するかを扱います。
開発工程ごとのセキュリティ対策
graph LR
A[要件定義] --> B[設計]
B --> C[実装]
C --> D[テスト]
D --> E[運用]
style A fill:#f9f,stroke:#333
style B fill:#f9f,stroke:#333
style C fill:#f9f,stroke:#333
style D fill:#f9f,stroke:#333
subgraph セキュリティアクション
A -.-> a1[セキュリティ要件の定義]
B -.-> b1[脅威分析・設計レビュー]
C -.-> c1[セキュアコーディング規約]
D -.-> d1[セキュリティ診断]
end脆弱性が見つかった時の対応
リリース後に脆弱性が見つかることはゼロにはできません。重要なのはその後の対応です。
- 窓口の設置:
security.txtやお問い合わせフォームなど、発見者が報告できるルートを用意する。 - トリアージ: 報告された内容が本当に脆弱性か、危険度はどの程度かを判断する(CVSSなどを活用)。
- 修正と公開: 修正パッチを適用し、影響を受けたユーザーへ誠実に告知する。
発注者と受注者の関係
セキュリティ要件は契約段階で明確にする必要があります。「安全なものを作ってください」という曖昧な発注ではなく、「IPAの『安全なウェブサイトの作り方』に準拠すること」といった具体的な基準を契約書や仕様書に盛り込むことがトラブル防止の鍵です。
アクションプラン:2026年のエンジニアが読むべきリソース
明日からの学習に追加すべき具体的なリソースです。
- OWASP API Security Top 10 を確認する
- Web Top 10(徳丸本の内容)だけでなく、API版のTop 10を見て、特に「認可(Authorization)」周りのチェック観点をアップデートしてください。
- GitHub Advanced Security や Snyk などのツールを知る
- コードを書く人間がセキュリティチェックをする時代(シフトレフト)です。依存ライブラリの脆弱性を自動検知するツールの使い方を学びましょう。
- 「IDトークン(OIDC)」と「アクセストークン(OAuth2.0)」の違いを説明できるようにする
- 認証基盤(Auth0, AWS Cognito, Firebase Authなど)を使うのが当たり前になりました。これらが内部でどう動いているか、JWT(JSON Web Token)の仕様を含めて理解を深めてください。
徳丸本の「原理原則」は不変ですが、戦場は「API」「ライブラリ」「AI」へと拡大しています。基礎を徳丸本で固め、応用をこれら新しいトピックで埋めていきましょう。

コメント