SQLインジェクションとプレースホルダー
入力文字の禁止に頼らず、SQLの構造と値を分ける理由を説明します。
説明
SQLの文字列へ入力を直接つなぐと、入力中の引用符や演算子がSQLの構造として解釈されることがあります。パラメーター束縛では、SQLの構造を先に決め、入力を値として渡します。
利用者名の検索を、文字列連結とプレースホルダーで実行します。通常の名前、引用符を含む名前、条件式のような入力を比較し、DBが実際に返す行を調べます。
入力文字の禁止に頼らず、SQLの構造と値を分ける理由を説明します。
1検索対象と連結するSQLを読む
架空の利用者3人を使います。O'Reillyには正当な名前として引用符が含まれています。
from sd02 import *
db = database()独立した利用者表を作る
print(db.execute("SELECT id,name FROM users ORDER BY id").fetchall())
print(unsafe("alice")連結で作ったSQLを表示する)連結版は入力を引用符で挟んでSQLへ埋め込みます。普通のaliceだけでは問題が見えません。
2通常の名前で実行する
連結版と束縛版を同じ入力で比較します。
print(db.execute(unsafe("alice"))連結版を実行する.fetchall())
print(safe(db,"alice")値を束縛する検索を実行する)どちらもaliceの1行です。正常値のテストだけで安全性を判断できない例です。
3引用符を含む名前を連結する
引用符がSQL文字列の終わりと解釈される形を確認します。
name = "O'Reilly"
print(unsafe(name)名前を埋め込んだSQLを表示する)
print(run(db,unsafe(name))DBの構文解析結果を確認する)連結したSQLは構文エラーになります。引用符を一律禁止すると、この正当な名前も扱えなくなります。
4同じ名前を値として束縛する
SQL本文はWHERE name=?のままで、値を別の引数に渡します。
sql = "SELECT id,name FROM users WHERE name=?値を入れる位置を固定する ORDER BY id"
print(sql,(name,))
print(db.execute(sql,(name,))名前を別引数として束縛する.fetchall())O'Reillyの行を取得できます。入力中の引用符は文字列値の一部として扱われます。
5入力が条件式へ変わる場合
固定の文字列を連結版へ渡します。引用符を閉じた後のORがSQLの条件として解釈されます。
candidate = "' OR 1=1 -- "
print(unsafe(candidate)入力で変わったSQL構造を表示する)
print(db.execute(unsafe(candidate))DBが返す行を確認する.fetchall())全3行が返ります。入力値でSQLの構造が変わり、名前の一致という条件を超えています。
6束縛した同じ入力を比較する
同じ文字列をパラメーターとして渡します。
print(safe(db,candidate)条件式のような文字列を値として検索する)
print(db.execute("SELECT COUNT(*) FROM users").fetchone())その文字列を名前に持つ利用者はいないので0行です。表の3行も残っています。
7空文字と正当な引用符を回帰確認する
通常値だけでなく、空文字、引用符を含む名前、条件式に似た値を同じ束縛APIで試します。
for value in ("alice","O'Reilly","",candidate): print(repr(value),safe(db,value)同じ束縛APIで複数の入力を比較する)それぞれ入力した名前だけを検索する動作になっています。拒否すべき型や長さの検証は、SQLの束縛とは別に設計します。
まとめ
文字列連結は入力をSQL構造へ混ぜます。
パラメーター束縛は構造と値を分けます。
正当な引用符を扱えることと、条件式のような値で範囲が広がらないことを確認します。
操作履歴と解説を振り返れます。
Python 3.15 / SQLite / 固定の利用者・商品表
from sd02 import *
db = database()
print(db.execute("SELECT id,name FROM users ORDER BY id").fetchall())
print(unsafe("alice"))[(1, 'alice'), (2, 'bob'), (3, "O'Reilly")] SELECT id,name FROM users WHERE name='alice' ORDER BY id
print(db.execute(unsafe("alice")).fetchall())
print(safe(db,"alice"))[(1, 'alice')] [(1, 'alice')]
name = "O'Reilly"
print(unsafe(name))
print(run(db,unsafe(name)))SELECT id,name FROM users WHERE name='O'Reilly' ORDER BY id near "Reilly": syntax error
sql = "SELECT id,name FROM users WHERE name=? ORDER BY id"
print(sql,(name,))
print(db.execute(sql,(name,)).fetchall())SELECT id,name FROM users WHERE name=? ORDER BY id ("O'Reilly",)
[(3, "O'Reilly")]candidate = "' OR 1=1 -- "
print(unsafe(candidate))
print(db.execute(unsafe(candidate)).fetchall())SELECT id,name FROM users WHERE name='' OR 1=1 -- ' ORDER BY id [(1, 'alice'), (2, 'bob'), (3, "O'Reilly")]
print(safe(db,candidate))
print(db.execute("SELECT COUNT(*) FROM users").fetchone())[] (3,)
for value in ("alice","O'Reilly","",candidate): print(repr(value),safe(db,value))'alice' [(1, 'alice')] "O'Reilly" [(3, "O'Reilly")] '' [] "' OR 1=1 -- " []
演習完了