最新消息:阿里云双12优惠,史上最低折扣。

如何避免出现SQL注入漏洞

云服务器 aliyun 224浏览

一.前言

本文将解释在开发过程中仍然经常出现的 SQL 编码缺陷背后的原理和原因。并以几个常见漏洞的形式,提醒技术同学注意相关问题。最后,根据原则,提供解决方案或缓解方案。


二.SQL注入漏洞的原理及原因

SQL注入漏洞基本上是由于外部输入错误执行为SQL代码。目前最好的解决方案是预编译方法。

在一条SQL语句的执行过程中,需要经过以下三个基本步骤:

代码语义分析

制定执行计划

获取返回结果

而一条SQL语句由代码和数据两部分组成,如:

SELECT id, name, phone FROM userTable WHERE name =xiaoming;

SELECT id, name, phone FROM userTable WHERE name = 是代码,xiaoming是数据。

预编译,以Mybatis为例,就是预先用占位符分析语义:

比如SELECT id, name, phone FROM userTable WHERE id = #{name};

然后然后将数据xiaoming传入占位符。这样,错开代码语义分析阶段就不会被误认为是部分代码。

早期,开发人员明确使用 JDBC 来创建连接和执行 SQL 语句。在这种情况下,如果外部可控数据在没有充分过滤的情况下拼接到SQL语句中,就会出现漏洞。这种情况在正常的业务开发过程中是很少见到的。按照公司规定,在没有特殊情况下,必须使用ORM框架来执行SQL。

但是目前在一些项目中,仍然使用JDBC来编写一些工具脚本,比如DataMerge.java和DatabaseClean.java,它们借用了JDBC的灵活性,通过这些脚本来进行数据库的批量操作。

此类代码不应出现在网络版本中,以免因各种情况被外部调用。


三、直接用Mybatis

1 容易出错的点

目前大部分平台代码都是基于Mybatis来处理持久层和数据库的交互。 Mybatis 有两个占位符用于传入数据 {} 和 #{}。 {}和#{}。 {}可以理解为语义分析前的字符串拼接,传入的参数原样传入。

例如

SELECT id, name, phone FROM userTable WHERE name =${name};

传入name=xiaoming后,就相当于

SELECT id, name, phone FROM userTable WHERE name = xiaoming;

在实际应用中

SELECT id, name, phone FROM userTable WHERE ${col} =xiaoming;

传入 col = “name”,相当于

SELECT id, name, phone FROM userTable WHERE name =xiaoming;

正如预编译原理介绍中提到的,#{}占位符不存在注入问题。但是,在某些业务场景中,不能直接使用#{}。

例如,在 order by 的语法中

如果写SELECT id, name, phone FROM userTable ORDER BY #{};,执行时会报错。因为order by后面的内容是列名,是代码语义的一部分。如果在语义分析部分没有确定,则相当于执行SELECT id, name, phone FROM userTable ORDER BY。一定有语法错误。

例如,在类似的场景中

SELECT id, name, phone FROM userTable WHERE name like%#{name}%;

#{} 不会被解析,导致错误。

in文法和文法之间是一样的,怎么解决问题呢?

2 正确书写

order by (group by) 语句使用 ${}

1.使用条件判断

2.使用全局过滤机制,限制order by后的变量内容只能是数字、字母、下划线。

如使用正则过滤:

这里需要注意,过滤需要使用白名单,不能使用黑名单,黑名单无法解决注入问题。

LIKE语句

由于需要like中的关键词需要包裹在两个%符号中,因此可以使用CONCAT函数进行拼接。

注意不要用 CONCAT(‘%’,’${stuName}’,’%’) ,这样仍然存在漏洞。也就是说,使用$符号是不对的,使用#符号才安全。

IN语句

类似于like语句,直接使用#{}会报错,常见的错误写法为:

正确的写法为:

四 Mybatis-generator使用安全

在繁重的CRUD代码压力下,开发者慢慢开始通过Mybatis-Generator、idea-mybatis-generator插件、通用Mapper、Mybatis-generator-plus自动生成Mapper、POJO、Dao等文件。

这些工具可以自动生成CRUD需要的文件,但是如果使用不当,就会自动生成SQL注入漏洞。我们以最常用的 org.mybatis.generator 为例来说明可能出现的问题。

1 动态语句支持

Mybatis-generator 提供了一些功能来帮助用户连接 SQL 的各种条件,例如多参数的 like 语法和多参数的比较语法。为了保证使用的简单性,需要将一些语义代码拼接成SQL语句。如果开发者使用不当,外部输入也会传递到 {} 占位符中。会有漏洞。

2 TargetRuntime 参数配置

配置生成器时,配置文件generator-rds.xml中有一个targetRuntime属性,默认为MyBatis3。在这种情况下,Mybatis 的动态语句支持将被激活,enableSelectByExample、enableDeleteByExample、enableCountByExample、enableUpdateByExample 的功能将被激活。

以 enableSelectByExample 为例,以下动态模块将被替换到 xml 映射文件中:



开发者include该模块就可以添加where条件,但如果使用不当,就会导致SQL注入漏洞:

并使用自定义的参数添加函数:



目的是为了实现同时对display_name、org、status、id的like操作。其中addCriterion是Mybatis-generator自带的函数:这里的误区在于,addCriterion本身提供了多个条件的支持,但开发者认为需要自己把多个条件拼接起来,一同传入addCriterion方法。如同案例中的代码一样,最终传入addCriterion的只有一个参数。从而执行Example_Where_Clause语句中的:


