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なら .htaccess で php_flag engine off、nginxなら設定ファイルで対応する。防衛線4と重ねて運用する |
02-05 |
| □ |
アップロードファイルのサイズを制限しているか |
PHPの upload_max_filesize と post_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_dump、print_r)を本番から除去しているか |
開発中に書いたデバッグ出力が残っていると、内部データやSQL結果が丸見えになる |
|
10. 通信とインフラ
| ✓ | チェック項目 | 解説 | 本編 |
| □ |
HTTPS で配信しているか |
パスワードやセッションCookieが平文で流れないよう、通信を暗号化する。レンタルサーバーなら無料SSL証明書が使えることが多い |
06-03 |
| □ |
HTTPからHTTPSへのリダイレクトを設定しているか |
http:// でアクセスされたときに自動で https:// へ転送する。.htaccess の RewriteRule やサーバー設定で対応する |
|
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 等からソースコード全体が取得できてしまう。.htaccess で RedirectMatch 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)。
一つひとつは地味ですが、重ねることで防御の層が厚くなります。
コードを書く力は、同時に壊す力でもあります。
このリストを使って守りの習慣を身につけた人は、自分のサービスだけでなく、使ってくれる人の安全も守れる人です。