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 インジェクションのリスクを大幅に減らせる。