1.SQL注入漏洞概念
用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据。举例来说在某个具有SQL漏洞的网站中注册后,正常情况下用户需通过用户名和密码来进行登录,但由于SQL漏洞的存在,他人只需知道你的用户名即可登录进你的账号来进行操作。
2.SQL注入示例
背景:数据表为user,其中有一条数据的用户名为"aaa",对应密码为"123"。
登录代码实现:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import jdbcUtils.JDBCUtils;
/**
* 登录功能的实现
* @author Silence_L
*
*/
public class Dao {
/**
* 登录方法
* @param username:用户名
* @param password:密码
* @return
*/
public boolean login(String username, String password) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// 定义一个变量用作返回结果
boolean flag = false;
try {
// 获得连接
conn = JDBCUtils.getConnection();
// 获得执行SQL语句的对象
stmt = conn.createStatement();
// 编写SQL语句
String sql = "select * from user where username = '" + username + "' and password = '" + password +"'";
// 执行SQL
rs = stmt.executeQuery(sql);
if(rs.next()) {
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
JDBCUtils.release(conn, stmt, rs);
}
return flag;
}
}
测试代码:
/**
* SQL注入漏洞示例
* @author Silence_L
*
*/
public class LoginTest {
public static void main(String[] args) {
Dao dao = new Dao();
String username = "aaa";
String password = "123";
if(dao.login(username, password)) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
}
}
(PS:JDBCUtils中编写了数据库的连接和资源释放两个静态方法)
正常结果(登录成功):
密码错误结果:
SQL注入结果1(用户名+’ or ‘1=1):
输入用户名为"aaa’ or ‘1=1",密码错误此时发现结果显示登录成功。
SQL注入结果2(用户名+’ – ):
输入用户名为"aaa’ – "(–后面必须带空格,不然会抛出异常),密码错误此时登录结果仍然是登录成功。
3.注入漏洞分析
第一种情况:
当输入的用户名为正确用户名+’ or '1=1时,此时对应的SQL语句为:
select * from user where username = 'aaa' or '1=1' and password = '456'
从SQL语句中不难看出在用户名检索条件后加了一个or语句,导致后面的密码判断的and部分没有执行就检索出了对应的结果。
第二种情况:
当输入的用户名为正确用户名+’ – 时,此时对应的SQL语句为:
select * from where username = 'aaa' -- and password = '456'
从代码的高亮显示不难看出“–”后面的内容变成了注释,故判断时只以用户名为判断依据,就造成了只需用户名正确就能登录成功的状况。
4.SQL注入漏洞的解决方法
利用PreparedStatement替代代码中的Statement来解决这个问题。修改后的代码如下所示:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import jdbcUtils.JDBCUtils;
/**
* 登录功能的实现
* @author Silence_L
*
*/
public class Dao {
/**
* 登录方法
* @param username:用户名
* @param password:密码
* @return
*/
public boolean login(String username, String password) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
// 定义一个变量用作返回结果
boolean flag = false;
try {
// 获得连接
conn = JDBCUtils.getConnection();
// 编写SQL语句
String sql = "select * from user where username = ? and password = ?";
// 预编译SQL
pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, username);
pstmt.setString(2, password);
// 执行SQL
rs = pstmt.executeQuery();
if(rs.next()) {
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
JDBCUtils.release(conn, pstmt, rs);
}
return flag;
}
}
结果展示:
从结果上来看此时两种输入方式都不能登录成功。
分析:
之所以能够解决注入问题,是因为PreparedStatement会先进行预编译,而代码中SQL语句中的问号相当于占位符,之后我们通过函数传参的方式来实现?的内容替代,但是由于设置了SQL语句的格式,此时?处的替代内容即便有SQL中的关键字也不会被识别,而是会当做普通的字符来进行识别,因此能够解决注入问题。