「セキュリティって大事」って聞くけど、実際なにが危なくて、どう対策すればいいのか分からない...。 そんな人向けに、IPAの「安全なWebサイトの作り方(第7版)」をベースに、よくある11の脆弱性と、その防ぎ方をザッと解説します。
本記事の目的
- 時間がない方でもどんな実装が危ないかを、コード例でザッと理解できる
- 危険なコードとその対策をセットで理解できる
- 初学者でも実践できるセキュリティ対策をざっくり把握する
脆弱性①:SQLインジェクション
どんな脆弱性?
ユーザーの入力をSQL文にそのまま埋め込むと、意図しない命令が実行されてしまう。
ダメな例
$name = $_GET['name'];
$sql = "SELECT * FROM users WHERE name = '$name'";
何がダメ?
$name
に a' OR '1'='1
と入力されると、全件取得される。
対策
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->bindParam(':name', $name);
$stmt->execute();
- SQLは文字列の連結で作らない
- プレースホルダ+バインドを使う
- エラー画面にSQL文を表示しない
- DBユーザーには読み取り専用など最低限の権限を与える
脆弱性②:OSコマンドインジェクション
どんな脆弱性?
exec()
や system()
などの関数にユーザー入力を渡すと、悪意あるコマンドがそのまま実行されてしまう。
ダメな例
$filename = $_GET['file'];
exec("ls $filename");
何がダメ?
$filename
に; rm -rf /
を渡すと、
ls ; rm -rf /
と解釈されてしまい、システムが破壊される危険がある。
対策
$allowedDirs = ['images', 'docs'];
$dir = $_GET['dir'];
if (in_array($dir, $allowedDirs)) {
exec("ls " . escapeshellarg($dir));
}
- ユーザー入力をOSコマンドに直接渡さない
-
escapeshellarg()
/escapeshellcmd()
を使用する - 可能なら
exec()
を使わず、PHP関数(例:scandir()
)で代替 - 処理対象をホワイトリストで制限する
脆弱性③:ディレクトリ・トラバーサル
どんな脆弱性?
ファイル名に../
を含めることで、上の階層へ移動し、任意のファイルにアクセスされる。
ダメな例
$file = $_GET['file'];
readfile("/uploads/" . $file);
何がダメ?
file=../../../../etc/passwd
のように指定されると、
システムの機密ファイルが読まれてしまう。
対策
$filename = basename($_GET['file']);
$filepath = "/uploads/" . $filename;
if (file_exists($filepath)) {
readfile($filepath);
}
-
basename()
でファイル名に../
を含めないようにする - ファイル名に使える文字列はホワイトリストで管理
- 絶対パスと
realpath()
でパスの正当性を確認
脆弱性④:セッション管理の不備
どんな脆弱性?
セッションIDを盗まれたり、ログイン後に再生成されないと、なりすましログインが可能になる。
ダメな例
session_start();
// ログイン後もセッションIDを再生成しない
何がダメ?
攻撃者にセッションIDが漏れたら、そのまま乗っ取られる。
また、IDを固定したままだと「セッション固定攻撃」にも弱い。
対策
session_start();
if ($isLoginSuccessful) {
session_regenerate_id(true); // IDを再生成
}
- ログイン成功時に
session_regenerate_id(true)
- CookieにSecure / HttpOnly / SameSite を設定
- HTTPSを常時使用(セッション盗聴防止)
脆弱性⑤:クロスサイトスクリプティング(XSS)
どんな脆弱性?
HTMLにユーザー入力をそのまま表示すると、悪意あるJavaScriptが実行されてしまう。
ダメな例
echo $_GET['comment'];
何がダメ?
攻撃者が次のような入力をすると…
<script>alert(document.cookie)</script>
→ そのスクリプトが訪問者のブラウザで実行されてしまう
対策
echo htmlspecialchars($_GET['comment'], ENT_QUOTES, 'UTF-8');
- HTMLに出力するすべてのユーザー入力はエスケープ
- JavaScript内で使う場合は、JS向けエスケープを検討
- 属性値、URLなどの用途に応じたエスケープ方法を使い分ける
脆弱性⑥:クロスサイトリクエストフォージェリ(CSRF)
どんな脆弱性?
ユーザーがログイン状態のまま、別の悪意あるサイトから勝手にリクエストを送られてしまう。
ダメな例
攻撃者が以下のようなHTMLを用意:
<img src="https://example.com/delete_account.php">
→ 被害者がこのページを開くと、自動で delete_account.php にアクセスされてしまう。
何がダメ?
ユーザーがこのHTMLを含むページを開くと、セッションIDが格納されたCookieが自動的に送信され、意図しない操作が実行されてしまう。
対策
//トークン発行(初回のみ)
session_start();
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
<!-- フォームにトークンを埋め込む -->
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
//トークン検証
session_start();
if (
!isset($_POST['csrf_token'], $_SESSION['csrf_token']) ||
$_POST['csrf_token'] !== $_SESSION['csrf_token']
) {
die('Invalid CSRF token');
}
- フォーム送信にCSRFトークンを埋め込んで、サーバー側で検証
- トークンはsessionで管理。
random_bytes()
で安全に生成 - Cookieには
SameSite=Lax
を指定して外部サイトからの送信をブロック
脆弱性⑦:HTTPヘッダインジェクション
どんな脆弱性?
ユーザー入力をheader()
関数などで使う際に、改行文字を含めると任意のヘッダを追加できる。
ダメな例
header("Location: ".$_GET['url']);
→ url=nextpage.php%0d%0aSet-Cookie:admin=true
何がダメ?
ヘッダの改行により、本来意図していないヘッダが注入される。
対策
$url = filter_var($_GET['url'], FILTER_SANITIZE_URL);
if (!preg_match('/^https?:\/\/yourdomain\.com/', $url)) {
$url = '/default';
}
header("Location: " . $url);
- 改行文字(\r, \n)は除去する
- リダイレクトURLはホワイトリスト制御
- フレームワークに頼るとより安全(例:redirect()->to())
脆弱性⑧:メールヘッダインジェクション
どんな脆弱性?
mail()
関数などでメールヘッダにユーザー入力をそのまま渡すと、Bcc挿入などの攻撃が可能。
ダメな例
$to = $_POST['email']; // 入力: victim@example.com\r\nBcc:attacker@example.com
mail($to, "Subject", "Body");
何がダメ?
意図しない相手にもメールが送信される・本文の改ざんなどが可能。
対策
$email = str_replace(["\r", "\n"], '', $_POST['email']);
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
mail($email, "Subject", "Body");
}
- メールヘッダに改行文字が含まれないかチェック
-
filter_var(..., FILTER_VALIDATE_EMAIL)
でバリデーション - 外部ライブラリ(PHPMailer など)を使うのも有効
脆弱性⑨:クリックジャッキング
どんな脆弱性?
悪意あるサイトが透明なiframe
で正規サイトを覆い隠し、ユーザーが意図せずクリックしてしまうことで、意図しない操作を実行させる。
ダメな例
// ヘッダに何も設定されていない
何がダメ?
以下のような攻撃ページを用意されると…
<iframe src="https://example.com/important-action.php" style="opacity:0; position:absolute; top:0; left:0; width:100%; height:100%;"></iframe>
ユーザーがボタンをクリックしたつもりでも、実際は透明なiframe内の危険な操作を実行してしまう。
対策
// クリックジャッキング対策ヘッダを追加
header('X-Frame-Options: DENY');
または同等のContent Security Policy(CSP)での対策も有効
header("Content-Security-Policy: frame-ancestors 'none';");
-
X-Frame-Options: DENY
や CSPでiframe埋め込みを防止 - フレームを使う必要がある場合でも、許可するドメインを明示的に制限する
脆弱性⑩:バッファオーバーフロー
どんな脆弱性?
もともとC/C++で多い脆弱性。PHPでは原則発生しないが、ユーザーの意図しない大量入力を処理する際の不備が原因で、メモリ不足や異常動作を招くことがある。**。
ダメな例
$input = $_POST['memo'];
file_put_contents('/tmp/memo.txt', $input);
何がダメ?
攻撃者が数百MB以上の文字列を送信すると、サーバーの負荷が急増し、DoS的な攻撃になる可能性がある。
対策
$input = $_POST['memo'];
if (strlen($input) > 1000) {
die('入力が長すぎます');
}
file_put_contents('/tmp/memo.txt', $input);
- ユーザー入力の長さに制限を設ける
- PHPの
post_max_size
やmax_input_vars
などの設定も確認 - ファイルアップロードサイズも必要に応じて制限する
脆弱性⑪:認可制御の欠如
どんな脆弱性?
ログイン後の操作に対して、ユーザーの権限を確認しないまま処理してしまうことで、管理者機能などを不正に使われる。
ダメな例
// 管理者ページ(認証は通っているが、権限チェックがない)
if (isset($_SESSION['user_id'])) {
// 管理者限定の処理
deleteAllUsers();
}
何がダメ?
一般ユーザーがログインしていれば、URLを直接入力するなどで管理機能を実行できてしまう。
対策
session_start();
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
die('許可されていない操作です');
}
// 管理者限定処理
deleteAllUsers();
- 認証(ログイン)と認可(権限チェック)は別物として実装
- ロールや権限に応じた機能制御をサーバー側で行う
- 画面側の非表示だけでは不十分(直接URLアクセスに備える)
まとめ
セキュリティ対策は一見むずかしく思えるかもしれませんが、「どんな脆弱性があるのか」「どう直すのか」をセットで知るだけで、十分に防げるようになります。「何が危なくて、どう直せばいいのか?」が分かれば、今日からでも実践可能です。実際、セキュリティ対策はたった1行のコード変更で防げるケースも少なくありません。
”知っていれば防げる。知らなければ狙われる。”
このガイドが、あなたのプロダクトを安全に一歩でも近づける助けになれば幸いです。