はじめに
Webアプリケーションを公開すると、さまざまな攻撃にさらされます。
PHPを使った開発では特に SQLインジェクション・XSS(クロスサイトスクリプティング)・CSRF(クロスサイトリクエストフォージェリ) の3つが頻繁に狙われます。
この記事では、それぞれの攻撃の仕組みと、PHPで実践できる具体的な防御策を紹介します。
SQLインジェクション
攻撃の仕組み
ユーザーの入力をそのままSQLクエリに組み込むと、悪意あるSQLが実行される危険があります。
// ❌ 危険な例
$username = $_GET['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";
たとえば username に ' OR '1'='1 を渡すと、すべてのユーザーレコードが返ってしまいます。
対策:プリペアドステートメントを使う
PDOのプリペアドステートメントを使うと、入力値がSQL文として解釈されなくなります。
// ✅ 安全な例(PDO + プリペアドステートメント)
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$stmt->bindParam(':username', $_GET['username'], PDO::PARAM_STR);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
bindParam を使うことで、値は常にリテラルとして扱われ、SQLの一部として実行されません。
XSS(クロスサイトスクリプティング)
攻撃の仕組み
ユーザーが入力した文字列をそのままHTMLに出力すると、スクリプトが埋め込まれてしまいます。
// ❌ 危険な例
echo "こんにちは、" . $_GET['name'] . "さん!";
name に <script>alert('XSS')</script> を渡されると、スクリプトがブラウザで実行されます。
対策:htmlspecialcharsでエスケープする
出力する際には必ず htmlspecialchars を通してHTMLエスケープを行います。
// ✅ 安全な例
$name = htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
echo "こんにちは、" . $name . "さん!";
ENT_QUOTES を指定することで、シングルクォートもエスケープされます。
文字コードは必ず 'UTF-8' を明示してください。
エスケープが必要な場所
| 出力先 | 対策 |
|---|---|
| HTML要素の内容 | htmlspecialchars |
| HTML属性値 | htmlspecialchars(ENT_QUOTES必須) |
| JavaScript内 | json_encode |
| URLパラメータ | urlencode |
CSRF(クロスサイトリクエストフォージェリ)
攻撃の仕組み
ログイン済みのユーザーを罠サイトに誘導し、意図しないリクエスト(送金・パスワード変更など)を実行させる攻撃です。
サーバー側からは「正規のユーザーからのリクエスト」に見えてしまうため厄介です。
対策:CSRFトークンを使う
フォームにランダムなトークンを埋め込み、サーバー側で検証します。
// トークンの生成とセッションへの保存
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// フォームにトークンを埋め込む
<form method="POST" action="/update-profile">
<input type="hidden" name="csrf_token"
value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8') ?>">
<input type="text" name="username">
<button type="submit">更新</button>
</form>
// リクエスト受信時にトークンを検証する
session_start();
if (!isset($_POST['csrf_token']) ||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
exit('不正なリクエストです');
}
// 検証を通過したら処理を続ける
hash_equals を使うことで、タイミング攻撃(timing attack)にも対応できます。
その他の基本的なセキュリティ対策
パスワードのハッシュ化
パスワードは必ず password_hash で保存し、password_verify で検証します。
// 保存時
$hash = password_hash($password, PASSWORD_BCRYPT);
// 検証時
if (password_verify($inputPassword, $hash)) {
echo 'ログイン成功';
}
エラーメッセージの非表示
本番環境ではエラーの詳細をユーザーに表示しないよう設定します。
// 本番環境では表示をオフに
ini_set('display_errors', '0');
error_reporting(E_ALL);
ini_set('log_errors', '1');
まとめ
| 脅威 | 対策 |
|---|---|
| SQLインジェクション | プリペアドステートメント |
| XSS | htmlspecialchars でエスケープ |
| CSRF | CSRFトークンの検証 |
| パスワード漏洩 | password_hash でハッシュ化 |
セキュリティ対策は「後から追加するもの」ではなく、最初から組み込む習慣 が大切です。
今回紹介した対策を日頃のPHP開発に取り入れて、安全なアプリケーションを作っていきましょう。