FlawOpen

flawopen.com/RubyのSQL Injection/ActiveRecord where()

Railsのwhere()文字列補間は安全?

リファレンス
要点

SQLを、指示が固定され値を入れる空欄がある用紙だと考えてください。バインドパラメーターは空欄を埋めますが、Rubyの補間はRailsが両者を分ける前にリクエスト文字列を指示部分へ書き込みます。SQLの構造を固定し、信頼できない値はパラメーターとして渡します。

UNSAFE
User.where(
  "id = #{params[:id]}"
)
SAFE
User.where(id: params[:id])
# or, for more complex conditions:
User.where("id = ?", params[:id])

補間でクエリが変わる理由

Active Recordが条件を受け取る前に、Rubyが#{...}を評価します。たとえばwhere("id = #{params[:id]}")は、結合済みのSQL文字列をRailsに渡すため、リクエスト入力で述語が変わる可能性があります。Active Recordのガイドはこの文字列構築を安全ではないと説明しています。対応する等値・範囲・部分集合条件にはハッシュを、その他の述語には?プレースホルダーを使います。プレースホルダーで束縛できるのは値であり、識別子やSQLキーワードではありません。クエリ構造を固定するか、厳格な許可リストから識別子を選びます。

コードベースの確認方法

rg -n 'where\s*\(' app/
条件文字列内にリクエストデータの補間や連結がないか、検索結果を確認します。Brakemanは解析で危険なデータフローを検出するとSQL Injectionのリスクを報告します。警告がなくても全クエリの安全性が証明されるわけではありません。

よくある質問

すべてのクエリでハッシュ条件を使えますか?

いいえ。Railsのハッシュ条件が扱うのは等値、範囲、部分集合(IN)です。それ以外の述語には、where("price > ?", amount)のようにSQL構造を固定して値をバインドします。バインドが保護するのは値であり、動的なテーブル名・列名やSQL断片は安全になりません。

参考資料

関連項目 SQL インジェクション(CWE-89)の仕組みと各言語での防ぎ方 — flawopen.com Sequelize query() は SQL Injection に安全? SQLAlchemy text() は SQL Injection に安全? sqlx のコンパイル時クエリ検査は SQL インジェクションを防ぎますか? ストアドプロシージャは SQL インジェクションを防ぎますか? TypeORM QueryBuilder は SQL Injection に安全? Django ORMとSQL Injection:安全な書き方