漏洞原理
所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击.当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入。
java开发中有一些框架会托管一部分的数据库的操作,我们也需要要了解一下
漏洞形式和修复:拼接和预编译
1、JDBC:直接拼接构造sql语句,未进行过滤
将用户可控的参数值直接拼接到SQL语句中,没有经过有效的过滤,导致SQL注入。
用户可控参数类型:
(1)直接可控参数:例如从用户请求中直接获取参数request.getParameter(“xxx”)获取的xxx参数值
(2)间接可控参数:从数据库中获取参数拼接到SQL语句中,但数据库中存储的值为用户提交的数据
JDBC的直接拼接方式,存在sql注入:
request.getParameter("id")
private String getNameByUserId(String id) {
Connection conn = getConn();//获得连接
String sql = "select name from user where id=" + id;
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs=pstmt.executeUpdate();
}
审计方式:全局搜索sql、select等关键字,判断是否通过拼接形式来构造sql语句,以及构造参数是否用户可控。
预防措施,修复方法
通过预编译,可有效防止sql注入:
//安全的,预编译的,防止了sql注入
Connection conn = getConn();//获得连接
String sql = "select id, username, password, role from user where id=?"; //执行sql前会预编译号该条语句
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, id);
ResultSet rs=pstmt.executeUpdate();
2、Mybatis和Hibernate 框架
框架主要都是使用注解或者xml将java对象与数据库sql操作对应。
下面以 Mybatis 讲解一下
- mybatis的maven配置
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
- config.xml 配置数据库连接的文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatistest"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
主要注意的点!
UserMapper.xml
正确的预编译
<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = #{username,jdbcType=VARCHAR}
and password = #{password,jdbcType=VARCHAR}
</select>
存在漏洞的预编译
<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = ${username,jdbcType=VARCHAR}
and password = ${password,jdbcType=VARCHAR}
</select>
#和$的区别:
1、#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号,正确的预编译,输入的参数会全部变成查询的部分
2、将传入的数据直接拼接在sql语句中 。造成sql注入如 :where username = 将传入的数据直接拼接在sql中。造成sql注入 如:where username=将传入的数据直接拼接在sql中。造成sql注入如:where username = {username},如果传入的值是111,那么解析成sql时的值为where username=111;
如果传入的值是1 and 1=1 ;,则解析成的sql为:select id, username, password, role from user where username=1 and 1=1
MyBatis框架易产生SQL注入漏洞的三种情况:
1、模糊查询
Select * from news where title like ‘%#{title}%’
在这种情况下使用#程序会报错,新手程序员就把#号改成了$,这样如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
正确写法:
select * from news where tile like concat(‘%’,#{title}, ‘%’)
2、in 之后的多个参数
in之后多个id查询时使用# 同样会报错
Select * from news where id in (#{ids})
正确用法为使用foreach,而不是将#替换为$
id in<foreach collection="ids" item="item" open="("separatosr="," close=")">#{ids} </foreach>
3、order by 之后
在order by之后直接使用#{}格式传参,会导致排序无效,所以只能需要${}格式。
例如根据前端传过来的参数,使用order by #{}格式进行排序。
select * from tableName order by #{column} #{desc};
虽然不会报错,但也不能正确排序。
因为当使用#运算符,Mybatis会将传入的对象当成一个字符串,在进行变量替换时会加上引号,所以上面的order by语句,替换后就变成了下面的样子
select * from tableName order by 'columnName' 'desc';
若要使其能正常排序需要使用${}形式构造sql语句,将SQL查询语句修改如下:
select * from tableName order by ${column} ${desc}
修改之后,程序通过预编译,可以正常实现排序效果。
由于使用${}形式构造sql语句,Mybatis不会进行预编译,直接把值传进去,无法防止sql注入。所以当我们需要传字段的名称时,可以考虑使用$符号,但在后台需要进行白名单限制或者传入数据校验,才能在一定程度上防止sql注入。
Hibernate防止SQL注入
对参数名称进行绑定在HQL语句中定义命名参数要用”:”开头,形式如下:
Query query=session.createQuery(“from User user where user.name=:customername and user:customerage=:age ”);
query.setString(“customername”,name);
query.setInteger(“customerage”,age);
按参数位置邦定:
在HQL查询语句中用”?”来定义参数位置,形式如下:
Query query=session.createQuery(“from User user where user.name=? and user.age =? ”);
query.setString(0,name);
query.setInteger(1,age);
setParameter()方法: ,
在Hibernate的HQL查询中可以通过setParameter()方法邦定任意类型的参数,如下代码:
String hql=”from User user where user.name=:customername ”;
Query query=session.createQuery(hql);
query.setParameter(“customername”,name,Hibernate.STRING);
setProperties()方法:
Entity entity=new Entity();
entity.setXx(“xx”);
entity.setYy(100);
Query query=session.createQuery(“from Entity c where c.xx=:xx and c.yy=:yy ”);
query.setProperties(entity);
0x05 挖掘方法
使用idea 搜索$关键字
可以先筛选xml文件搜索$,逐个分析,要特别注意mybatis-generator的order by注入
Ctrl+shift+F 调出Find in Path,筛选后缀xml,搜索$关键字
找到是mybatis的数据库文件
找到调用函数后,alt+f7查看调用链,检查中间是否被过滤