SQLインジェクション入門:仕組みと対策
プレースホルダで値を別に渡す
この回でやること
SQLの骨組みを先に固め、値はあとから穴へ別に渡す。? が何をしているのか、エスケープを頑張るのと何が根本から違うのかを、コードの形で確かめます。
- 読む 約 9 分
- 最後にクイズ 1 問
このレッスンで学ぶこと
プレースホルダは、SQLの骨組みを先に固め、入力を値としてあとから別に渡すしくみです。この回は ? が実際に何をしているのか、なぜ入力に何を入れても構文にならないのかを、同じ users テーブルのコードで押さえます。
読み終えると、エスケープを頑張るのとプレースホルダが、なぜ根本から違うのかを説明できるようになります。
骨組みを先に固め、値をあとから別に渡す
壊れている作りは、入力を文字列として連結し、値と命令が混ざった1本のSQLをデータベースへ渡していました。プレースホルダは順番を入れ替えます。まずSQLの骨組みだけを渡し、値の入る場所を ? で空けておきます。
SQL クエリ
SELECT name, role FROM users WHERE name = ? AND password = ?データベースはこの骨組みを先に受け取り、name と password を1つずつ比べる文だと構造を確定させます。値はそのあとで、? の穴に対応する別のリストとして手渡されます。骨組みが決まったあとに届くので、値がSQLの構造を作り替える余地はどこにもありません。
? は空き枠であって、文字列の一部ではない
admin' -- を name の値として渡しても、データベースは admin' -- という名前を探すだけです。シングルクォートは値の中の文字にとどまり、文字列を閉じません。-- もコメントにならず、ただの記号として名前に含まれます。骨組みはもう確定しているので、あとから届いた文字が WHERE の形をいじることはできません。そんな名前の利用者はいないため、admin' -- は401で弾かれます。連結で消えていた値と命令の境界が、渡し方の側で引き直されています。
エスケープを頑張るのと、どこが違うのか
一見似た直し方に、入力のシングルクォートをエスケープする(' を '' に置き換える)ものがあります。正しく書けば o'brien のような正規の名前も壊さず、文字列の中に混ぜた攻撃は止められます。ですが連結の作りを残す以上、引用符で囲まない数値の場所や、文字コードの扱いから抜ける道が残り、一箇所の書き漏らしで穴が開きます。あとの回で出てくる「シングルクォートを取り除く」直し方はこれとは別物で、o'brien を obrien に変えて本人を締め出します。
プレースホルダは無害化ではありません。値をそもそも構文の組み立てに立ち入らせないので、入力を疑って加工する必要が消えます。弾く文字を数える発想から、境界を構造で引く発想への切り替えです。
エスケープは危ない値を安全な形に書き換えようとする後始末で、書き換え漏れが残ります。プレースホルダは値を命令の組み立てに一度も混ぜないので、書き換えるものがありません。塞ぐ強さの差はここから来ます。
ORMやクエリビルダとの関係
多くのORMやクエリビルダは、内部でこのプレースホルダを使っています。メソッドに値を渡せば、自動で ? の位置へ回してくれます。
JavaScript
db.query(
"SELECT name, role FROM users WHERE name = ? AND password = ?",
[name, password],
)だからORMを使えば自動で安全、とは言い切れません。同じORMでも、文字列を自分で連結してから渡す口が残っていることが多いからです。
JavaScript
db.query("SELECT name, role FROM users WHERE name = '" + name + "'")これは連結した1本を渡しているだけで、穴は元のままです。安全かどうかは道具の名前ではなく、値を別に渡しているかで決まります。
プレースホルダは同じ骨組みを値だけ変えて使い回せるので、速さのために連結へ戻す理由もありません。自分の手で確かめるなら、練習用の環境で admin' -- を連結版とプレースホルダ版の両方へ送り、返る行の違いを見比べます。試すのは自分が権限を持つ練習用の環境だけにします。