【问题标题】:How to prevent SQL Injection with JPA and Hibernate?如何使用 JPA 和 Hibernate 防止 SQL 注入?
【发布时间】:2012-12-15 15:45:11
【问题描述】:

我正在使用 hibernate 开发应用程序。当我尝试创建登录页面时,出现了 Sql Injection 的问题。 我有以下代码:

@Component
@Transactional(propagation = Propagation.SUPPORTS)
public class LoginInfoDAOImpl implements LoginInfoDAO{

@Autowired
private SessionFactory sessionFactory;      
@Override
public LoginInfo getLoginInfo(String userName,String password){
    List<LoginInfo> loginList = sessionFactory.getCurrentSession().createQuery("from LoginInfo where userName='"+userName+"' and password='"+password+"'").list();
    if(loginList!=null )
        return loginList.get(0);
    else return null;   
          }
      }

在这种情况下我将如何防止Sql Injection?loginInfo表的创建表语法如下:

create table login_info
  (user_name varchar(16) not null primary key,
  pass_word varchar(16) not null); 

【问题讨论】:

标签: java sql hibernate jpa sql-injection


【解决方案1】:
Query q = sessionFactory.getCurrentSession().createQuery("from LoginInfo where userName = :name");
q.setParameter("name", userName);
List<LoginInfo> loginList = q.list();

您还有其他选择,请参阅 mkyong 的这个漂亮的 article

【讨论】:

  • 谢谢,我们如何在查询中包含密码??@Petr Mensik
  • 与我包含用户名的方式相同
【解决方案2】:

你需要使用命名参数来避免sql注入。此外(与 sql 注入无关,但通常与安全性有关)不返回第一个结果,而是使用 getSingleResult 所以如果由于某种原因有多个结果,查询将失败并返回 NonUniqueResultException 和登录将不成功

 Query query= sessionFactory.getCurrentSession().createQuery("from LoginInfo where userName=:userName  and password= :password");
 query.setParameter("username", userName);
 query.setParameter("password", password);
 LoginInfo loginList = (LoginInfo)query.getSingleResult();

【讨论】:

    【解决方案3】:

    什么是 SQL 注入?

    当恶意攻击者可以操纵查询时,就会发生 SQL 注入 构建进程,以便他可以执行不同的 SQL 语句 应用程序开发人员最初的意图

    如何防止SQL注入攻击

    解决方案非常简单直接。您只需确保始终使用绑定参数:

    public PostComment getPostCommentByReview(String review) {
        return doInJPA(entityManager -> {
            return entityManager.createQuery("""
                select p
                from PostComment p
                where p.review = :review
                """, PostComment.class)
            .setParameter("review", review)
            .getSingleResult();
        });
    }
    

    现在,如果有人试图破解这个查询:

    getPostCommentByReview("1 AND 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) )");
    

    SQL 注入攻击将被阻止:

    Time:1, Query:["select postcommen0_.id as id1_1_, postcommen0_.post_id as post_id3_1_, postcommen0_.review as review2_1_ from post_comment postcommen0_ where postcommen0_.review=?"], Params:[(1 AND 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) ))]
    

    JPQL 注入

    使用 JPQL 或 HQL 查询时也可能发生 SQL 注入,如下例所示:

    public List<Post> getPostsByTitle(String title) {
        return doInJPA(entityManager -> {
            return entityManager.createQuery(
                "select p " +
                "from Post p " +
                "where" +
                "   p.title = '" + title + "'", Post.class)
            .getResultList();
        });
    }
    

    上面的 JPQL 查询没有使用绑定参数,所以容易受到SQL injection 的攻击。

    看看当我像这样执行这个 JPQL 查询时会发生什么:

    List<Post> posts = getPostsByTitle(
        "High-Performance Java Persistence' and " +
        "FUNCTION('1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) ) --',) is '"
    );
    

    Hibernate 执行以下 SQL 查询:

    Time:10003, QuerySize:1, BatchSize:0, Query:["select p.id as id1_0_, p.title as title2_0_ from post p where p.title='High-Performance Java Persistence' and 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) ) --()=''"], Params:[()]
    

    动态查询

    您应该避免使用字符串连接来动态构建查询的查询:

    String hql = " select e.id as id,function('getActiveUser') as name from " + domainClass.getName() + " e ";
    Query query=session.createQuery(hql);
    return query.list();
    

    如果你想使用动态查询,你需要使用 Criteria API 来代替:

    Class<Post> entityClass = Post.class;
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Tuple> query = cb.createTupleQuery();
    Root<?> root = query.from(entityClass);
    query.select(
        cb.tuple(
            root.get("id"),
            cb.function("now", Date.class)
        )
    );
    
    
    return entityManager.createQuery(query).getResultList();
    

    【讨论】:

      【解决方案4】:

      我想在这里添加一个特殊的 SQL 注入,可以在搜索中使用 Like 查询。

      假设我们有一个查询字符串如下:

      queryString = queryString + " and c.name like :name";
      

      在设置名称参数时,大多数人通常会使用这个。

      query.setParameter("name", "%" + name + "%");
      

      现在,如上所述,由于 TypedQuery 和 Hibernate 将默认处理它,因此无法注入像“1=1”这样的传统参数。

      但是这里可能存在特殊的 SQL 注入,这是因为 LIKE 查询结构使用了 下划线

      下划线通配符用于精确匹配一个字符 MySQL的意思,比如select * from users where user like 'abc_de';这将产生以 abc 开头的用户的输出,结束 与 de 之间正好有 1 个字符。

      现在,如果在我们的场景中,如果我们设置

      • name="_" 产生姓名至少包含 1 个字母的客户
      • name="__" 产生姓名至少为 2 个字母的客户
      • name="___" 产生姓名至少为 3 个字母的客户

      等等。

      理想的解决方法:

      为了缓解这种情况,我们需要使用前缀转义所有下划线。

      ___ 将变为 \_\_\_(相当于 3 个原始下划线)

      同样,反之亦然的查询也会导致需要转义 % 的注入。

      【讨论】:

        【解决方案5】:

        我们应该总是尝试使用存储过程来防止 SQLInjection。如果存储过程是不可能的;我们应该尝试使用Prepared Statements。

        【讨论】:

        • 我同意,它在技术上更好,因为速度。但是,我所知道的任何语言的 ORM 框架都不会生成存储过程。如果他们这样做,那就太好了。但由于它们不这样做,而且 ORM 框架提供的生产力至关重要,因此准备好的语句将占主导地位。
        • 对所有事物使用存储过程在 ORM 中并不是那么明智。准备好的陈述应该是好的!
        猜你喜欢
        • 2015-09-27
        • 2011-06-04
        • 1970-01-01
        • 2017-03-14
        • 2013-05-20
        • 2017-06-09
        • 1970-01-01
        • 2015-09-07
        相关资源
        最近更新 更多