SQLインジェクションを再現し、プリペアドステートメントで無力化し、最小権限を適用しましょう。
このレッスンを Kodokon で開くSQLインジェクションは、信頼できないデータがクエリの文字列に連結された瞬間に起こります。攻撃者は、文字列を閉じてロジックを乗っ取る値を与えます。usersテーブルに対する攻撃を再現してみましょう。入力ada' OR '1'='1を直接連結すると、正確なフィルターが常に真の条件に変わってしまいます。
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'が普遍的に真の条件を加えるからです。ある変種は、元のクエリの残りをコメントアウトするために--を末尾に付け足します。教訓は、データとコードが同じ文字列を共有している限り、手作業のエスケープはどれも本当には信頼できないということです。
-- Positional placeholder
SELECT * FROM users WHERE name = ?;
-- Named placeholder
SELECT * FROM users WHERE name = :name;対策はプリペアドステートメントです。ドライバーはまずクエリの構造をコンパイルし、それから値を別々にバインドします。バインドされたデータは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' OR '1'='1がインジェクションに成功するのは、それが...?パラメーターを介してテーブル名をバインドできますか?:nameでのみ