SQL注入攻击的实现和防止

时间:2022-03-04 13:58:35

什么是SQL攻击?

在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL语句片段和我们DAO中的SQL语句组合成一个完整的SQL语句。就会出现一些意想不到的结果。

演示SQL注入攻击:

CREATE TABLE tab_user(
userid VARCHAR(10) PRIMARY KEY,
username VARCHAR(20) NOT NULL,
PASSWORD VARCHAR(20) NOT NULL
);
插入四条数据
INSERT INTO tab_user(userid,username,PASSWORD) VALUES('1','德玛','123');
INSERT INTO tab_user(userid,username,PASSWORD) VALUES('2','信爷','123');
INSERT INTO tab_user(userid,username,PASSWORD) VALUES('3','皇子','123');
INSERT INTO tab_user(userid,username,PASSWORD) VALUES('4','拉克丝','123');
下面通过Java代码来模拟登陆:
public boolean login(String uname, String upwd) {
Connection conn = null;
Statement state = null;
ResultSet set = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url, user, password);
state = conn.createStatement();
String sql = "select * from tab_user where username='"+uname+"' and password='"+upwd+"'";
set = state.executeQuery(sql);
System.out.println(sql);
return set.next();
} catch (Exception e) {
throw new RuntimeException("查询错误:"+e);
}
finally {
try {
if(set!=null)
set.close();
if(state!=null)
state.close();
if(conn!=null)
conn.close();
} catch (Exception e) {
throw new RuntimeException("关闭错误:"+e);
}
}
}
测试方法:
@Test
public void fun3() {
System.out.println(login("德玛", "123"));
}
输出结果是:
select * from tab_user where username='德玛' and password='123'
1,德玛,123。说明这样能取到记录,可以登录。
那如果这样写呢?
@Test
public void fun3() {
login("a' or 'a'='a", "a' or 'a'='a");
}
输入结果是:
select * from tab_user where username='a' or 'a'='a' and password='a' or 'a'='a'
1,德玛,123
2,信爷,123
3,皇子,123
4,拉克丝,123
四条记录全部取到,如果是登陆的话是不是很可怕。 原因就是输入了一些sql的语句符合和现在Java代码中写的sql语句的符号组合了,形成了一条新的并且作用效果并不是预期的语句。

解决方法是:

只要阻止输入[or ']这类字符就行。(这个方法可以在页面和代码中实现)还有就是分步校验。下面演示:
public void login2(String uname,String upwd){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url,user,password);
stmt = conn.createStatement();
String sql = "select password from tab_user where username='"+ uname+"'";
rs = stmt.executeQuery(sql);
if(rs.next()){
if(upwd.equals(rs.getString(1))){
System.out.println("登陆成功");
}else{
System.out.println("登陆失败");
}
}
}catch(Exception e){
throw new RuntimeException(e);
}finally{
try{
if(rs!=null) rs.close();
if(stmt!=null) stmt.close();
if(conn!=null) conn.close();
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
先测试正确的
@Test
public void fun3() {
login2("拉克丝","123");
}
输出结果:登陆成功
在测试使用违法的
@Test
public void fun3() {
login2("a' or 'a'='a", "a' or 'a'='a");
}
输出结果:登陆失败。SQL注入失败。可以

还有一种方法是使用PreparedStatement也能实现,但它不仅能防止SQL注入。它主要作用是提高了代码的可读性和可维护性。最主要是提高了运行效率。下面示范:
public void login3(String uname,String upwd){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url,user,password);
String sql = "select * from tab_user where username=? and password=?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, uname);//设置第一个问号的值
pstmt.setString(2, upwd);//设置第二个问号的值
rs= pstmt.executeQuery();//注意这里是无参的方法
if(!rs.next()){//如果没有满足条件的记录
System.out.println("登陆失败");
}else{
rs.beforeFirst();
while(rs.next()){
System.out.println(rs.getString(1)+","+rs.getString(2)+","+rs.getString(3));
}
}

}catch(Exception e){
throw new RuntimeException(e);
}finally{
try{
if(rs!=null) rs.close();
if(pstmt!=null) pstmt.close();
if(conn!=null) conn.close();
}catch(Exception e){
throw new RuntimeException(e);
}
}

}

演示测试结果:
@Test
public void fun3() {
login3("a' or 'a'='a", "a' or 'a'='a");
}
登陆失败
@Test
public void fun3() {
login3("信爷","123");
}
2,信爷,123。
可以防止