#数据库泄露的风险 由于数据库通常存储的是业务的价值数据,例如用户信息、交易信息,如果这些内容泄露后果不堪设想。
携程SQL注入漏洞
新浪网SQL漏洞
#数据库注入 在Web应用架构下,终端用户无法直接访问数据库。他们必须通过发送HTTP请求,到Java应用服务器,然后由Java应用服务器来访问后端数据库。所以,恶意用户想要获取数据库中的核心价值数据,就绕不开Java Web应用程序。他们唯一的途径,就是利用程序漏洞,伪装自己的请求,欺骗业务程序,达到最终获取数据库数据的目的。
##被注入代码示例 这段程序中,根据用户名密码去后端数据库查询user表,查看是否用根据用户名密码匹配的用户。如果数据库记录返回的不为空,这个应用程序也会返回一个不为空的User对象。将信息返回调用者。
##注入过程 ###程序正常运行
###简单注入,跳过用户密码 用户添加了;
导致确认前半段为SQL语句,后半段的--
表示被注释掉了,则最终的SQL执行,并没有经过密码的验证功能。 , 利用Java程序动态拼接SQL的漏洞,篡改了SQL语义,欺骗了服务器,恶意的获取了数据库中的数据。
##SQL注入 用户在输入URL或者表单中,输入SQL命令。达到欺骗服务器的目的,篡改原有的SQL语义,发送恶意的SQL到后端,导致后端数据库信息泄露的漏洞。我们称之为SQL注入。
##问题根源 SQL注入的问题根源,在于SQL语句是动态拼接而成。在用户输入参数前,我们SQL语义本身是不确定的。如果用户输入的参数,夹带着SQL命令的特殊字符,会导致原有的SQL语义发生改变。举例
原始SQL语义,是通过两个过滤条件,确认用户信息。但篡改后,只执行了一个SQL过滤条件。
##解决方案
###PrepatedStatement
对象 由于是动态拼接SQL造成的漏洞。首先我们应当确定SQL的语义,然后要保证能够传入的SQL参数,不改变SQL语义。我们可以通过connection.preparedStatement(sql)
来创建preparedStatement
对象,preparedStatement
对象实现了Statement
接口的所有方法,但是相对于Statement
最大的优势提供了参数化的SQL的方式。我们调用preparedStatement
方法,传入格式化的SQL语句。格式化SQL是指所有外部需要出入的参数都使用?
代替。这样我们就生成了preparedStatement
对象。也就是说SQL语义伴随着获取对象,确定了语义。?
代替了参数,实现了占位符的功能。connection.preparedStatement(sql)
主要确定SQL的语义。
###PrepatedStatement
传入参数 我们按照格式化参数,从左到右的出现顺序。根据参数类型,如果是Int
我们就使用setInt()
,如果是String
我们就使用setString()
,传入参数的函数有两个参数,一个是序号,参数从左到右的出现顺序。在下图例子中userName
是在password
前面,userName
参数是1,password
参数是2。第二个就是我们所要输入具体的值。经过上面函数所需要的两个参数,我们就把参数的值传入到了SQL里面。并且这个参数可以保证不能变更SQL的语义,完成了参数的注入。PrepatedStatement
是最基本也是最常用的预防SQL注入的方法。
##实例测试 ###初始化数据库
CREATE TABLE `user_login` (
`Id` int NOT NULL AUTO_INCREMENT ,
`userName` varchar(100) NULL ,
`sex` int NULL ,
`password` varchar(100) NULL ,
PRIMARY KEY (`Id`)
)
;
INSERT INTO `user_login` (`Id`, `userName`, `sex`, `password`) VALUES ('1', 'Zhangsi', '0','123456');
INSERT INTO `user_login` (`Id`, `userName`, `sex`, `password`) VALUES ('2', 'LiSan', '0','123456');
INSERT INTO `user_login` (`Id`, `userName`, `sex`, `password`) VALUES ('3', 'GuoYi', '0','123456');
###被注入的SQL语句
Class.forName(JDBC_DRIVER);
try {
connection = DriverManager.getConnection(DB_URL,USER,PASSWORD);
statement = connection.createStatement();
String sql = "SELECT * FROM user_login WHERE userName = '"+ username + "' AND password = " +password;
resultSet = statement.executeQuery(sql);
while(resultSet.next())
{
user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("userName"));
user.setSex(resultSet.getInt("sex"));
user.setPassword(resultSet.getString("password"));
}
} catch (SQLException e)
{
System.out.println(e.toString());
}
finally {
try{
if(connection != null) connection.close();
if(statement != null)statement.close();
if(resultSet != null)resultSet.close();
}catch (SQLException e)
{
}
}
###注入测试
public void testLoginError() throws Exception {
User user = Login.login("Zhangsi\';-- ","1234567");
if(user == null)
System.out.println(false);
else
System.out.println(true);
}
###输出结果
true
###使用parentStatement
try {
connection = DriverManager.getConnection(DB_URL,USER,PASSWORD);
// statement = connection.createStatement();
String sql = "SELECT * FROM user_login WHERE userName = '"+ username + "' AND password = " +password;
// resultSet = statement.executeQuery(sql);
sql = "SELECT * FROM user_login WHERE userName = ? AND password = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
resultSet = preparedStatement.executeQuery();
while(resultSet.next())
{
user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("userName"));
user.setSex(resultSet.getInt("sex"));
user.setPassword(resultSet.getString("password"));
}
} catch (SQLException e)
{
System.out.println(e.toString());
}
finally {
try{
if(connection != null) connection.close();
if(preparedStatement != null)preparedStatement.close();
if(resultSet != null)resultSet.close();
}catch (SQLException e)
{
}
}
输出
false
##其他注意事项
- 严格的数据库权限管理
-
- 仅给予Web应用访问数据库的最小权限
-
- 避免Drop table等权限
- 封装数据库错误
-
- 禁止直接将后端数据库异常信息爆漏给用户
-
- 对后端异常信息进行必要的封装,避免用户直接查看到后端异常
- 机密信息禁止明文存储
-
- 涉密信息需要加密处理
-
- 使用AES_ENCRYPT/AES_DECRYPT加密和解密