記事の内容はあくまで個人の考えに基づくものであり、組織を代表するものではありません。
sqlite3を使って開発をする中で、よろしくない方法でPython変数をバインドしていた。
社内のコードレビューでご指摘いただき、事前に気づくことができた。
備忘も兼ねてまとめておく。
アンチパターン
もともと下記のような方法でPython変数をバインドしていた。
import sqlite3
name = "test user"
age = 20
con = sqlite3.connect('example.db')
cur = con.cursor()
cur.execute("create table user(id integer primary key autoincrement, name text, age integer);")
cur.execute("insert into user(name, age) values('{}', {});".format(name, age))
con.commit()
con.close()
上記方法でも、エラーなく動作はする。
しかし、下記の通り、Pythonの文字列操作でクエリを組み立てる方法は、SQLインジェクション攻撃に対して脆弱であると公式ドキュメントで説明されている。
SQL operations usually need to use values from Python variables. However, beware of using Python's string operations to assemble queries, as they are vulnerable to SQL injection attacks
SQLインジェクション(英: SQL injection)とは、アプリケーションのセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のこと。また、その攻撃を可能とする脆弱性のことである。wikipediaより
正しい方法
公式ドキュメントに記載されている方法は2パターンある。
パターン1: qmark style
- SQLの値を挿入する箇所に
?
を配置する - cursorのexecute()メソッドの第二引数に
?
の数と同じ要素数のタプルを渡す -
?
の数とタプルの要素数が一致しない場合、ProgrammingError
が発生する
cur.execute("insert into user(name, age) values(?, ?);", (name, age))
パターン2: named style
- SQLの値を挿入する箇所に名前付きパラメータ(
:
と任意の名前
を組み合わせたもの)を指定する - cursorのexecute()メソッドの第二引数に名前付きパラメータ全てのキーを持つdictを渡す
- 名前付きパラメータがdictのキーに含まれない場合
ProgrammingError
が発生し、余分なキーについては無視される
cur.execute("insert into user(name, age) values(:name, :age);", {"name": name, "age": age})