SQL注入与防范
经常遇到的问题:数据安全问题,尤其是sql注入导致的数据库的安全漏洞
国内著名漏洞曝光平台:WooYun.org
数据库泄露的风险:用户信息、交易信息的泄露等
什么是SQL数据库注入?
web应用下,终端用户是无法直接访问数据库的,他们必须通过发送http请求到Java服务器,由Java服务器访问后端服务器。因此,恶意用户想要获取数据库中的数据,必须通过Java服务器来访问后端数据库而无法绕行。他们的唯一途径就是利用应用程序的漏洞,伪装自己的请求,欺骗业务程序,达到访问数据库的目的。
之前学习的代码:用户登录场景
User user = null;
String sql = "select * from user where userName = '" + userName
+ "' and password = '" + password + "'";
rs = stmt.executeQuery(sql);
while(rs.next()) {
user = new User();
user.setUserName(rs.getString("userName"));
user.setCardNum(rs.getString("cardNum"));
}
return user;
Java应用程序接收到用户的输入后,检索后端数据库,匹配数据库记录。
如果:
用户输入为“Zhangsan’;-- ” 密码随便输入(因为不知道真实密码)
也会成功登录,为什么呢?
select * from user where userName = ‘ZhangSan’;-- ‘ and password = ‘111’;
密码条件被注释
总结:数据库注入就是用户在输入部分输入SQL语句,破坏原有SQL的语义,以达到欺骗服务器并发送恶意SQL的业务程序的漏洞。
原因:SQL语句为动态拼接的,语义未知。
解决方案:除了动态拼接,还有什么办法能使用web传入的参数呢?
参数化SQL的试下:1、确定SQL的含义;2、传入参数
利用Connection对象的.preparedStatement(sql)方法;
preparedStatement ()相对Statement的最大优点:提供了参数化SQL的试下(格式化的SQL)
select * from user where userName = ? and password = ?
?为占位符,替代了一个参数。SQL的语义确定了。
根据参数的类型:.setInt(); .setString(); .setBoolean(); 来替代参数化SQL中的占位符
除了使用PreparedStatement,我们还应该有一些其他的注意事项:
- 执行严格的数据库权限管理
仅授予web应用访问数据库的最小权限
严格禁止Drop table等权限
- 封装数据库错误:不能将数据库异常直接暴露给用户(因为数据库异常经常包含了大量的数据库信息)
禁止直接将后端数据库异常信息暴露给客户
对后端异常进行必要的封装,避免用户直接查看到后端异常
- 机密信息禁止明文存储
涉密信息需要加密处理
针对MySQL,可使用AES_ENCRYPT/AES_DECRYPT进行加密和解密
课堂交流区:
其他的SQL注入场景:可以使用 “' or 1;#” 或者 “' or 1;-- ”
i.e. select name from student where name =” ' or 1;#”;
会返回所有。
在通过某个字段查找数据表时,如果SQL语句本身是拼接而成的,并且程序中没有对字段数据进行有效性验证,则有暴露用户信息和业务信息的可能。如下面的程序,可以查询出数据库中所有用户的数据信息。String userName = "anystring' or '1=1";
String sql = "select * from user where userName='" + userName +"'";
作业:有一张学生表
现在需要根据学生名称获取学生的期末考试分数。
public static void getStudent(String name) throws ClassNotFoundException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(DB_URL, USER, PASS);
stmt = conn.createStatement();
rs = stmt.executeQuery("select name,score from student where name =' " + name +"'");
while (rs.next()) {
System.out.println(rs.getString("name") + ":" + rs.getInt("score"));
}
} catch (SQLException e) {
// ignore
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
if (stmt != null) {
try {
stmt.close();
} catch (Exception e) {
// ignore
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
}
请重新编写应用程序,解决上述风险。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class sql {
static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
static final String USER = "root";
static final String PASSWORD = "xjj5211314";
public static void getStudent(String name) throws ClassNotFoundException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from user where userName = '" + name + "';");
while (rs.next()) {
System.out.println(rs.getString("userName") + ":" + rs.getInt("number"));
}
} catch (SQLException e) {
// ignore
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
if (stmt != null) {
try {
stmt.close();
} catch (Exception e) {
// ignore
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
}
public static void main(String[] args) {
String name = "' or 1;-- ";
try {
getStudent(name); //使用了这个变量,会吧所有用户搜出来,所以应该用PreparedStatement替代Statement
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}