一覧SD-02-1 · SQLインジェクションと安全な問合せ

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 / 固定の利用者・商品表

1
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
2
print(db.execute(unsafe("alice")).fetchall()) print(safe(db,"alice"))
[(1, 'alice')]
[(1, 'alice')]
3
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
4
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")]
5
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")]
6
print(safe(db,candidate)) print(db.execute("SELECT COUNT(*) FROM users").fetchone())
[]
(3,)
7
for value in ("alice","O'Reilly","",candidate): print(repr(value),safe(db,value))
'alice' [(1, 'alice')]
"O'Reilly" [(3, "O'Reilly")]
'' []
"' OR 1=1 -- " []

演習完了