SQLインジェクション
なぜ ? を使うのか
前のレッスンで、値を ? に渡しました。文字列をつないでも同じ SQL になりそうです。
JavaScript
db.prepare("SELECT * FROM items WHERE name = '" + keyword + "'").all();普通の検索なら動きます。ですがこの書き方は、fleama の商品を全部盗み出せる穴になります。
検索欄に SQL を書かれる
受講者が次の文字列を検索欄に入れたとします。
プレーンテキスト
' OR '1'='1つなぐと、こういう SQL ができあがります。
SQL クエリ
SELECT * FROM items WHERE name = '' OR '1'='1''1'='1' はいつでも成り立ちます。つまり条件が消えて全件が返ります。非公開の商品も、下書きも、すべてです。
閉じるはずの ' が、入力に含まれていたために早く閉じてしまった。ここが原因です。入力がデータではなく、SQL の一部として読まれたわけです。
もっと危ないこともできる
読まれるだけでは済みません。
プレーンテキスト
'; DELETE FROM items; --これで商品を全部消せます。-- から後ろは注釈として無視されるので、後ろに何が続いていても関係ありません。
この攻撃を SQL インジェクションと呼びます。日本語では「SQL の注入」です。
プレースホルダが防ぐ理由
JavaScript
db.prepare("SELECT * FROM items WHERE name = ?").all(keyword);こう書くと、' OR '1'='1 はそういう名前の商品を探すだけになります。0 件です。
違いは、SQL の形を先に決めてしまう点にあります。? の場所に入るものは、どんな文字が来ても値としてしか扱われません。SQL の構造をあとから書き換えることはできません。
文字列を組み立ててから解釈させるのか、形を決めてから値を差し込むのか。 この順番が安全を分けます。
逃がす処理を自分で書かない
「危ない文字を消せばよい」と考えたくなりますが、やめてください。抜け道が多く、専門家でも塞ぎきれません。
外から来た値を SQL につながない。 これだけを守れば、この攻撃は成立しません。
手を動かす
演習では、まず攻撃が成立することを自分の目で確かめ、そのうえで直します。危険を知らないまま防ぐことはできません。
要件
unsafeはWHERE name = 'に値をつないで組み立てた SQL で数えるsafeはWHERE name = ?のプレースホルダで数える- どちらも件数を数値で返す
入出力例
search("レザースニーカー") → {"safe":1,"unsafe":1}
search("' OR '1'='1") → {"safe":0,"unsafe":3}
search("帽子") → {"safe":0,"unsafe":0}