SQL注入攻击
拼接SQL语句存在SQL注入攻击
方式
表现为通过输入aaa' OR '1'='1改变后台SQL语句语义,不需要正确密码即可获取数据资料
比如以下登录代码中:
public class JDBCDemo6 {
public static void main(String[] args) {
System.out.println("欢迎登录");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String username = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
try (
Connection connection = DBUtil.getConnection();
){
Statement statement = connection.createStatement();
String sql = "SELECT id,username,password,nickname,age " +
"FROM userinfo " +
"WHERE username='"+username+"' " +
"AND password='"+password+"'";
ResultSet rs = statement.executeQuery(sql);
if(rs.next()){//根据该用户名和密码作为过滤条件查询到记录,说明登录成功
String nickname = rs.getString("nickname");
System.out.println("登录成功!欢迎回来:"+nickname);
}else{
System.out.println("登录失败,用户名或密码错误");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
sql语句WHERE筛选条件正常语义判断为满足正常用户名和密码正确可登录,不满足失败
String sql = "SELECT id,username,password,nickname,age " +
"FROM userinfo " +
"WHERE username='"+username+"' " +
"AND password='"+password+"'";
ResultSet rs = statement.executeQuery(sql);
此时我正常输入不满足的值,登录失败。
如果注入攻击输入,即:
登录成功了,并获取到了数据。
因为它此时的SQL语句是:
SELECT id,username,password,nickname,age FROM userinfo WHERE username='aaa' AND password='aaa' OR '1'='1'
AND优先级比OR高,那么WHERE中为A OR B满足其中一条就可通过,而‘1’=‘1’是恒定为true的,所以WHERE失效。
针对这个问题,JAVA提供了预编译。
预编译SQL
当SQL中含有变量时,通常我们不能直接拼接SQL语句,因为这存在SQL注入攻击。
- 我们可以使用预编译SQL语句,在预编译SQL语句中变量部分先用"?"代替。
- 然后将整条预编译SQL传给数据库使其生成执行计划(此时数据库就已经确定了SQL语义),然后等待我们传入"?"对应的值然后执行这条SQL。
- 此时无论我们传入的值是什么,数据库都仅会将其当做值看待,并不会在改变SQL语义。
例如:
为登录逻辑编写预编译SQL语句
SELECT id,username,password,nickname FROM userinfo WHERE username=? AND password=?
先将这个SQL发送给数据库,此时数据库就理解了这个SQL的语义。但是还不能执行,因为缺少?表达的两个值
当我们需要执行这条SQL时,我们仅需要再传入两个值给数据库即可:
public class JDBCDemo7 {
public static void main(String[] args) {
try (
Connection connection = DBUtil.getConnection();
){
String sql = "SELECT id,username,password,nickname,age " +
"FROM userinfo " +
"WHERE username=? AND password=?";
//创建PrepareStatement是要现将预编译SQL传入,此时该方法会将该SQL传给数据库
PreparedStatement ps = connection.prepareStatement(sql);
//为?指定值
//给第一个"?"(用户名的值)指定值,由于用户名为VARCHAR类型,因此这里要设定字符串
//参数1:第几个"?".下标从1开始.
// ps.setString(1,"张三");
// ps.setString(2,"123456");
ps.setString(1,"abbaba");
ps.setString(2,"a' OR '1'='1");//不会改变语义,仅将其当做密码的值看待
//执行该DQL,此时不需要再传入SQL语句了.执行时会将上述设置的"?"表达的值传递给数据库
ResultSet rs = ps.executeQuery();
if(rs.next()){
String nickname = rs.getString("nickname");
System.out.println("登录成功!欢迎回来:"+nickname);
}else{
System.out.println("登录失败,用户名或密码错误");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
代码思路:
获得连接对象connection
通过连接对象获得ps预编译执行对象,并在此时将预编译SQL传入(没有预编译时是在调用执行对象方法是传入)
调用setString给?赋值
在调用执行方法ps.executeQuery()执行(此时不用传SQL了)
判断是否有满足WHERE语句的记录并输出
同理在其他语言中也是如此,比如DML语言中: