JAVA中关于SQL注入的安全性问题解决办法
由于项目在上生产前,有一个安全性代码检测问题,首先检测出来的就是SQL注入问题,就是在写sql语句时,将参数用字符串的形式直接写在sql中,这样会造成安全性问题。图示是SQL注入检测报告:以下是分析:1.通过用户输入构造一个动态 SQL 指令,攻击者就能够修改指令的含义或者执行任意的 SQL 命令。2.SQL injection 错误在以下情况下出现:①.数据从...
由于项目在上生产前,有一个安全性代码检测问题,首先检测出来的就是SQL注入问题,就是在写sql语句时,将参数用字符串的形式直接写在sql中,这样会造成安全性问题。
图示是SQL注入检测报告:
以下是分析:
1.通过用户输入构造一个动态 SQL 指令,攻击者就能够修改指令的含义或者执行任意的 SQL 命令。
2.SQL injection 错误在以下情况下出现:
①.数据从一个不可信赖的数据源进入程序。
②. 数据用于动态地构造一个 SQL 查询。
例1:以下代码动态地构造并执行了一个SQL查询,该查询可以搜索与指定名称相匹配的项。 该查询仅会显示条目所有
者与被授予权限的当前用户一致的条目。
...
String userName = ctx.getAuthenticatedUserName();
String itemName = request.getParameter("itemName");
String query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ itemName + "'";
ResultSet rs = stmt.execute(query);
...
这一代码所执行的查询遵循如下方式:
SELECT * FROM items
WHERE owner = <userName>
AND itemname = <itemName>;
但是,由于这个查询是动态构造的,由一个常数基查询字符串和一个用户输入字符串连接而成,因此只有在 itemName
不包含单引号字符时,才会正确执行这一查询。 如果一个用户名为 wiley 的攻击者在 itemName 中输入字符串“name'
OR 'a'='a”,那么构造的查询就会变成:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name' OR 'a'='a';
附加条件 OR 'a'='a' 会使 where 从句永远评估为 true,因此该查询在逻辑上将等同于一个更为简化的查询:
SELECT * FROM items;
这种查询的简化会使攻击者绕过查询只返回条目所有者与被授予权限的当前用户一致的要求;而现在的查询则会直接
返回所有储存在 items 表中的条目,不论它们的所有者是谁。
例2: 这个例子指出了不同的恶意数值传递给在例 1 中构造和执行的查询时所带来的各种影响。如果一个用户名为 wiley
的攻击者在 itemName 中输入字符串 “name'; DELETE FROM items; --”,那么最后构造的查询将变成两个:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name';
DELETE FROM items;
--'
众多数据库服务器,其中包括 Microsoft(R) SQL Server 2000,都可以一次性执行多条用分号分隔的 SQL 指令。 对于那些
不允许运行用分号分隔的批量 SQL 指令的数据库服务器,比如 Oracle 和其他数据库服务器,攻击者输入的这个字符串
只会导致错误;但是在那些支持这种操作的数据库服务器上,攻击者可能会通过执行多条 SQL 而在数据库上执行任意
SQL 指令。
注意成对的连字符 (--);这在大多数数据库服务器上都表示下面的语句将作为注释使用,而不能加以执行 [4]。 在这种
情况下,注释字符的作用就是删除修改的查询指令中遗留的最后一个单引号。 而在那些不允许这样加注注释的数据库
中,通常攻击者可以如例 1 那样来攻击。如果攻击者输入字符串 “name'); DELETE FROM items; SELECT * FROM items
WHERE 'a'='a” 就会创建如下三个有效的 SQL 指令:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name';
DELETE FROM items;
SELECT * FROM items WHERE 'a'='a';
避免 SQL injection 攻击的传统方法之一是,把它作为一个输入合法性检查的问题来处理,只接受列在白名单中的字符,
或者识别并避免那些列在黑名单中的恶意数据。 白名单方法是一种非常有效方法,它可以强制执行严格的输入检查规
则,但是参数化的 SQL 指令所需维护更少,而且能提供更好的安全保障。 而对于通常采用的列黑名单方式,由于总是
存在一些小漏洞,所以并不能有效地防止 SQL injection 威胁。 例如,攻击者可以:
— 把没有被黑名单引用的值作为目标
— 寻找方法以绕过对某一转义序列元字符的需要
— 使用存储过程来隐藏注入的元字符
手动去除 SQL 查询中的元字符有一定的帮助,但是并不能完全保护您的应用程序免受 SQL injection 攻击。
防范 SQL injection 攻击的另外一种常用方式是使用存储过程。 虽然存储过程可以阻止某些类型的 SQL injection 攻击,但
是对于绝大多数攻击仍无能为力。 存储过程有助于避免 SQL injection 的常用方式是限制可作为参数传入的指令类型。
但是,有许多方法都可以绕过这一限制,许多危险的表达式仍可以传入存储过程。 所以再次强调,存储过程可以避免
部分情况,但是并不能完全保护您的应用系统抵御 SQL injection 的攻击。
Recommendations:
造成 SQL injection 攻击的根本原因在于攻击者可以改变 SQL 查询的上下文,使程序员原本要作为数据解析的数值,被
篡改为命令了。 当构造一个 SQL 查询时,程序员应当清楚,哪些输入的数据将会成为命令的一部分,而哪些仅仅是作
为数据。 参数化 SQL 指令可以防止直接窜改上下文,避免几乎所有的 SQL injection 攻击。 参数化 SQL 指令是用常规的
SQL 字符串构造的,但是当需要加入用户输入的数据时,它们就需要使用捆绑参数,这些捆绑参数是一些占位符,用
来存放随后插入的数据。 换言之,捆绑参数可以使程序员清楚地分辨数据库中的数据,即其中有哪些输入可以看作命
令的一部分,哪些输入可以看作数据。 这样,当程序准备执行某个指令时,它可以详细地告知数据库,每一个捆绑参
数所使用的运行时的值,而不会被解析成对该命令的修改。
前面的例子可以改成使用参数化 SQL 指令的攻击方式(替代用户输入连续的字符串),如下所示:
...
String userName = ctx.getAuthenticatedUserName();
String itemName = request.getParameter("itemName");
String query =
"SELECT * FROM items WHERE itemname=? AND owner=?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, itemName);
stmt.setString(2, userName);
ResultSet results = stmt.execute();
...
更加复杂的情况常常出现在报表生成代码中,因为这时需要通过用户输入来改变 SQL 指令的命令结构,比如在
WHERE 条件子句中加入动态的约束条件。 不要因为这一需求,就无条件地接受连续的用户输入,从而创建查询语句
字符串。 当必须要根据用户输入来改变命令结构时,可以使用间接的方法来防止 SQL injection 攻击: 创建一个合法的
字符串集合,使其对应于可能要加入到 SQL 指令中的不同元素。 在构造一个 SQL 指令时,让用户从这个集合中去选择
字符串,因为这个集合的字符串在系统的控制之内。
Tips:
使用参数化 SQL 指令的一个常见错误是使用由用户控制的字符串来构造 SQL 指令。 这显然背离了使用参数化 SQL 指令
的初衷。 如果不能确定用来构造参数化指令的字符串是否由应用程序控制,请不要因为它们不会直接作为 SQL 指令执
行,就假定它们是安全的。 务必彻底地检查 SQL 指令中所有由用户控制的字符串,确保它们不会修改查询指令的含意
。
Foritfy RTA adds protection against this category.
注:处理SQL注入的通用方法。
增删改查都能处理的通用方法:
附上代码:
package com.yinxin.tools;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
**********************数据库工具类*************************
*
* 数据库操作工具类
* @author: syp
* @time: 2019年3月29日16:39:27
*
********************************************************
*/
public class TestTools {
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs=null;
/**
* *********************创建数据库链接*********************
*
* @return 数据库链接
*
* ****************************************************
*/
private Connection getStatment() {
//ReadConfig.PullConfigXml();
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
String url = "jdbc:oracle:thin:@" + 数据库IP + ":" + 数据库PORT + ":" + 数据库实例名+ ""; //宁夏生产
String user = "数据库用户名";
String password = "数据库密码";
connection = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
Log4jBean.logger.error("加载数据库驱动异常,异常信息为:[" + e.getMessage() + "]");
} catch (SQLException e) {
Log4jBean.logger.error("创建数据库连接异常,异常信息为:[" + e.getMessage() + "]");
}
return connection;
}
public void CloseConnection() {
try {
Log4jBean.logger.info("开始关闭数据库连接");
if (pstmt != null) {
pstmt.close();
}
if (connection != null) {
connection.close();
Log4jBean.logger.info("关闭数据库连接成功");
}
if(rs!=null) {
rs.close();
Log4jBean.logger.info("查询结果集已关闭");
}
} catch (SQLException e) {
Log4jBean.logger.error("关闭数据库连接异常,异常信息为:[" + e.getMessage() + "]");
}
}
/*
* 更新通用方法(包括增、删、改)
* @author syp
* @param sql:sql语句;str:参数
* @time 2018-10-17 10点28分
* @return int
*/
public int updateMethod(String sql,Object[] str) {
connection=this.getStatment();
if(connection==null){
Log4jBean.logger.info("数据库连接异常,请联系系统管理员");
return -1;
}
try {
pstmt=connection.prepareStatement(sql);
} catch (Exception e) {
// TODO Auto-generated catch block
Log4jBean.logger.error("修改数据出现异常,异常信息为:[" + e.getMessage() + "]");
return -1;
}
if(str!=null &&str.length>0) {
for(int i=0;i<str.length;i++) {
try {
pstmt.setObject(i+1, str[i]);
} catch (Exception e) {
// TODO Auto-generated catch block
Log4jBean.logger.error("修改数据出现异常,异常信息为:[" + e.getMessage() + "]");
return -1;
}
}
}
int temp=0;
try {
temp=pstmt.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
Log4jBean.logger.error("修改数据出现异常,异常信息为:[" + e.getMessage() + "]");
return -1;
}finally{
CloseConnection();
}
return temp;
}
/*
* 查询通用方法
* @author syp
* @param sql:sql语句;str:参数
* @time 2018-10-17 10点28分
* @return 结果集
*/
public ResultSet selectMethod(String sql,Object[] str) {
connection=getStatment();
if(connection==null){
Log4jBean.logger.info("数据库连接异常,请联系系统管理员");
return null;
}
try {
pstmt=connection.prepareStatement(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
Log4jBean.logger.error("查询数据出现异常,异常信息为:[" + e.getMessage() + "]");
}
if(str!=null && str.length>0) {
for(int i=0;i<str.length;i++) {
try {
pstmt.setObject(i+1, str[i]);
} catch (SQLException e) {
// TODO Auto-generated catch block
Log4jBean.logger.error("查询数据出现异常,异常信息为:[" + e.getMessage() + "]");
}
}
}
try {
rs=pstmt.executeQuery();
} catch (SQLException e) {
// TODO Auto-generated catch block
Log4jBean.logger.error("查询数据出现异常,异常信息为:[" + e.getMessage() + "]");
}
return rs;
}
public static void main(String[] args) {
/*ReadConfig.PullConfigXml();*/
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)