SQL インジェクションは、ユーザー入力を SQL クエリに直接埋め込むことで発生する脆弱性である。Flask と SQLAlchemy を使った安全なデータベース操作を解説する。
SQL インジェクションの危険性
# 危険: ユーザー入力を直接埋め込む
@app.route('/search')
def search():
q = request.args.get('q')
# 攻撃者が q に "'; DROP TABLE users; --" を入力すると...
result = db.engine.execute(f"SELECT * FROM posts WHERE title = '{q}'")
return render_template('results.html', results=result)
攻撃者は入力を操作することで、任意の SQL を実行できてしまう。
SQLAlchemy ORM を使う
SQLAlchemy ORM はパラメータを自動的にエスケープする。
@app.route('/search')
def search():
q = request.args.get('q')
# 安全: ORM がパラメータをエスケープ
posts = Post.query.filter(Post.title == q).all()
return render_template('results.html', posts=posts)
パラメータ化クエリ
生の SQL を使う必要がある場合は、パラメータ化クエリを使う。
from sqlalchemy import text
@app.route('/search')
def search():
q = request.args.get('q')
# 安全: パラメータをバインド
result = db.session.execute(
text("SELECT * FROM posts WHERE title = :title"),
{'title': q}
)
return render_template('results.html', results=result)
:title はプレースホルダで、{'title': q} で値をバインドする。
LIKE 検索の注意点
# 危険: % をそのまま埋め込む
posts = Post.query.filter(Post.title.like(f'%{q}%')).all()
# 安全: ワイルドカードをエスケープ
from sqlalchemy import func
q_escaped = q.replace('%', r'\%').replace('_', r'\_')
posts = Post.query.filter(Post.title.like(f'%{q_escaped}%', escape='\\')).all()
動的なカラム名の危険
# 危険: カラム名をユーザー入力から取る
order_by = request.args.get('sort', 'id')
posts = Post.query.order_by(order_by).all() # SQL インジェクションの可能性
# 安全: ホワイトリストでチェック
ALLOWED_SORT = {'id', 'title', 'created_at'}
order_by = request.args.get('sort', 'id')
if order_by not in ALLOWED_SORT:
order_by = 'id'
posts = Post.query.order_by(getattr(Post, order_by)).all()
IN 句の安全な使用
# 安全: リストをそのまま渡す
ids = [1, 2, 3]
posts = Post.query.filter(Post.id.in_(ids)).all()
SQLAlchemy が適切にエスケープしてくれる。
生の SQL を避けられない場合
from sqlalchemy import text
# 複雑なクエリでも必ずパラメータ化
query = text("""
SELECT p.*, u.username
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.status = :status AND u.role = :role
""")
result = db.session.execute(query, {'status': 'published', 'role': 'author'})
防御のまとめ
ORM を使う(最も安全)
生 SQL は必ずパラメータ化クエリ
カラム名・テーブル名はホワイトリストで検証
エラーメッセージで SQL 構造を露出しない
SQLAlchemy を正しく使えば、SQL インジェクションのリスクを大幅に減らせる。