【Mybatis】SQLインジェクション対策

mybatis

 Mybatisを利用して、SQLを組み立てる際は、SQL Injectionが発生しないように注意する必要があります。特にSQLに値を埋め込みたいとき、「${…}」を使うべきか、「#{…}」を使うべきかを意識しない方はいると思いますが、実は「${…}」を使う場合はかなり危険です。

 基本的に、必ずアプリケーションとして安全な値であることを担保した上で、テーブル名、カラム名、ソート条件などに限定して「${…}」を使用しますが、その以外の場合は基本的に「#{…}」を使うべきです。

バインド変数と置換変数


 MyBatis3では、SQLに値を埋め込む仕組みとして、以下の2つの方法を提供している。

項番方法説明
1バインド変数(#{…})を使用して埋め込むこの方法を使用すると、 SQL組み立て後にjava.sql.PreparedStatementを使用して値が埋め込められるため、安全に値を埋め込むことができる。
ユーザからの入力値をSQLに埋め込む場合は、原則バインド変数を使用すること。
2置換変数(${…})を使用して埋め込むこの方法を使用すると、SQLを組み立てるタイミングで文字列として置換されてしまうため、安全な値の埋め込みは保証されない。

 MyBatisのデフォルトではJavaのPreparedStatementを利用してSQLを実行します。実はバインド変数#{…}PreparedStatementのパラメータとして設定されます。つまり以下のようなパラメータが利用できる箇所でのみ、利用することができます。
 ・SELECT文のWHERE句における値
 ・INSERT文のVALUES句における値
 ・UPDATE文のSET句における値、WHERE句における値
 ・DELETE文のWHERE句における値

 バインド変数を使用する例は以下です。

<insert id="create" parameterType="Example">
    INSERT INTO
        t_example
    (
        item_id,
        item_title,
        created_time
    )
    VALUES
    (
        #{itemId},
        #{itemTitle},
        #{createdTime}
    )
</insert>

 置換変数を使用する例は以下です。

public interface TodoRepository {
    List<Todo> findAll(@Param("direction") String direction);
}
<select id="findAll" parameterType="String" resultType="Todo">
    SELECT
        *
    FROM
        t_todo
    ORDER BY
        todo_id ${direction}
</select>
なぜ置換変数は危険なの?

 以下の例を考えてみましょう。
 元々itemnameを指定して、特定のレコードを絞り込んで取得したいが、itemnameの値を「’name’ OR 1=1;」と指定したら、itemsのすべてのレコードを取得してしまうことになります。
 更に、itemnameの値を「’name’; DELETE * FROM items;」と指定したら、itemsテーブルの値をすべて削除されてしまいます。

SELECT *
FROM items
WHERE itemname = ${itemName}
対策

 置換変数を使用する場合、必ず代入の値をチェックもしくは制限するようにします。
 例えば、コード値とSQLに埋め込むための値のペアをMapに格納しておき

Map<String, String> directionMap = new HashMap<String, String>();
directionMap.put("1", "ASC");
directionMap.put("2", "DESC");

 入力値はコード値として扱い、SQLを実行する処理の中で安全な値に変換することが望ましい。

String direction = directionMap.get(directionCode);
todoRepository.findAllByCriteria(criteria, direction);

コメント

タイトルとURLをコピーしました