
SQLインジェクションを防ぐPython実装|なぜ文字列連結が危ないのか
2026-09-14 ・ 実践
Webアプリの脆弱性の代表格 SQLインジェクション。攻撃手法を覚えるより、**「なぜ起きるか」と「どう根本的に防ぐか」**を理解するのがエンジニアの仕事です。この記事ではPythonのsqlite3で、危険なコードと安全なコードを並べて防御を体得します。Webの脆弱性の実装編です。
なぜ起きるのか:入力がSQLの一部になる
原因はただ一つ、ユーザー入力を文字列連結でSQLに混ぜること。入力が「データ」ではなく「命令」として解釈されてしまうのが問題の本質です。
準備
標準ライブラリのsqlite3だけ。
① 危ない書き方(文字列連結)
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE users(name TEXT, secret TEXT)")
db.execute("INSERT INTO users VALUES ('alice', 'topsecret')")
def login_unsafe(name):
# ❌ 絶対にやってはいけない:入力を直接埋め込む
query = f"SELECT secret FROM users WHERE name = '{name}'"
return db.execute(query).fetchall()
# 普通の入力なら動くが…
print(login_unsafe("alice"))
# 入力が「命令」になってしまう例(' OR '1'='1)
print(login_unsafe("x' OR '1'='1")) # 全員のsecretが漏れる!
x' OR '1'='1 という入力で、WHERE条件が常に真になり全レコードが漏洩します。入力がSQLの構造そのものを書き換えたのが原因です。
② 安全な書き方(パラメータ化クエリ)
入力を?のプレースホルダで渡すと、DBが入力を必ず「データ」として扱い、命令として解釈しません。
def login_safe(name):
# ✅ 入力はパラメータとして渡す(連結しない)
return db.execute("SELECT secret FROM users WHERE name = ?", (name,)).fetchall()
print(login_safe("alice")) # 正常
print(login_safe("x' OR '1'='1")) # → [] 何も漏れない
同じ悪意ある入力でも、パラメータ化すれば [](該当なし)。入力は単なる文字列として扱われ、攻撃が成立しません。
覚えることは1つだけ
「ユーザー入力をSQL文字列に連結しない。必ずプレースホルダで渡す」——これだけでSQLインジェクションはほぼ根絶できます。ORMを使う場合も、生SQLを書くときは同じ原則です。
多層防御も忘れずに
パラメータ化が最重要ですが、加えて「入力値の検証」「DBユーザーの権限最小化」「エラーメッセージに内部情報を出さない」を重ねると、より堅牢になります。1つの対策に頼らないのがセキュリティの基本です。
まとめ
- SQLインジェクションは「入力を文字列連結でSQLに混ぜる」ことが原因
- 入力がデータでなく命令として解釈され、情報漏洩や改ざんに繋がる
- 対策はパラメータ化クエリ(
?で渡す)。入力は必ずデータ扱いになる - 入力検証・権限最小化・エラー秘匿の多層防御を重ねる
もう少し詳しく(背景と理論)
SQLインジェクションは、ユーザー入力を文字列連結でSQL文に埋め込むことで、攻撃者が任意のSQLを注入できてしまう脆弱性です(CWE-89)。OWASP Top 10 で長年上位を占める代表的なインジェクション欠陥です1。根本対策はプレースホルダを使ったパラメータ化クエリ(プリペアドステートメント)——データとコードを分離し、入力が「値」としてしか解釈されないようにします2。これはエスケープ処理より確実で、多くのORMもこの仕組みの上に立っています。加えて、DBアカウントの権限は必要最小限に絞る最小権限の原則を併用し、万一の被害範囲を限定します3。
次の一歩 🌸
他のWeb脆弱性はWebの脆弱性、出力側の対策はXSSとエスケープ、通信の保護はHTTPSのしくみへどうぞ。