在日常生活中我们在网上购物、出行买票等一系列的操作时都需要很重要的一步,那就是先进入自己的个人账户,里面有更详尽的且别人看不到的私有个人信息,如果别人能够进入我们的个人账户,那就杯具了,本文将介绍一种安全性的隐患——仅通过获取正确的用户名就可以进入他人账户,它就是注入攻击,是Mysql数据库中内部机制产生的一种攻击方式。
注入攻击产生的原因是这样的:由于sql语句是由前台参数与后台语句拼接而来,在用户传入的参数位置可能会输入数据库的关键字。这些关键字可能是sql语句的语义发生改变,从而达到一些特殊的效果,这种操作方式称之为sql注入攻击。
由于大量用户的账号登录信息是存储在数据库中的,在输入用户信息时是一个动态的过程,后台设计登录与验证的程序,会根据用户输入的信息去查询数据库中的字段是否匹配,验证通过即可进入个人账户,在用后台程序执行sql语句更新或者查询数据库时,这些sql语句是由前台参数和后台语句拼接的,若用户名中的一些字符恰好是数据库的关键字时,会改变sql语义导致执行sql语句时发生特殊的效果。本文以数据库中关键字#为例,设计一个demo来表述sql语义改变而产生的特殊效果,以及会给出对应的解决方案。
A:注入攻击示例
1.在Mysql中创建database mydb4,再创建table users
2.用java执行sql语句,执行时是把前台参数(用户信息)和后台语句拼接
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
import com.mysql.jdbc.PreparedStatement;
public class Login {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入用户名:");
String username=sc.nextLine();
System.out.println("请输入密码:");
String password=sc.nextLine();
testLogin(username,password);
}
private static void testLogin(String username, String password) {
Connection conn=null;
Statement stat=null;
ResultSet rs=null;
try {
conn=JDBCUntils.getConnection();// ② 数据库驱动jdbc,抽取成一个方法来使用
stat=conn.createStatement(); //①npk 做下标记
rs=stat.executeQuery("select * from users where username='"+username+"'and password='"+password+"'");
if(rs.next()){//如果为true,则证明能查询到用户名,登录进去
System.out.println("登陆成功");
}else{
System.out.println("登陆失败,请重新登陆");
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCUntils.close(conn, stat, rs); }
}
}
3.接着我们看下运行结果
上述都是和数据库匹配后得到的效果,是没问题的。
4.接着我们在以下这个结果
怎么会这个样子呢。在输入密码那步我是按enter键跳过执行的,在得知一个真实的用户名后,在用户名后加了‘#,未输密码依旧“成功”进入一个账户中,如果这是一个储蓄账户,想想就后背发凉吧。
那我们分析传入参数(npk’#)依旧登录成功的原因:
由于前台传入的参数 与后台的sql语句动态拼接,在①npk行的代码中是查询sql语句,sql语句本义是 select * from user where username=‘username’ and password=‘password’;
可是为了实现动态拼接"select * from users where username=’"+username+"‘and password=’"+password+"’";当输入(npk’#)我们可以把整个拼接语句进行拆解,
拆解"select * from users where username=’",中是"" 引的是字符串,
//“select * from users where username=’”+npk‘#是读到了,但是(npk’#)中的’直接和第一个’拼接,组成username=‘npk’,而"‘and password=’"+password+"’“拆解为”‘and password=’" 与 password 和 “’” (空串),由于#时数据库中的关键字,注释了参数password
而最终的sql语义最终变成了select * from user where username=‘npk’# ‘and password=’
password '; 参数password没有读到,因此就会出现这种情况: 一旦正确输入用户名,加上 '# ,关键字#把要传入的参数注释了,uername正确读到,password为空串,“登陆成功”。
之前某路出行买票软件也因此出现个例,后来就改好了。
B:解决方案–利用Statement子接口PreparedStatement防止sql注入攻击
PreparedStatement由于具有预编译的功能,先将sql语句的主干部分发送到数据库服务器中,参数位置使用"?"预留,sql主干语句到达服务器中时,会立刻变成一段机器码(二进制数据),这个机器码不能被操作。
PreparedStatement发送sql 的步骤:先传sql语句主干,在把参数发送到数据库服务器,这些参数到达数据库服务器后时只会作为纯文本内容使用(比如关键字**#**只是text文本“#”),这样一来就不会使sql语义变化而注入攻击了。
以下是利用PreparedStatement来预编译防止注入攻击的代码:
//导包没变
public class Login {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入用户名:");
String username=sc.nextLine();
System.out.println("请输入密码:");
String password=sc.nextLine();
PreparedtestLogin(username,password);
}
private static void PreparedtestLogin(String username, String password) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try{
conn=JDBCUntils.getConnection();
ps= (PreparedStatement) conn.prepareStatement("select * from users where username=? and password=?");
ps.setString(1, username);
ps.setString(2, password);
rs=ps.executeQuery();
if(rs.next()){
System.out.println("登陆成功");
}else{
System.out.println("登陆失败,请重新登陆");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUntils.close(conn, ps, rs);
}
}
运行结果如下:
以上就是出现问题和解决的过程。 ps:其中第一个代码片中②行是数据库驱动jdbc,驱动的6部曲,我利用工厂模式建了一个JDBCuntils类,抽取了获取连接和关闭资源方法来简单配合着使用。 有空再把数据库驱动jdbc和工厂模式在整理下。