SQL 注入防御方法总结

SQL 注入是一类危害极大的攻击形式。虽然危害很大,但是防御却远远没有XSS那么困难。

SQL 注入可以参见:https://en.wikipedia.org/wiki/SQL_injection

SQL 注入漏洞存在的原因,就是拼接 SQL 参数。也就是将用于输入的查询参数,直接拼接在 SQL 语句中,导致了SQL 注入漏洞。

1. 演示下经典的SQL注入

我们看到:select id,no from user where id=2;

如果该语句是通过sql字符串拼接得到的,比如: String sql = "select id,no from user where id=" + id;

其中的 id 是一个用户输入的参数,那么,如果用户输入的是 2, 那么上面看到查到了一条数据,如果用户输入的是 2 or 1=1 进行sql注入攻击,

那么看到,上面的语句(select id,no from user where id=2 or 1=1;)将user表中的所有记录都查出来了。

这就是典型的sql注入。

再看一列:

我们看到通过 sql 注入能够直接将表 sqlinject 删除掉!可见其危害!

2. sql 注入的原因

sql注入的原因,表面上说是因为 拼接字符串,构成sql语句,没有使用 sql语句预编译,绑定变量。

但是更深层次的原因是,将用户输入的字符串,当成了 “sql语句” 来执行。

比如上面的 String sql = "select id,no from user where id=" + id;

我们希望用户输入的 id 的值,仅仅作为一个字符串字面值,传入数据库执行,但是当输入了: 2 or 1=1 时,其中的 or 1=1 并没有作为 where id= 的字面值,而是作为了 sql语句 来执行的。所以其本质是将用户的输入的数据,作为了命令来执行。

3. sql注入的防御

1> 基本上大家都知道 采用sql语句预编译和绑定变量,是防御sql注入的最佳方法。但是其中的深层次原因就不见得都理解了。

        String sql = "select id, no from user where id=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1, id);
        ps.executeQuery();

如上所示,就是典型的采用 sql语句预编译和绑定变量 。为什么这样就可以防止sql 注入呢?

其原因就是:采用了PreparedStatement,就会将sql语句:"select id, no from user where id=?" 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该sql语句的 语法结构了,因为语法分析已经完成了,而语法分析主要是分析sql命令,比如 select ,from ,where ,and, or ,order by 等等。所以即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,因为这些sql命令的执行, 必须先的通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为sql命令来执行的,只会被当做字符串字面值参数。所以sql语句预编译可以防御sql注入。

2> 但是不是所有场景都能够采用 sql语句预编译,有一些场景必须的采用 字符串拼接的方式,此时,我们严格检查参数的数据类型,还有可以使用一些安全函数,来方式sql注入。

比如 String sql = "select id,no from user where id=" + id;

在接收到用户输入的参数时,我们就严格检查 id,只能是int型。复杂情况可以使用正则表达式来判断。这样也是可以防止sql注入的。

安全函数的使用,比如:

        MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
        name = ESAPI.encoder().encodeForSQL(codec, name);
        String sql = "select id,no from user where name=" + name;
ESAPI.encoder().encodeForSQL(codec, name)

该函数会将 name 中包含的一些特殊字符进行编码,这样 sql 引擎就不会将name中的字符串当成sql命令来进行语法分析了。

注:

实际项目中,一般我们都是采用各种的框架,比如ibatis, hibernate,mybatis等等。他们一般也默认就是sql预编译的。对于ibatis/mybatis,如果使用的是 #{name}形式的,那么就是sql预编译,使用 ${name} 就不是sql预编译的。

 

### SQL注入防御方法最佳实践 #### 1. 使用参数化查询 参数化查询是防止SQL注入的核心措施之一。它通过将SQL逻辑与数据分离,确保用户输入不会被解释为SQL命令的一部分。 ```php try { $dbh = new PDO('mysql:host=localhost;dbname=test', 'username', 'password'); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $dbh->prepare("SELECT * FROM users WHERE username = :username AND password = :password"); $stmt->bindParam(':username', $username); $stmt->bindParam(':password', $password); $username = $_POST['username']; $password = $_POST['password']; $stmt->execute(); } catch (PDOException $e) { echo "Error!: " . $e->getMessage() . "<br/>"; } ``` 这种方法强制用户输入作为独立的参数传递给数据库,而不是嵌入到SQL字符串中[^4]。 #### 2. 输入验证与清理 对所有来自用户的输入进行全面验证和清理是一个重要的补充措施。这包括但不限于长度检查、格式校验以及非法字符过滤。 - **正则表达式**可用于限定输入格式。 - **白名单验证**只接受已知合法的字符集或模式。 例如,在PHP中可以这样实现简单的输入验证: ```php function validateInput($input) { return preg_replace("/[^a-zA-Z0-9]/", "", $input); // 只保留字母数字组合 } $username = validateInput($_POST['username']); $password = validateInput($_POST['password']); ``` #### 3. 避免动态拼接SQL语句 任何情况下都不应该直接将未经处理的外部输入嵌入到SQL语句中。即使采取了初步的输入净化措施,也不能完全依赖其安全性[^3]。 #### 4. 最小权限原则 配置数据库账户时遵循最小权限原则,即只为应用程序分配完成工作所需的最低限度权限。如果某部分程序只需要读取数据,则不应赋予写入或其他更高级别的权限。 ```sql GRANT SELECT ON database_name.table_name TO 'user'@'host'; ``` #### 5. 错误消息控制 生产环境中应当隐藏详细的错误信息以防泄露敏感细节给潜在攻击者。替代方案是可以显示通用性的错误提示如“发生未知错误,请稍后再试。”同时记录完整的日志至后台供管理员分析解决[^2]。 #### 6. 定期更新补丁及审计代码 持续关注软件供应商发布的新版修复包及时安装升级;另外也要养成周期性复查现有业务逻辑的习惯寻找可能存在的隐患点并加以修正[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值