概要

Webセキュリティは「入力は信じない・保存は暗号化・出力は逃がす・通信は守る・状態は検証する」の 5原則がベースです。このページでは、XSS / CSRF / SQLインジェクション / クリックジャッキング など 代表的な脅威と、PHP/フロントの実装指針をまとめました。右の目次からジャンプできます。

✅ まず押さえるチェックリスト

  • 出力時は htmlspecialchars() でエスケープ
  • フォームは CSRF トークンで保護
  • DB はプリペアドステートメントのみ
  • 重要情報は暗号化 / ハッシュ化(パスワードは password_hash()
  • 強いセキュリティヘッダ(CSP / HSTS / COOP / CORP)
  • Cookie は HttpOnly + Secure + SameSite

基本

XSS(クロスサイトスクリプティング)

攻撃者が用意したスクリプトが、被害者のブラウザで実行されてしまう脆弱性。対策は出力時のエスケープコンテンツ分離(CSP)

// 出力時は必ずエスケープ
$safe = htmlspecialchars($userInput, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo "<p>".$safe."</p>";

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

ユーザーが意図しないリクエストを、別サイト経由で送らされる攻撃。対策はCSRFトークンSameSite Cookie

// トークン生成(ログイン時など)
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

// フォームに埋め込む
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES) ?>">

// 受信側で検証
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
  http_response_code(403);
  exit('CSRF token invalid');
}

SQLインジェクション

入力値がSQL文として解釈される攻撃。対策はプリペアドステートメント一択。

$pdo = new PDO('mysql:host=localhost;dbname=app;charset=utf8mb4', 'user', 'pass', [
  PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->bindValue(':email', $email, PDO::PARAM_STR);
$stmt->execute();
$user = $stmt->fetch();

クリックジャッキング

透明なIFRAMEでボタンを隠し、誤クリックを誘導。対策は frame-ancestors 'none' などのCSPまたは X-Frame-Options: DENY

基礎

パスワード強度チェッカー

長さ・大小英字・数字・記号の多様性で評価します。

安全なパスワード保存

// ハッシュ化して保存
$hash = password_hash($password, PASSWORD_DEFAULT);

// 認証時は検証
if (password_verify($password, $hash)) {
  // OK
}
ファイルアップロードの基本ガード
// 1) 拡張子とMIMEタイプの二重チェック
$allowed = ['png' => 'image/png', 'jpg' => 'image/jpeg'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name'] ?? '');
$ext = array_search($mime, $allowed, true);
if ($ext === false) { exit('Invalid file'); }

// 2) ランダム名にして保存ディレクトリを固定
$path = __DIR__ . '/uploads';
$filename = bin2hex(random_bytes(16)) . "." . $ext;
move_uploaded_file($_FILES['file']['tmp_name'], $path . "/" . $filename);

最新情報

ライブラリ更新・脆弱性情報は運用の要です。月1回以上の「棚卸し」を推奨します。 下のチェックで最低限の自己点検をしましょう。

⚙️ 手元セルフチェック

応用

セキュリティヘッダ一括メモ

header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"); // HSTS
header("Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: no-referrer-when-downgrade");
header("Permissions-Policy: geolocation=(), camera=(), microphone=()");
header("Cross-Origin-Opener-Policy: same-origin");
header("Cross-Origin-Resource-Policy: same-origin");

Rate Limit(超簡易版)

session_start();
$now = time();
$_SESSION['hits'] = array_filter($_SESSION['hits'] ?? [], fn($t) => $t >= $now - 60);
if (count($_SESSION['hits']) >= 30) { // 60秒で30回まで
  http_response_code(429);
  exit('Too Many Requests');
}
$_SESSION['hits'][] = $now;

発展

暗号化の安全な取り扱い
// OpenSSLで機密データを暗号化/復号(例)
$key = random_bytes(32); // AES-256
$iv  = random_bytes(16);
$cipher = 'aes-256-gcm';
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);
// 復号
$plain = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);
ログ監査とアラート

失敗ログイン回数や管理操作の監査ログを取り、しきい値超過で通知(Slack/Webhookなど)。

テクニック(動かして理解)

🧪 サニタイズ確認フォーム(XSS防止)

※ 入力は htmlspecialchars() で無害化して表示しています。

🔎 簡易ヘッダセルフチェック

このページで送出している主なヘッダ:

※ 実際のレスポンスヘッダはブラウザのDevTools → Networkで確認してください。