CURRICULUM  /  APPENDIX F

付録F ─ セキュリティチェックリスト

コードを書いたあと、公開する前に確認するためのチェックリストです。

本編で学んだセキュリティの急所を、一覧にまとめています。

全てを暗記する必要はありません。「コードが書けた」と思ったあとに、このページを開いて一つずつ確認してください。

本編で扱ったものには、該当する章へのリンクを添えています。

1. 入力の検品(バリデーション)

チェック項目解説本編
型の検証をしているか 数値を期待するパラメータには (int) キャストや filter_var($id, FILTER_VALIDATE_INT) を使う。文字列のまま処理に渡さない 02-01, 02-02
文字列の長さを制限しているか 名前欄に10万文字を入れられると、DBの容量を圧迫したり表示が壊れたりする。mb_strlen() で上限を設ける 02-01
メールアドレスの形式を検証しているか filter_var($email, FILTER_VALIDATE_EMAIL) で形を確認してからDBに問い合わせる 02-01, 02-02
許可する値をホワイトリストで絞っているか セレクトボックスやラジオボタンの値も、サーバー側で「許可リストに含まれるか」を確認する。フォームの選択肢はブラウザ側で改ざんできる 02-01
trim() で前後の空白を除去しているか 空白だけの入力を弾いたり、意図しない空白による不一致を防ぐ基本処理 06-02
JavaScriptの検証だけに頼っていないか ブラウザ側のバリデーション(required 属性、JS検証)は補助であり、セキュリティ対策にはならない。必ずサーバー側でも検証する 01-05
リダイレクト先URLを検証しているか(オープンリダイレクト対策) ログイン後の戻り先などをURLパラメータ(?redirect=...)で受け取る場合、外部サイトのURLが指定されていないか検証する。チェックなしで header('Location: ' . $redirect) を実行すると、攻撃者がフィッシングサイトへ誘導できてしまう。自サイトのパスで始まるURLだけを許可する

2. SQLインジェクション対策

チェック項目解説本編
全てのSQL文でプレースホルダ(プリペアドステートメント)を使っているか SQL文と値を別々に渡すことで、値がコードとして解釈される経路を閉じる。これがSQLインジェクション対策の唯一の正解 01-06, 02-02
変数をSQL文に文字列連結(.)していないか "SELECT * FROM users WHERE id = " . $id のような書き方は全てSQLインジェクションの温床。見つけたら即座にプレースホルダに書き換える 01-06, 02-02
addslashes()mysql_real_escape_string() を使っていないか これらは古い対策で、抜け道がある。新しいコードでは使わない 02-02
LIKE句のワイルドカードをエスケープしているか ユーザー入力を LIKE 句に使う場合、%_ をエスケープしないと意図しない全件マッチが起きる。str_replace(['%', '_'], ['\\%', '\\_'], $input) で処理する

3. XSS対策(クロスサイトスクリプティング)

チェック項目解説本編
HTMLに出力する全ての変数に htmlspecialchars()(または e())を通しているか 1箇所でも忘れればそこがXSSの穴になる。「出力時に必ずエスケープ」を習慣にする 01-05, 02-03
引数に ENT_QUOTES'UTF-8' を指定しているか htmlspecialchars($str, ENT_QUOTES, 'UTF-8') が正しい形。引数を省略するとシングルクォートがエスケープされず穴が残る 02-03
JavaScript・CSS・URLの文脈では別のエスケープをしているか htmlspecialchars() はHTMLの文脈用。JavaScriptの文字列内、CSSの値、URLパラメータには、それぞれ別のエスケープが必要 02-03
ユーザー入力をHTMLタグとして出力していないか リッチテキストエディタ等でHTMLを受け入れる場合は、許可するタグをホワイトリストで制限する。安易に e() を外さない 02-03

4. CSRF対策(クロスサイトリクエストフォージェリ)

チェック項目解説本編
POSTフォームにCSRFトークンを埋め込んでいるか bin2hex(random_bytes(32)) でトークンを生成し、セッションとhiddenフィールドの両方に保持する 02-05
トークンの比較に hash_equals() を使っているか ===== での比較はタイミング攻撃に対して脆弱。hash_equals($saved, $sent) で定時間比較を行う 02-05
破壊的操作(削除・更新・送金等)をGETで受け付けていないか GETリクエストは画像タグやリンクで簡単に発火できるため、破壊的操作は必ずPOSTで受ける 02-02, 02-05
トークンの生成に md5(time()) 等の予測可能な値を使っていないか time() は秒単位で予測できる。random_bytes() を使って暗号学的に安全な乱数を生成する 02-05

