FlawOpen

flawopen.com/JavaのSQL Injection/MyBatis

MyBatisの${}と#{}:SQL Injectionを防ぐ

リファレンス
要点

#{value}はPreparedStatementの保護された値欄です。DBは入力をデータとして扱います。${value}は準備前にSQLへテキストをコピーします。

バインド値と生のSQLテキスト

危険 — ${}生テキスト
<select id="getUser">
  SELECT * FROM users
  WHERE id = ${id}
</select>
// Raw substitution: the SQL text changes
バインド値 — #{}
<select id="getUser">
  SELECT * FROM users
  WHERE id = #{id}
</select>
// MyBatis creates a PreparedStatement parameter

MyBatisに両方の記法がある理由

#{}はJDBC PreparedStatementで値を束縛します。パラメーターはテーブル名、列名、ソート方向などのSQL構文を置き換えられません。${}はテキストを直接置換し、エスケープしません。静的SQLまたは<choose>を優先し、必要ならサーバー側許可リストから識別子全体を選びます。任意の入力を補間しないでください。

動的識別子が必要な場合

SQL識別子または断片全体を許可リストで制限

バインド値で識別子は置き換えられません。並べ替え選択をサーバー側の固定SQLに対応付けるか、<choose>を使います。クライアント由来の列名、テーブル名、ASC/DESCを${}へ渡さないでください。エスケープだけでは任意のSQL断片を安全にできません。

MyBatis mapperの監査方法

rg -n -F '${' --glob '*.xml' --glob '*.java' .
XMLとアノテーションの該当箇所を確認し、値の出所を追います。値には#{}、${}にはサーバー側の有限な許可リストを使います。検索だけでは間接的なSQL生成を見逃します。

よくある質問

アノテーション形式のmapperも対象ですか?

はい。同じ規則です。@Selectの${column}は生テキストを挿入します。値は#{value}で束縛し、識別子は許可リストで制限します。

${}の検索だけで安全性を証明できますか?

いいえ。検索結果を確認してください。SQLが別の場所で組み立てられることがあります。XMLとアノテーションを検査し、ソート選択が固定SQLだけに対応するテストを追加します。

参考資料

関連項目 SQL インジェクション(CWE-89)の仕組みと各言語での防ぎ方 — flawopen.com MySQLiとPDO:安全なPrepared Statement NHibernate は SQL Injection を防げますか? MongoDB NoSQL Injection:安全なクエリフィルター 数値 ID でも文字列連結で SQL Injection が起きますか? パラメータ化クエリとは何か Prisma $queryRaw は SQL Injection に安全?