スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Spring Data JPA JPQLインジェクション対策

今回は、JPQLインジェクションの検証と対策を行いたいと思う。

きっかけはJPQLでもSQLインジェクションのような事象が起こるのではないかという疑問が浮かんだことによる。

検証は、前回ページング機能を実装した検索一覧の検索条件を使って確認してみる。

検索一覧
ページング_1

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0

■JPQLに関する過去の記事
Spring MVC Spring Data JPAを使って検索する
Spring MVC ページング機能を実装する(DBアクセス編)

1.文字列結合の場合

package jp.co.sample.book.dao;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import jp.co.sample.book.entity.BookEntity;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public class BookDaoImpl implements BookDao<BookEntity> {

private static Logger logger = LoggerFactory.getLogger(BookDaoImpl.class);

@PersistenceContext
private EntityManager manager;

@Override
@Transactional(readOnly=true)
public Page<BookEntity> findPageByNameLike(Pageable pageable,String name) {
logger.info("findPageByNameLike start");
Query query = manager.createQuery("from BookEntity where bookName like '%" + name + "%'");
int total = query.getResultList().size();
List<BookEntity> list = query.setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList();
Page<BookEntity> pageList = new PageImpl(list,pagaeble,total);
return pageList;
}
//他のメソッドは省略
}

31行目「"from BookEntity where bookName like '%" + name + "%'"」がJPQLとなるが、検索条件変数「name」を文字列結合している。このJPQLに対して、検索条件「XXX%' or bookId like '%2」と入力した場合、本来タイトルで抽出すべきにも関わらず、ID=2の「Spring AOP Sample」が抽出されてしまう。文字列結合のため、JPQLが「"from BookEntity where bookName like '%XXX%' or bookId like '%2%'"」となってしまうことによる。
また、「"from BookEntity where bookName like '%"」の後に別のJPQLが埋め込まれた場合を想定して「%';」としてセミコロンで区切った場合は、例外「org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: org.hibernate.QueryException: unexpected char: ';' [from jp.co.sample.book.entity.BookEntity where bookName like '%%';%']」が発生した。

①「XXX%' or bookId like '%2」と入力した場合
JQPLインジェクション_3


これは問題があるので、対策を行う。

対策は、JPQLでもPreparedStatementのように、パラメータに値を設定する「setParameter」メソッドを使用する。

2.Ordinal Parametersを使用する場合

@Override
@Transactional(readOnly=true)
public Page<BookEntity> findPageByNameLike(Pageable pageable,String name) {
logger.info("findPageByNameLike start");
Query query = manager.createQuery("from BookEntity where bookName like ?1");
query.setParameter(1, "%" + name + "%");
//query.setParameter(1, "'%" + name + "%'"); レコードが見つからない
int total = query.getResultList().size();
List<BookEntity> list = query.setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList();
Page&ly;BookEntity> pageList = new PageImpl(list,pageable,total);
return pageList;
}

5行目「"from BookEntity where bookName like ?1"」を見て分かるように、「?」の後に番号を付与する。そして、6行目の「query.setParameter(1, "%" + name + "%"); 」でJPQLの番号と値を対応させている。ちなみに、「?」の後の番号と「setParameter」の第1引数の番号が一致していれば、「?0」でも「?2」でも動作することが確認できた。しかし、JPQL側が「?2」でsetParameter側が「1」の場合、例外「org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: org.hibernate.QueryParameterException: Position beyond number of declared ordinal parameters. Remember that ordinal parameters are 1-based! Position: 1」が発生した。
また、7行目は文字列結合のパターンと同じように「'」で括ると検索がヒットしないため、コメント化して紹介している。

Ordinal Parametersを使用した結果、「XXX%' or bookId like '%2」「%';」ともに例外にならず、「レコードが見つかりませんでした」と検索0件の結果となった。

①「XXX%' or bookId like '%2」と入力した場合
JQPLインジェクション_1

②「%';」と入力した場合
JQPLインジェクション_2


3.Named Parametersを使用する場合

Query query = manager.createQuery("from BookEntity where bookName like :title");
query.setParameter("title", "%" + name + "%");

番号ではなく、「:」(コロン)の後に任意の名前を付与することも可能。
結果は、Ordinal Parameters同様、「レコードが見つかりませんでした」と検索0件の結果になる。

最後に、「Ordinal Parameters」「Named Parameters」を使うと、JPQLインジェクションを回避できるほか、プリコンパイルされたクエリーを再利用することができるため、性能面でもメリットが生じることも補足しておく。

■過去のSpring関連記事
Spring関連記事 Index

スポンサーサイト

コメントの投稿

非公開コメント

プロフィール

bookmount8

Author:bookmount8
システムエンジニア。サーバーサイドでjavaを扱うことが多い。最近は、ミドルやフロント周りも関心あり。

最新記事
カテゴリ
検索フォーム
最新コメント
月別アーカイブ
これまでの訪問者数
ブロとも申請フォーム

この人とブロともになる

RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。