5. 認証(Authentication)

チェック項目解説本編
パスワードを password_hash() でハッシュ化して保存しているか 平文保存は論外。MD5やSHA-1も使ってはいけない。password_hash($pw, PASSWORD_DEFAULT) がPHPの正解 02-04, 04-04
パスワード照合に password_verify() を使っているか ハッシュ値を自分で比較しない。password_verify($input, $hashFromDb) がソルトの違いも吸収してくれる 02-04, 04-04
ログイン成功時に session_regenerate_id(true) を呼んでいるか セッション固定攻撃を防ぐ。ログイン前のセッションIDを引き継がせない 04-04
存在しないユーザーでも password_verify() を走らせているか ユーザーが見つからなかった場合に即座にエラーを返すと、処理時間の差から「そのメールアドレスは未登録」が推測できる(タイミング攻撃)。ダミーハッシュで時間を揃える 04-04
ログイン試行回数を制限しているか ブルートフォース攻撃(総当たり)を防ぐために、一定回数の失敗後にアカウントを一時ロックするか、応答を遅延させる 04-04

6. 認可(Authorization)

チェック項目解説本編
ログインが必要なページで、ログイン状態を確認しているか URLを直接入力すればログイン画面を飛ばせてしまう。全ての保護ページの先頭で認証チェックを行う 04-04
ロール(役割)に基づくアクセス制御をしているか 管理者用ページに一般ユーザーがアクセスできないようにする。require_role('admin') のようなヘルパーで統一的に制御する 04-04
「自分のデータだけ操作できる」チェックを入れているか URLの ?id=5?id=6 に書き換えるだけで他人のデータが見えたり消せたりしないように、データの所有者を確認する 04-04

7. セッション管理

チェック項目解説本編
セッションCookieに HttpOnly 属性を付けているか JavaScriptからCookieを読めなくすることで、XSS経由でのセッションID窃取を防ぐ 02-04
セッションCookieに Secure 属性を付けているか HTTPS通信でのみCookieを送信する設定。HTTP接続時にセッションIDが平文で流れることを防ぐ 02-04
セッションCookieに SameSite 属性を設定しているか Lax または Strict に設定することで、他サイトからのリクエストにCookieが付かなくなり、CSRFの第一防衛線になる 02-04, 02-05

8. ファイルアップロード

チェック項目解説本編
拡張子をホワイトリストで検査しているか 「許可するのはこれだけ」というリスト方式で検品する。ブラックリスト方式は列挙漏れで必ず破綻する 02-05
サーバー側でファイルの中身を検査しているか finfo_file()getimagesize() を使い、自分で判定する。$_FILES['file']['type'] は攻撃者が自由に書き換えられるため信用してはいけない 02-05
ファイル名を自分でリネームしているか 元のファイル名には二重拡張子(avatar.php.jpg)やパストラバーサル(../../shell.php)が仕込まれている可能性がある。bin2hex(random_bytes(16)) 等で新しい名前を生成する 02-05
アップロード先をドキュメントルート外にしているか URLで直接叩ける場所に保存すると、万一PHPとして実行される設定が残っていた場合にRCE(任意コード実行)に直結する 02-05
アップロードディレクトリでPHPの実行を禁止しているか Apacheなら .htaccessphp_flag engine off、nginxなら設定ファイルで対応する。防衛線4と重ねて運用する 02-05
アップロードファイルのサイズを制限しているか PHPの upload_max_filesizepost_max_size を適切に設定する。サーバー側でもファイルサイズを検証し、巨大なファイルによるリソース枯渇を防ぐ

9. エラー処理と情報漏洩防止

