flawopen.com/リファレンス/FastAPI SQLインジェクション
FastAPIはデフォルトでSQLインジェクションを防ぎますか?
Python・APIガイド
要点
Pydanticは値が宣言された入力型に合うかを確認しますが、SQLを書き換えたり、Python文字列をデータベースパラメータに変換したりはしません。検証済みの name: str も、呼び出し元が制御するテキストです。検証は小包の宛名ラベルを確かめる作業、バインドは値をSQLの指示書から分離して封をする作業だと考えてください。SQLテンプレートを固定し、値はSQLAlchemy経由で別に渡します。
FastAPIで危険な書き方と安全な書き方
危険:補間でSQL文字列が変わる
# A string type check does not make input safe inside SQL syntax
@app.get("/users")
def get_users(name: str, db: Session = Depends(get_db)):
query = text(f"SELECT * FROM users WHERE name = '{name}'")
return db.execute(query).fetchall()
安全:SQLAlchemyが値を別にバインド
# :name is a bind parameter; the value is passed separately
@app.get("/users")
def get_users(name: str, db: Session = Depends(get_db)):
query = text("SELECT * FROM users WHERE name = :name")
return db.execute(query, {"name": name}).fetchall()
パラメータバインドで防げないもの
バインドパラメータで表せるのはデータ値であり、テーブル名、列名、SQL演算子、並び順ではありません。リクエストから並び替え項目を選ばせる場合は、選択肢を固定したSQLAlchemy列式の許可リストに対応付けます。生のリクエスト文字列をSQLに連結してはいけません。パラメータ化は認可の代わりにもなりません。読み取り可能な行を制限し、データベースアカウントにはエンドポイントに必要な権限だけを与えます。
防止チェックリスト
- ✓リクエストデータをSQLAlchemyの
text()や生のデータベース実行文字列に補間しない。 - ✓名前付きバインドパラメータを使い、値を別に渡す。例:
db.execute(query, {"name": name})。 - ✓動的なテーブル、列、並び替えの選択肢は、既知のSQLAlchemy式を列挙した固定の許可リストに対応付ける。
- ✓行単位の認可を別途確認し、データベースアカウントにはエンドポイントに必要な権限だけを付与する。
- ✓クエリを明確に表現できる場合はSQLAlchemyの
select()を優先する。ORMを使っても危険な生SQLが安全になるわけではない。