如何使用JPA和Hibernate防止SQL注入?

33

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

@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 注入攻击?loginInfo 表的创建语法如下:

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

请参考此链接[1]: https://dev59.com/5G445IYBdhLWcg3w9O7R - Dinesh Vadivelu
此外,如果您在2020年访问此链接,请使用Spring Security进行此类操作。 - anuj pradhan
5个回答

28
Query q = sessionFactory.getCurrentSession().createQuery("from LoginInfo where userName = :name");
q.setParameter("name", userName);
List<LoginInfo> loginList = q.list();

你还有其他选项,可以查看mkyong的这篇不错的文章


谢谢,我们如何在查询中包含密码?@Petr Mensik - Mr. Singthoi
1
就像我包含了userName一样 - Petr Mensik

21

您需要使用命名参数来避免 SQL 注入。此外(与 SQL 注入无关但与安全性有关),不要返回第一个结果,而是使用 getSingleResult,这样如果出现多个结果的情况,查询将失败并且登录将不会成功。

 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();

16

什么是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注入攻击。

看看当我像这样执行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();

2
我想在这里补充一点,使用Like查询进行搜索时可能会出现一种奇特的SQL注入。
假设我们有一个如下所示的查询字符串:
queryString = queryString + " and c.name like :name";

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

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

现在,如上所述,传统参数如“1=1”由于TypedQuery和Hibernate默认处理,无法被注入。

但是,这里存在一种奇特的SQL注入可能性,这是由于LIKE查询结构中使用了下划线

在MySQL中,下划线通配符用于匹配一个字符,例如,select * from users where user like 'abc_de'; 这将产生以abc开头,以de结尾,并且中间恰好有1个字符的用户。

现在,在我们的情况下,如果我们设置

  • name="_" 生成至少有1个字母的客户姓名
  • name="__" 生成至少有2个字母的客户姓名
  • name="___" 生成至少有3个字母的客户姓名

等等。

理想修复:

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

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

同样地,反之查询也会导致注入,需要转义%号。

0

通常我们应该尽量使用存储过程来防止SQL注入。如果不能使用存储过程,应该尝试使用预处理语句。


我同意,从技术上讲,因为速度更快,ORM框架提供的生产力是必不可少的,因此预处理语句将主导开发。然而,我所知道的任何语言中都没有ORM框架生成存储过程,如果他们能生成存储过程,那就太好了。 - Dennis
在ORM中,仅使用存储过程并不明智。准备好的语句应该更好! - Diablo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接