Kodokon kodokon.com

セキュリティ:SQLインジェクション、権限、プリペアドステートメント

SQLインジェクションを再現し、プリペアドステートメントで無力化し、最小権限を適用しましょう。

11 分 · 3 問

このレッスンを Kodokon で開く

SQLインジェクションは、信頼できないデータがクエリの文字列に連結された瞬間に起こります。攻撃者は、文字列を閉じてロジックを乗っ取る値を与えます。usersテーブルに対する攻撃を再現してみましょう。入力ada' OR '1'='1を直接連結すると、正確なフィルターが常に真の条件に変わってしまいます。

SQL
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  is_admin INTEGER NOT NULL DEFAULT 0
);

INSERT INTO users (id, name, is_admin)
VALUES (1, 'ada', 0), (2, 'root', 1);

-- Dangerous concatenation (NEVER do this)
-- input = ada' OR '1'='1
SELECT * FROM users
WHERE name = 'ada' OR '1'='1';
注入されたクエリはすべての行を返す。

このペイロードが効くのは、引用符がリテラル'ada'を早々に閉じ、続くOR '1'='1'が普遍的に真の条件を加えるからです。ある変種は、元のクエリの残りをコメントアウトするために--を末尾に付け足します。教訓は、データとコードが同じ文字列を共有している限り、手作業のエスケープはどれも本当には信頼できないということです。

SQL
-- Positional placeholder
SELECT * FROM users WHERE name = ?;

-- Named placeholder
SELECT * FROM users WHERE name = :name;
SQLiteにおけるバインドパラメーターの二つの形。

対策はプリペアドステートメントです。ドライバーはまずクエリの構造をコンパイルし、それから値を別々にバインドします。バインドされたデータはSQLとして解析されることは決してなく、ただ比較されるだけです。おまけに、コンパイルされた構造は再利用できます。いくつもの値を続けてバインドすれば、呼び出しのたびにプランを再コンパイルせずに済みます。

SQL
-- The bound value stays data, not SQL
SELECT * FROM users
WHERE name = :name;
-- :name = ada' OR '1'='1
-- -> 0 rows: no user
--    is literally named that
同じペイロードだが、今や無害。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. パラメーター付きのプリペアドステートメントは、なぜSQLインジェクションを防ぐのですか?
    • 送信される値を暗号化するから
    • バインドされた値がデータとして扱われ、決してSQLとして解析されないから
    • 文字列の引用符を自動でエスケープするから
    • ユーザー入力の長さを制限するから
  2. ペイロード' OR '1'='1がインジェクションに成功するのは、それが...
    • データベースサーバーのメモリを飽和させるから
    • 文字列リテラルを閉じてから常に真の条件を加えるから
    • クエリが狙うテーブルを削除するから
    • 現在のユーザーの権限を変えるから
  3. ?パラメーターを介してテーブル名をバインドできますか?
    • はい、プレースホルダーはあらゆる種類の識別子を受け付ける
    • いいえ、プレースホルダーは値だけをバインドします。識別子はホワイトリストと照らして検証しなければなりません
    • はい、ただし名前付きの形:nameでのみ
    • はい、テーブルがすでに存在していれば