FlawOpen

flawopen.com/PHPのSQL Injection/mysqliとPDO

MySQLiとPDO:安全なPrepared Statement

リファレンス
要点

Prepared StatementはSQL命令を固定し、入力値を別に送信します。ラベル付きの空欄を埋めるイメージです。PDOもMySQLiも対応します。安全性はライブラリ名ではなく、値を正しくバインドすることから生まれます。

同じクエリを各APIで安全に準備する

危険 — リクエスト値を連結したSQL
$userId = $_GET['id'];
$sql = "SELECT id, email FROM users WHERE id = " . $userId;

// Both calls execute the already-built SQL string:
$rows = $mysqli->query($sql);
$rows = $pdo->query($sql);
安全 — 両APIで値をバインド
// PDO
$stmt = $pdo->prepare(
    "SELECT id, email FROM users WHERE id = :id"
);
$stmt->execute(["id" => $userId]);

// MySQLi
$stmt = $mysqli->prepare(
    "SELECT id, email FROM users WHERE id = ?"
);
$stmt->bind_param("i", $userId);
$stmt->execute();

PDOの主な違いはデータベース間の移植性

PDOの主な違いはデータベース間の移植性

PDOは複数ドライバー共通のAPIを提供し、MySQLiはMySQLとMariaDB向けです。PDOだけでSQLが移植可能になるわけではなく、方言・ドライバー動作・機能は異なります。

プレースホルダーの形式が異なる

PDOはドライバーにより:idのような名前付き、または?位置指定を使えます。同じ文で混在させないでください。MySQLiは?を使います。どちらも値を束縛しますが、表・列名、キーワード、ソート方向は束縛できません。

PDOの準備方式はドライバーによる

PDOドライバーはネイティブまたはエミュレート準備を使います。MySQLではPDO::ATTR_EMULATE_PREPARESで利用可能な場合のエミュレーションを制御します。ネイティブ準備非対応や失敗時にエミュレートへ切り替わる場合があります。ドライバーの文書に従ってください。この設定は補間SQLを安全にしません。SQLを固定し値をバインドします。

どちらのライブラリでも守るSQL Injection対策

prepare()を使い、信頼できない値をすべて束縛します。SQL構造は固定し、動的識別子はサーバー側の厳格なマップか有限のクエリ群から選びます。Prepared Statementは行へのアクセス権を与えません。必要に応じて認証ユーザーの範囲に絞ります。LIKEではバインドがSQL構文注入を防ぎますが、リテラル検索用にエスケープしない限り%と_はワイルドカードです。

PDOとMySQLiの選び方

よくある質問

PDOの名前付きプレースホルダーはMySQLiの?より安全ですか?

いいえ。クエリを準備し値を束縛すれば、どちらも安全に値を扱えます。名前付きは読みやすさの利点ですが、識別子を束縛せず認可要件も変えません。

PDOのエミュレート準備は常に無効化すべきですか?

一律には言えません。ドライバーの対応とフォールバックは異なります。文書と要件を確認し、どの方式でもSQLを固定して値をパラメーターで渡します。

Prepared Statementでテーブル名や列名を束縛できますか?

できません。パラメーターは値であり、SQL識別子やキーワードではありません。クライアントの選択肢をサーバー側の有限な信頼済みSQL断片に対応付けます。

参考資料

関連項目 SQL インジェクション(CWE-89)の仕組みと各言語での防ぎ方 — flawopen.com NHibernate は SQL Injection を防げますか? MongoDB NoSQL Injection:安全なクエリフィルター 数値 ID でも文字列連結で SQL Injection が起きますか? パラメータ化クエリとは何か Prisma $queryRaw は SQL Injection に安全? Railsのwhere()文字列補間は安全?