0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SQL】Parameterised Query(パラメータ化クエリ)とは何か

Posted at

はじめに

SQLインジェクションを根本から断つ最強の仕組み

SQLインジェクション対策の教科書を開くと、
必ず最初に登場するのが Parameterised Query(パラメータ化クエリ)

しかし、

「値を安全に渡せます」
という一言で片づけられがちで、その本質を深く理解しているエンジニアは意外と少ない。

この記事では、
仕組み → 書き方 → 何を防げるか → 何を防げないか
まで丁寧に解説し、最後に実務で必要な“安全な DB レイヤー設計”まで踏み込む。


1. Parameterised Query とは?

SQL文そのもの(構造)と、実際の入力値(パラメータ)を完全に分離して実行する仕組み。

  • SQL文 → 完全に固定のテンプレート
  • 値(ユーザー入力) → ドライバが安全にエスケープしてバインド

この“分離”こそが、SQLインジェクションを防ぐ最大のポイント。


2. なぜ必要なのか?(インジェクションの危険性)

悪い例は有名だけど復習。

username = input_user
sql = "SELECT * FROM users WHERE username = '" + username + "';"
cursor.execute(sql)

入力がこれだと:

' OR 1=1 --

SQLはこう壊れる:

SELECT * FROM users WHERE username = '' OR 1=1 --';

→ 全件ダンプ
→ ゲームオーバー


3. パラメータ化クエリの正しい姿

【Python / sqlite3 の例】

cursor.execute(
    "SELECT * FROM users WHERE username = ?",
    (input_user,)
)

【Python / psycopg2(PostgreSQL)】

cursor.execute(
    "SELECT * FROM users WHERE username = %s",
    (input_user,)
)

書き方は違っても、本質は完全に同じ。


4. 何がどう安全なのか?(内部動作)

パラメータ化時、SQLドライバは次のように動く:

  1. SQL構造だけを先に解析・コンパイル(Prepare)
  2. プレースホルダは「値を入れる穴」として扱われる
  3. 値は literal(ただの文字列)としてバインドされる
  4. パラメータは SQL 構造の一部には絶対にならない

つまり ' OR 1=1 -- が来ても、

ただの文字列

として扱われる。
構造に入り込む余地なし。
強すぎる。


5. Parameterised Query が防げるもの・防げないもの

防げる

  • 単純な SQL Injection
  • WHERE 句内の値インジェクション
  • INSERT/UPDATE の値
  • LIMIT の値(DBによる)
  • ログインフォーム攻撃全般

防げない

  • 動的に SQL の“構造”を作るケース
    例:
cursor.execute("SELECT " + column + " FROM accounts;")

ここに
column = "*, (SELECT password FROM users)"
が入ったらアウト。

なぜなら:

パラメータ化クエリは“値”だけに効き、カラム名やテーブル名(identifier)には使えない。

SQL仕様として、

SELECT ? FROM table;

カラム名をパラメータ化できない
→ これは避けようのない仕様。


6. どうして identifier(カラム名)には使えない?

理由はシンプル。

  • パラメータは「literal value」としてしか扱えない
  • カラム名やテーブル名は「identifier」
  • literal と identifier は SQL の構文階層が別

だから、次は成立しない:

SELECT ? FROM users;

たとえばアプリ側でこう実行すると:

cursor.execute("SELECT ? FROM users;", ("username",))

→ 実行すると 'column_name' という文字列扱いになり、構文エラー。


7. 実務における正しい使い方

原則1:ユーザー入力が SQL 構造に入らないようにする

ダメな例

order_by = input_value
cursor.execute(f"SELECT * FROM users ORDER BY {order_by}")

✔ 良い例(allowlist)

allowed = {"name", "email", "id"}
if order_by not in allowed:
    raise ValueError("Invalid order key")

cursor.execute(f"SELECT * FROM users ORDER BY {order_by}")

原則2:WHERE / INSERT / UPDATE の値は必ずパラメータ化

cursor.execute(
    "UPDATE users SET email=? WHERE id=?",
    (new_email, user_id)
)

原則3:ORM の Query Builder を使うと安全性が爆上がり

SQLAlchemy / Django ORM は
identifier を安全に扱えるように作られている。


8. Parameterised Query + 2nd-order SQL Injection

2nd-order SQLi にも触れておくと、

  • Parameterised Query は「その場でのインジェクション」は防げる
  • だが悪意ある値を DBに保存 → 別のSQL構文に混ざる
    …という攻撃は防げない

例:

  1. 攻撃者が特殊な値を登録
  2. 別処理がそれをカラム名として利用
  3. SQL が破壊される

つまり:

Parameterised Query = 万能ではない

“SQL 構造が動的に変わらない設計”とセットで初めて完全体になる。


まとめ

  • Parameterised Query は SQL構造と値を分離する技術
  • SQL Injection を最も根本的に防ぐ
  • WHERE / INSERT / UPDATE の値は完全に安全
  • ただし、カラム名・テーブル名など構造部分は防げない
  • 結局重要なのは
    SQL構造を動的にしない設計
    allowlist / ORM / QueryBuilder を併用する

パラメータ化クエリはシンプルだけど、
本質を理解すると アプリケーションのセキュリティ設計そのものが変わるくらい強力。


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?