Java代码审计-SQL注入
前言
今天学习了java代码中导致的sql注入的相关知识,浅浅的记录一下。
一、SQL注入简介
SQL 注入是因为程序未能正确对用户的输入进行检查,将用户输入以拼接的方式带入 SQL 语句中,影响原 SQL 语句的逻辑,导致了 SQL 注入的产生。
SQL 注入漏洞可能会造成服务器的数据库信息泄露、数据被窃取、网页被篡改,甚至可能会造成网站被挂马、服务器被远程控制、被安装后门等。
二、Java主要执行SQL语句的方式
1.JDBC
JDBC是Java连接数据库的标准API。它提供了一组接口和类,用于与关系型数据库进行交互。通过JDBC,可以连接到各种数据库(如MySQL、Oracle、SQL Server等),执行SQL语句、事务管理和结果集处理等操作。
2.MyBatis
ORM框架,允许将Java对象和关系型数据库之间进行映射,进而实现数据库操作。
Mybatis 是一个持久层框架,它封装数据连接、获取结果集等一系列的繁琐操作,使用者只需关注操作 SQL 语句的编写,通过 xml 或注解的方式就能将数据库中的数据与对象形成映射进行返回。
Mybatis 的学习曲线相对较低,适合对 SQL 有深入理解的开发者。
3.Hibernate
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将java对象与数据库表建立映射关系,是一个全自动的orm框架。
Hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
Hibernate 的学习曲线较高,需要理解其 ORM 模型和配置。
三、JDBC方式产生的漏洞
1.Statement方式
java.sql.Statement 是 Java JDBC 下执行 SQL 语句的一种原生方式,执行语句时需要通 过拼接来执行。若拼接的语句没有经过过滤,将出现 SQL 注入漏洞。
//采用拼接的方式
String sql = "select * from user where id = "+req.getParameter("id");
Statement st = con.createStatement(); //创建Statement对象
ResultSet rs = st.executeQuery(sql); //执行查询
由上述代码可以看到,该方式只是对前端用户输入直接进行sql语句的拼接,然后代入数据库查询,可导致SQL注入漏洞。
2.PreparedStatement方式
PreparedStatement 会预处理 SQL 语句,为原本需要拼接的参数参数保留一个问号(?)作为占位符。
采用预编译的方式,可以有效地防止SQL注入漏洞。
//sql语句如下 使用?代替之前需要拼接的参数
String sql = "select * from user where id = ?";
PreparedStatement pstt = con.prepareStatement(sql); //对sql语句进行预编译
pstt.setInt(1,Integer.parseInt(req.getParameter("id"))); //对里面的参数进行设置 可以理解成将id转换为int后放到第一个?的位置
ResultSet rs = pstt.executeQuery();//执行查询
像上述这样正确使用预编译的操作是比较安全的,可以有效防止SQL注入。
那么,预编译防止SQL注入怎么做的呢?
- 对下面代码,输入" 小明’ ",经过测试发现是对特殊符号加入转义字符。
String sql = "select * from user where name = ?"; // sql语句
PreparedStatement st = conn.prepareStatement(sql); //预编译
st.setString(1, "小明'"); // 参数赋值 sql注入测试
System.out.println(st.toString()); //输出为:select * from user where name = '小明\''
那么,使用预编译的方法就完全避免注入的影响了嘛?、
- 答案是否定的。
经过测试发现,预编译不会对%进行转移,这正好是SQL语句like查询需要的。
假如代码如下所示,则会遇到SQL注入的风险。
String sql = "select * from user where name like ?"; // 含有参数
st = conn.prepareStatement(sql);
st.setString(1, "%小明%" + "%"); // 参数赋值
System.out.println(st.toString()); //输出为select * from user where name like '%小明%%'
这样就改变了原本查询的意思。
另外,预编译也不会对_进行转义。
那么,还有个问题,就是不是所有的语句都能够使用预编译来解决的。
- 比如代码中使用order by语句时,这种特殊情况无法使用预编译。order by 子句后面需要加字段名或者字段位置,而预编译后产生的是字符串,不再是字段名,这种情况下只能使用sql语句拼接的办法。
- 所以使用这些特殊语句时,要注意对输入进行过滤。
四、MyBatis方式产生的漏洞
1.两种参数符号造成的SQL注入
MyBatis支持两种参数符号,一种是#,另一种是$。
- 当使用#时,MyBatis底层会使用预编译的机制。
- 使用参数符号$时,MyBatis底层直接用字符串拼接把参数和SQL语句拼接在一起,然后执行。
2.order by、like、in引发注入
- 与第三部分的描述类似,在mybatis中使用order by语句的话,只能使用参数符号$,即使用字符串拼接的方式,如果没有进行严格过滤,则导致注入。
- like in 同理
五、Hibernate方式产生的漏洞
1.简介
- HQL注入:Hibernate中没有对数据进行有效的验证导致恶意数据进入应用程序中造成的。与SQL注入类似。
2.原理
与第三部分、第四部分类似。
参数绑定,这样是安全的
String queryString = "from Item item where item.deion like :searchString”;
List result = session.createQuery(queryString).setString("searchString", searchString).list();
直接拼接是不安全的。
HQL注入利用比较有限。
六、总结
综上所述,主要是考虑代码是否使用了预编译,以及对于一些未使用预编译的特殊语句,是否进行了安全的校验。