PHPのSQLインジェクション対策を完全解説!初心者でも安全にデータベースを扱う方法
生徒
「PHPでデータベースに保存する処理を作っているんですが、SQLインジェクションって危ないと聞きました。どうやって防げばいいんですか?」
先生
「SQLインジェクションは、フォームなどに入力された文字を悪用して不正なSQLを実行されてしまう攻撃のことです。PHPでは、PDOやmysqliを正しく使うことで簡単に対策できますよ。」
生徒
「PDOって聞いたことはありますけど、どう書けば安全になるんですか?」
先生
「では、まずSQLインジェクションがどんな攻撃かを理解して、次にPDOとmysqliを使った安全な書き方を見ていきましょう。」
1. SQLインジェクションとは?初心者でもわかる危険性の解説
SQLインジェクションとは、ユーザーが入力する文字の中に「特別な命令」を混ぜて、データベースに不正な操作をさせてしまう攻撃です。例えば、ログイン画面で本来はIDとパスワードだけを入力させる部分に、悪意のあるSQL文を紛れ込ませることで、データの書き換えや削除が行われてしまうことがあります。
たとえるなら、注文書を書く欄に「配送先:倉庫→そのまま全部捨てろ」と追加で書かれてしまって、そのまま実行してしまうようなものです。普通はそんな指示は無視すべきですが、対策をしていないとPHPはそのまま命令を実行してしまいます。
2. なぜSQLインジェクションが起きるのか?
原因は「文字列をそのままSQL文にくっつけてしまうこと」です。PHPでありがちな失敗例として、下記のようなコードがあります。
$user = $_POST['user'];
$sql = "SELECT * FROM users WHERE name = '$user'";
$stmt = $pdo->query($sql);
このコードは一見正しく見えますが、ユーザーが次のように入力したらどうでしょう?
' OR '1'='1
SQL文は下記のようになり、全ユーザーの情報が表示されてしまいます。
SELECT * FROM users WHERE name = '' OR '1'='1'
これがSQLインジェクションの典型的な例です。このような危険を防ぐためには、入力された値を「そのままSQLにつなげない」ことが重要です。
3. PDOを使った安全なSQLインジェクション対策
もっとも一般的で安全な方法は「プリペアドステートメント」を使うことです。プリペアドステートメントとは、SQL文の中に「?」などのプレースホルダー(後で値を入れる場所)を用意して、入力値を自動で安全に処理してくれる仕組みのことです。
PDOを使った安全なコードは下記のようになります。
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$user = $_POST['user'];
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->bindParam(':name', $user, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetchAll();
ポイントは、値を prepare() と bindParam() に渡すことで、mysql側が自動で危険な文字をエスケープ(安全に変換)してくれる点です。これにより、不正なSQL文が実行されることを防げます。
4. mysqliでのSQLインジェクション対策
PHPではPDOだけでなく、mysqli を使ったデータベース接続でもSQLインジェクション対策ができます。難しそうに見えますが、基本はPDOと同じ「プリペアドステートメント」を使うだけです。
$mysqli = new mysqli("localhost", "root", "", "test");
$user = $_POST['user'];
$stmt = $mysqli->prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $user);
$stmt->execute();
$result = $stmt->get_result();
「s」は文字列(string)の意味で、値の型を指定します。mysqliでも、prepare → bind → execute の流れで安全にSQLを実行できます。
5. SQLインジェクション対策として絶対にやってはいけないこと
初心者の方が特にやってしまいがちなNG例も紹介します。これらは危険なので避けましょう。
- ユーザー入力を直接SQLに連結する
- addslashes() でエスケープすれば大丈夫だと思い込む
- Magic Quotes(古いPHPの機能)に頼る
現代のPHPでは、プリペアドステートメントを使うことがもっとも安全で、最も推奨される方法です。
6. フォーム入力をバリデーションして安全性をさらに高める
SQLインジェクション対策には、プリペアドステートメントが絶対ですが、さらに安全性を高めるためには「バリデーション」が重要です。バリデーションとは、入力値に対して「正しい形式かどうか」を確認する処理です。
例えば、ログインフォームで「ユーザー名は20文字以内」「ひらがなは禁止」などのルールを作り、それを守らない入力をはじくことで、攻撃の入り口を狭めることができます。
まとめ
SQLインジェクション対策の重要ポイント
PHPでデータベースを扱う際に最も注意すべきセキュリティリスクの一つがSQLインジェクションです。この記事で解説した通り、ユーザーからの入力値をそのままSQL文に連結してしまうと、意図しない命令が実行され、データの漏洩や改ざん、削除といった重大な被害につながる可能性があります。 特にログイン機能や検索機能、登録フォームなど、ユーザー入力を扱う場面では、常に安全性を意識することが重要です。
SQLインジェクションを防ぐための基本は、「ユーザー入力を信用しない」という考え方です。そのうえで、PHPではPDOやmysqliのプリペアドステートメントを活用することで、安全にSQLを実行することができます。 prepareメソッドでSQL文の構造を先に定義し、bindParamやbind_paramで値を後から渡すことで、入力値が単なるデータとして扱われ、不正なSQLとして解釈されることを防げます。
安全な実装のポイントを整理
- SQL文にユーザー入力を直接連結しない
- PDOやmysqliのプリペアドステートメントを必ず使用する
- bindParamやbind_paramで型を明示する
- フォーム入力にバリデーションを実装する
- 不要なデータ取得を避け、最小限の権限で操作する
また、単にプリペアドステートメントを使うだけでなく、入力チェック(バリデーション)も組み合わせることで、より堅牢なシステムを構築できます。 例えば、ユーザー名の文字数制限や使用可能な文字の制御を行うことで、攻撃の入り口をさらに狭めることができます。
サンプルプログラム(安全な実装例)
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// 入力値の取得
$user = $_POST['user'] ?? '';
// 簡易バリデーション(空チェックと文字数制限)
if ($user === '' || mb_strlen($user) > 20) {
echo "不正な入力です";
exit;
}
// プリペアドステートメントの使用
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->bindParam(':name', $user, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetchAll();
foreach ($result as $row) {
echo htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . "<br>";
}
?>
このサンプルでは、プリペアドステートメントに加えてバリデーション処理も組み込んでいます。さらに、出力時にはhtmlspecialchars関数を使用しており、クロスサイトスクリプティング対策にも配慮しています。 このように、複数のセキュリティ対策を組み合わせることで、安全性の高いPHPアプリケーションを実現できます。
実務で意識したいセキュリティの考え方
実務では、SQLインジェクション対策だけでなく、さまざまな攻撃を想定した設計が求められます。データベース接続ユーザーに必要最低限の権限のみを付与する、エラーメッセージに詳細情報を表示しない、ログを適切に管理するなど、総合的なセキュリティ対策が重要です。 また、フレームワークを使用する場合でも内部の仕組みを理解しておくことで、より安全なコードを書くことができます。
生徒
「SQLインジェクションって、ただの入力ミスじゃなくて、ちゃんとした攻撃なんですね。今まで深く考えていませんでした。」
先生
「そうですね。見た目は普通の入力でも、裏では危険な命令になっていることがあります。だからこそ、仕組みで防ぐことが大切なんです。」
生徒
「プリペアドステートメントを使えば安全になるというのは理解できました。PDOとmysqliのどちらでも対応できるのも安心です。」
先生
「その通りです。ただし、それだけで完璧ではありません。バリデーションや出力時のエスケープも合わせて実装することで、より安全になります。」
生徒
「なるほど、入力・処理・出力のすべてで対策する必要があるんですね。」
先生
「はい。それがセキュリティの基本です。今回学んだSQLインジェクション対策は、PHPでの開発において必須の知識なので、ぜひ実際の開発でも活用してください。」