チェック項目解説本編
本番環境で display_errors = Off にしているか エラーの詳細(ファイルパス、DB名、テーブル名、SQL文など)が画面に出ると、攻撃者に内部構造を教えることになる 05-01, 06-03
エラーを error_log() でログに記録しているか 画面には出さないが、ログには残す。error_reporting(E_ALL)ini_set('log_errors', '1') を設定する 05-01
例外発生時にユーザーに技術的な詳細を見せていないか catch ブロックでは $e->getMessage() をログに書き、画面には「システムエラーが発生しました」等の一般的なメッセージだけ表示する 05-01
デバッグ用のコード(var_dumpprint_r)を本番から除去しているか 開発中に書いたデバッグ出力が残っていると、内部データやSQL結果が丸見えになる

10. 通信とインフラ

チェック項目解説本編
HTTPS で配信しているか パスワードやセッションCookieが平文で流れないよう、通信を暗号化する。レンタルサーバーなら無料SSL証明書が使えることが多い 06-03
HTTPからHTTPSへのリダイレクトを設定しているか http:// でアクセスされたときに自動で https:// へ転送する。.htaccessRewriteRule やサーバー設定で対応する

11. HTTPセキュリティヘッダ

これらのヘッダは、ブラウザに「このサイトをどう扱うか」を指示するものです。

PHPの header() 関数か、.htaccess で設定します。

本編では詳しく扱っていませんが、本番公開時に設定しておくと防御が一段厚くなります。

チェック項目解説本編
X-Content-Type-Options: nosniff ブラウザがMIMEタイプを勝手に推測(sniffing)して、テキストファイルをHTMLとして実行してしまう事故を防ぐ
X-Frame-Options: DENY または SAMEORIGIN このサイトが他サイトの <iframe> に埋め込まれることを禁止する。クリックジャッキング攻撃の対策
Content-Security-Policy(CSP) 読み込み可能なスクリプトやスタイルの提供元を限定する。XSSが仮に成立しても、外部スクリプトの読み込みをブロックできる。最初は default-src 'self' から始めて、必要に応じて緩めるとよい
Referrer-Policy: strict-origin-when-cross-origin 外部サイトへの遷移時に、URLのパスやクエリパラメータが Referer ヘッダで漏れることを防ぐ

12. サーバー設定と保守

チェック項目解説本編
ディレクトリリスティングを無効化しているか Apacheの Options -Indexes を設定していないと、index.php がないディレクトリにアクセスしたときにファイル一覧が丸見えになる
設定ファイル(config.php 等)への直接アクセスを防いでいるか DB接続情報やAPIキーを含むファイルが直接閲覧できないよう、ドキュメントルート外に置くか .htaccess でアクセスを禁止する
.git フォルダを公開していないか Gitリポジトリをそのままサーバーに置くと、/.git/config 等からソースコード全体が取得できてしまう。.htaccessRedirectMatch 404 /\.git を設定するか、デプロイ時に除外する
PHPのバージョンは最新の安定版か サポートが終了したPHPバージョンにはセキュリティパッチが提供されない。レンタルサーバーのコントロールパネルで確認・変更する
サードパーティライブラリを定期的に更新しているか Stripeライブラリ等の外部パッケージに脆弱性が見つかることがある。Composerを使っていれば composer update で更新できる
エラーログを定期的に確認しているか 週に1回でもエラーログを見ておけば、不正なアクセスの兆候や未知のエラーに早期に気付ける 05-01, 06-03

このチェックリストの使い方

全ての項目に一度で対応する必要はありません。

以下の優先順位で進めると、最も効果が高いものから順に守りが固まります。

最優先(これがないと致命的)

SQLインジェクション対策(項目2)、XSS対策(項目3)、パスワードのハッシュ化(項目5)、display_errorsの無効化(項目9)、HTTPS(項目10)。

ここが抜けていると、データの漏洩や改ざんが現実的に起こり得ます。

次に重要(穴を塞ぐ)

CSRF対策(項目4)、認可チェック(項目6)、セッション管理(項目7)、入力の検品(項目1)。

機能としては動いているが、悪意のあるリクエストを受けたときに事故が起きる領域です。

公開前に確認(守りを厚くする)

ファイルアップロード(項目8)、エラー処理の仕上げ(項目9の残り)、HTTPセキュリティヘッダ(項目11)、サーバー設定(項目12)。

一つひとつは地味ですが、重ねることで防御の層が厚くなります。

コードを書く力は、同時に壊す力でもあります。

このリストを使って守りの習慣を身につけた人は、自分のサービスだけでなく、使ってくれる人の安全も守れる人です。