一、典型代码框架简述
Spring是一个开源的Java框架,提供了一系列组件和工具,用于构建企业级应用程序。
审计一个基于Spring的项目,它的目录看起来是这样的
假设我们有一个简单的订单管理系统,包含以下几个层级:
model层:数据库实体层,也被称为entity层,pojo层。
一般数据库一张表对应一个实体类,类属性同表字段一一对应。
dao层:数据持久层,也被称为mapper层。
访问数据库,向数据库发送sql语句,完成数据的增删改查任务。
service层:业务逻辑层。
调用dao层接口,接收dao层返回的数据,完成项目的基本功能设计。
controller层:控制层。
功能为请求和响应控制。负责前后端交互,接受前端请求,调用service层,接收service层返回的数据,最后返回具体的页面和数据到客户端。
在上述示例中,通过使用Spring的依赖注入(@Autowired 注解)将不同层级的组件连接在一起。控制层的 OrderController 依赖于业务逻辑层的 OrderService,而业务逻辑层的 OrderService 又依赖于数据访问层的 OrderRepository。
当用户发起对订单详情的请求时,OrderController 的 getOrderDetails 方法被调用。它使用 OrderService 来获取订单对象。
OrderService 中的 getOrderById 方法使用 OrderRepository 来执行数据库查询,并返回相应的订单对象。
总体而言,Spring框架通过依赖注入的方式,实现了不同层级之间的解耦和灵活的组件替换。每个层级只需要关注自己的责任,而不需要关心具体的依赖实现。这种解耦和抽象使得程序更易于开发、测试和维护,并支持可扩展性和可重用性。
接下来,我们对sql注入的漏洞代码进行审计。
二、JDBC漏洞
JDBC(Java DataBase Connectivity:java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系型数据库提供统一访问,它是由一组用Java语言编写的类和接口组成的。
实现使用java代码操作数据库
JDBC存在两种方法执行SQL语句,分别为PreparedStatement和Statement。PreparedStatement会对SQL语句进行预编译,Statement会直接拼接sql语句造成SQL注入漏洞
下图例子为Vuln code,指“敏感代码”,含有漏洞
构建SQL查询语句时,没有对输入的username参数进行任何检查或者过滤,直接将其拼接到SQL语句中。
攻击者可以通过在username中插入恶意的SQL代码来修改原始的查询意图,甚至执行其他未授权的数据库操作。例如,攻击者可以使用‘ OR ‘1’=’1′–作为username的值,这将导致查询语句变为:
select * from users where username = ” OR ‘1’=’1′–‘
这句sql语句的含义是:
SELECT *: 选择所有列,意味着返回users表中符合条件的所有行的所有列数据。
FROM users: 指定要查询的表是users。
WHERE username = ” OR ‘1’=’1′–‘: 这是查询的条件部分。
username = ”:检查username列是否等于空字符串。这似乎是要查找username为空的记录。
OR ‘1’=’1′:这部分是一个逻辑表达式,始终返回true。由于‘1’与‘1’相等,所以这个表达式永远为真。因此,这个条件可以绕过前一个条件username = ”。
–‘:这是一个注释符号,表示在这个符号后面的内容将被视为注释,不会被数据库处理。
综上所述,这个查询的意图似乎是为了获取users表中username为空或者绕过其他条件而返回所有用户的信息。然而,这个查询存在SQL注入的风险。使用‘1’=’1′作为永真条件,可以绕过原有的条件并获取所有用户的信息。
要修复这个漏洞,应该使用参数化查询或预编译语句,而不是直接将用户输入拼接到SQL语句中。具体实现方式取决于编程语言和使用的数据库管理系统
下图例子为安全代码,使用PreparedStatement类的setString方法
PreparedStatement.setString()将指定的参数设置为给定的Java字节数组。驱动程序在将其发送到数据库时将其转换为SQL VARBINARY或LONGVARBINARY(SQL的数据类型),实现PrepareStatement对SQL语句进行预编译。预编译的好处不仅在于在一定程度上防止了sql注入,还减少了sql语句的编译次数,提高了性能,其原理是先去编译sql语句,无论最后输入为何,预编译的语句只是作为字符串来执行,而SQL注入只对编译过程有破坏作用,执行阶段只是把输入串作为数据处理,不需要再对SQL语句进行解析,因此解决了注入问题。
PrepareStatement防御预编译的写法是使用?作为占位符然后将SQL语句进行预编译,由于“?”作为占位符已经告诉数据库整个SQL语句的结构,即?处传入的是参数,而不会是sql语句,所以即使攻击者传入sql语句也不会被数据库解析。传入的payload已进行转义,安全。
JDBC易产生漏洞点:
1.未使用占位符
PreparedStatement只有在使用“?”作为占位符才能预防sql注入,直接拼接仍会存在sql注入漏洞
2.使用in语句
删除语句中可能会存在此类语句,由于无法确定delIds含有对象个数而直接拼接sql语句,造成sql注入。
String sql = “delete from users where id in(“+delIds+”); //存在sql注入
解决方法为遍历传入的对象个数,使用“?”占位符。
3.使用like语句进行模糊查询
LIKE 运算符用于在 WHERE 子句中进行模糊匹配。它可以根据模式匹配字符串,并返回匹配的结果。% 是通配符,表示任意字符的零个或多个实例。所以 ‘%keyword%’ 可以匹配包含 keyword 的任意位置的字符串。
String sql = “select * from users where password like ‘%” + con + “%'”; //存在sql注入
4.%和_
预编译是不能处理%的, 所以需要手动过滤%,否则会造成慢查询,数据库性能下降,影响系统的响应时间。
5.Order by、from等无法预编译
当使用order by语句时是无法使用预编译的,原因是order by子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名,然而使用PreapareStatement将会强制给参数加上‘,所以,在使用order by语句时就必须得使用拼接的Statement,所以就会造成SQL注入,需要进行手动过滤,否则存在sql注入。
String sql = “Select * from news where title =?” + “order by ‘” + time + “‘ asc”
三.Mybatis框架下的sql注入
Mybatis是一款半自动的ORM(对象关系映射,Java对象和数据库的关系模型之间建立一种对应关系)持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,数据库无关性较低
Mybatis使用parameterType向sql语句传参,在sql引用传参可以使用#{Parameter}和${Parameter}两种方式
1 .${Parameter}方式
${Parameter}采用拼接的方式构造SQL语句,在对用户输入过滤不严格的前提下,存在sql注入漏洞
假如在controller层使用
userMapper.findByUserNameVuln01(username);进行查表
调用mapper层的Usermapper.java 的接口
2.#{Parameter}方式
#{Parameter}采用预编译的方式构造SQL语句,避免了SQL注入的产生
假如在controller层使用
userMapper.findByUserName(username);进行查表
调用mapper层的Usermapper.java 的接口
MyBatis易产生SQL注入的三种情况
1.使用like语句进行模糊查询
模糊查询使用#{}程序会报错,如果把#号改成了$,而java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
正确写法如下:
其他正确写法:
select * from users where username like concat(‘%’,#{username},’%’)oracle:
select * from users where username like ‘%’||#{username}||’%’sqlserver:
select * from users where username like ‘%’+#{username}+’%’
2.使用in语句
使用in语句时直接使用#{}会报错,如果使用${}直接拼接,造成sql注入
正确写法如下:
使用foreach,而不是将#替换为$
id in<foreach collection=”ids” item=”item” open=”(“separatosr=”,” close=”)”>#{ids} </foreach>
3.使用order by 语句
和JDBC同理,使用#{}方式传参会导致order by语句失效,所以使用order by语句的时候还是需要做好过滤
原文始发于微信公众号(瑞不可当):Java代码审计—SQL注入篇