也就是说,开发者把自己拼接的SQL语句,直接代入了${criterion.condition}中,从而导致了漏洞的产生。而按照Mybatis-generator的文档,正确的写法应该是:

or方法负责创建Criteria,这时触发的逻辑就是

${criterion.condition}被替换为了没有单引号的like,like作为语义代码,在语义分析前拼接到了SQL语句中,而”%” + keyword + “%”会作为数据添加到预编译#{criterion.value}中去,从而避免了注入。
类似的,也提供了In语法的安全使用方法:

Beetween的安全使用方法:

Mybatis-generator默认生成的order by语句也是使用${}直接进行拼接的:

如果没有对传入的参数进行额外的过滤的话,就会导致注入问题。

3 order by

除了自己写的SQL语句以外,Mybatis-generator默认生成的order by语句也是使用${}直接进行拼接的:

如果没有对传入的参数进行额外的过滤,就会导致注入问题。

PS:在实际扫雷过程中,发现很多句子自动生成了order by syntax,但是上层调用的时候,没有传入可选参数,这种情况应该删除冗余order by syntax。

4 其他插件

插件和插件之间的安全漏洞是不一样的。下面简单列举几个常用的插件。

想法-mybatis-生成器

这是一个IDEA插件,可以在开发过程中从IDE层面自动生成CRUD中需要的文件。使用本插件时,有一些默认的安全隐患需要注意。

1) 通过加工定制订单

like\in\between 可以参考官方文档使用,不存在安全隐患。

但是插件没有内置的处理顺序,需要自己写。写的时候参考Case2

2)在默认IF条件之前,需要判断是否为空

插件默认生成的语法大致如下:


当ID参数为null时,if标签下的逻辑不会被添加到SQL语句中,这可能会导致DOS和权限绕过等漏洞。因此,在将参数传入查询语句之前,需要确认它不为空。

com.baomidou.mybatis-plus

在 apply 方法中传递参数时,{}
应该使用内置的 last 方法。原理是直接拼接到SQL语句的末尾,存在注入漏洞。

五 其它ORM框架

1 休眠

ORM全称为对象关系映射(Object Relational Mapping)。简单的说就是把数据库中的表映射到Java对象上,只有属性没有业务逻辑对象也叫POJO(PlAIn Ordinary Java Object)对象。

HiberNATe 是第一个广泛使用的 ORM 框架。它通过 XML 管理数据库连接,并提供高度封装的全表映射模型。配置好映射文件和数据库链接文件后,Hibernate就可以通过Session对象进行数据库操作了,开发者不需要接触SQL语句。只需编写 HQL 语句。

Hibernate 经常与 Struts 和 Spring 结合使用,后者是 Java 世界中经典的 SSH 框架。

与 SQL 相比,HQL 有更多的语法限制:

无法查询未映射的表。仅当模型之间的关系明确时才能使用 UNION 语法。
表名、列名区分大小写。
没有 *、#、–。
没有延迟功能。

所以HQL注入比SQL注入困难得多。从代码审计的角度来看,和普通的SQL注入是一致的:

拼接会导致注入漏洞:

您可以使用占位符和命名参数来阻止 SQL 语句,其本质是预编译。


Hibernate在使用过程中存在很多不足:

1.全表映射不灵活,更新时需要发送所有字段,影响程序效率。
2.对复杂查询的支持很差。
3.对存储过程的支持很差。
4.HQL 性能较差,无法基于 SQL 进行优化。

在审计 Hibernate 相关的注入时,可以通过全局搜索 createQuery 快速定位 SQL 操作的位置。

2 JPA

JPA 称为 Java 持久性 API。它是 Java EE 提供的数据持久化规范,它允许开发人员通过 XML 或注解将对象持久化到数据库中。

主要包括三个方面:

1.ORM映射元数据,通过XML或注解,描述对象与数据表的对应关系。框架可以自动将对象中的数据保存到数据库中。

常见的注解有:@Entity、@Table、@Column、@Transient

2.数据操作API,内置接口,方便对数据表进行CRUD操作,节省开发者编写SQL的时间。

常用方法有: entityManager.merge(T t);

3.JPQL,它提供了一种面向对象而非面向数据库的查询语言,将程序与数据库和SQL解耦。

JPA是一个集合规范,Hibernate 实现了这个 JPA 规范。

在Spring框架中,提供了一个简单版本的JPA实现——spirng data jpa。按照约定的方法命名规则编写dao层接口,无需编写接口实现即可访问和操作数据库。同时,它提供了除CRUD之外的许多功能,如分页、排序、复杂查询等。使用起来比较方便,但是底层还是在使用Hibernate的JPA实现。

与HQL注入相同,如果使用拼接方式将用户可控的数据替换到查询语句中,会导致SQL注入。

安全查询应该使用预编译技术。

Spring Data JPA 的预编译写法是:

Tips:其实Hibernate比JPA规范出现的更早。 Hibernate逐渐成熟后,JavaEE开发团队邀请Hibernate核心开发者共同开发JPA。规格。之后,根据规范进一步优化了Spring Data JPA。此外,还有很多产品用于实现JPA 规范,例如Eclipse 的TopLink(OracleLink)。

六小结

经过上面的介绍,特别是围绕Mybatis易出错点的讨论,我们可以得出以下结论:

1.存在多种类型的持久层组件。
2.开发者对工具使用的误解是造成该漏洞的主要原因。
3.由于自动生成插件的动态特性,不能简单地使用${}来发现自动发现的SQL漏洞。必须根据全局持久层组件特性,做详细的匹配规则。

 

转载请注明:小猪云服务器租用推荐 » 如何避免出现SQL注入漏洞