XSS(クロスサイトスクリプティング)
攻撃者が用意したスクリプトが、被害者のブラウザで実行されてしまう脆弱性。対策は出力時のエスケープとコンテンツ分離(CSP)。
// 出力時は必ずエスケープ
$safe = htmlspecialchars($userInput, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo "<p>".$safe."</p>";
Webアプリの守り方を、動くデモとコードで体感しながら学ぶ。
Webセキュリティは「入力は信じない・保存は暗号化・出力は逃がす・通信は守る・状態は検証する」の 5原則がベースです。このページでは、XSS / CSRF / SQLインジェクション / クリックジャッキング など 代表的な脅威と、PHP/フロントの実装指針をまとめました。右の目次からジャンプできます。
htmlspecialchars() でエスケープpassword_hash())HttpOnly + Secure + SameSite攻撃者が用意したスクリプトが、被害者のブラウザで実行されてしまう脆弱性。対策は出力時のエスケープとコンテンツ分離(CSP)。
// 出力時は必ずエスケープ
$safe = htmlspecialchars($userInput, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo "<p>".$safe."</p>";
ユーザーが意図しないリクエストを、別サイト経由で送らされる攻撃。対策は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文として解釈される攻撃。対策はプリペアドステートメント一択。
$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");
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など)。
※ 入力は htmlspecialchars() で無害化して表示しています。
このページで送出している主なヘッダ:
※ 実際のレスポンスヘッダはブラウザのDevTools → Networkで確認してください。