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だけに対応するテストを追加します。