在上篇文章中 《 JDBC技术详解(一)--入门及其API 》,提到的使用statement对象执行字符串拼接的sql会存在注入攻击漏洞。这篇文章,我们来解决这个问题。
首先,什么是sql注入攻击?
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。
通俗的说:sql注入攻击是指,在使用字符串拼接sql语句时,恶意拼接带sql语言关键词,使得sql语句的原意改变,达到恶意攻击的目的。
下面我们通过案例(查询指定用户)来演示sql注入攻击:
在数据库中有如下一张User表,我要根据name查询用户信息。
代码如下:
package jdbc.test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.jdbc.Driver;
public class test
{
public static void main(String[] args) throws ClassNotFoundException, SQLException
{
// 1、正常赋值情况下
// String userName = "admin";
// 2、非法赋值情况下
String userName = "admin' OR '1'='1 ";
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mydb";
String username = "root";
String pwd = "000000";
Connection con = DriverManager.getConnection(url, username, pwd);
Statement st = con.createStatement();
String sql = "select * from user where name='" + userName + "'";
ResultSet rs = st.executeQuery(sql);
// 5.对ResultSet进行遍历,得到信息.
while (rs.next())
{
int id = rs.getInt("id");
String name = rs.getString("name");
String password = rs.getString("password");
System.out.println("ID:" + id + " NAME:" + name + " password:" + password);
}
// 6.关闭资源
rs.close();
st.close();
con.close();
}
}
1、正常情况下,我们对userName字段赋值为admin时,拼接后的sql查询语句是:
select * from user where name='admin'
查询结果是我们期望的,只查询出了admin的信息。
2、但是要是给userName字段非法赋值为 admin' OR '1'='1 呢,拼接后的sql查询语句是:
select * from user where name='admin' OR '1'='1'
这个查询语句的条件就变成了永真,会将所有用户的信息查询出来,如下:
如何解决sql注入攻击漏洞问题?
其实很简单,不再使用字符串去拼接sql语句,而是通过将jdbc中的提供的preparedStatement对象中的参数化查询。下面我们来看下如何使用吧。
statement对象不提供这一功能。但是preparedStatement对象提供了这一功能。步骤如下:
1、先定义好sql语句,参数的地方用“?”作为暂占位符。
2、设置一系列参数,获取到connection对象。
3、通过connection对象获得preparedStatement对象,需要将sql语句作为参数传进去,其内部进行sql预格式化。
4、给占位符赋值。即使用setXxxx( int index, Xxx param)方法将占位符替换成我们想要的值。
5、执行sql查询。注意,要使用空参的executeQuery() 方法。
不废话了,上例子:
package jdbc.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.mysql.jdbc.Driver; public class test2 { public static void main(String[] args) throws ClassNotFoundException, SQLException { // 正常赋值情况下 String userName = "admin"; // 非法赋值情况下 // String userName = "admin' OR '1'='1 "; // 1、先定义好sql语句,其中需要使用参数的地方使用 "?" 来占位。 String sql = "select * from user where name=?"; Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/mydb"; String username = "root"; String pwd = "000000"; Connection con = DriverManager.getConnection(url, username, pwd); // 2、获取能执行sql语句的preparedStatement对象。在获取对象时,需要将sql语句作为参数传进去,其内部会对sql语句进行预格式化处理。 PreparedStatement pst = con.prepareStatement(sql); // 3、使用参数来替换掉sql语句中的问号占位符。 // 参数1:表示要替换的是第几个问号,从1开始。这里表示第一个问号。 // 参数2:表示要替换的内容是什么。这里的内容是userName的值,即“admin” pst.setString(1, userName); // 4、执行查询,注意:这个查询方法是没有参数的。 ResultSet rs = pst.executeQuery(); // 5.对ResultSet进行遍历,得到信息. while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); String password = rs.getString("password"); System.out.println("ID:" + id + " NAME:" + name + " password:" + password); } // 6.关闭资源 rs.close(); pst.close(); con.close(); } }
你会发现,不管你怎么输入参数,结果都是我们期望的。因为preparedStatement对象中,即使我们传进去的参数有关键的字符,但是它也会将我们传进去的整个字符串作为正真的一个参数,而不是简单的拼接字符串,所以,这样的话,sql语句就不会出现第二种意义了。这也就防止了sql注入攻击。
Statement和PreparedStatement对象的比对:
Statement
PreparedStatement
sql语句
sql语句使用字符串拼接
sql语句使用“?”占位符占位
获取对象
不需要参数 con.createStatement();
需要参数 con.prepareStatement(sql);
给参sql中参数赋值
不需要
setXxxx(索引,值)。
执行查询
需要sql作为参数 st.executeQuery(sql);
不需要参数 pst.executeQuery();
注意:关于给参数赋值,你得参数是什么类型的,就用对应类型的set方法,例如:
Int型: setInt(int index, int value)
String型:setStringt(int index, String value)
总结:
1、在企业开发中,这两种执行sql的方式都会使用到,但是其侧重点不一样。
2、非参数化查询,适合执行不带条件的sql语句,执行批处理性能相对高。
2、参数化查询,适合执行带条件的sql语句,执行批处理性能相对低。
本文为原创,如需转载,请注明出处!