预防SQL注入:前端数据不可信

363 阅读3分钟

SQL注入预防:切记前端数据不可靠

SQL注入(SQL Injection)是当应用程序允许用户输入未经验证的数据直接插入到数据库查询中时,攻击者可以利用这一点插入恶意SQL语句,从而获得数据库的机密信息或执行其他恶意操作。为了有效预防SQL注入,我们需要理解如何使用正确的参数化查询来避免漏洞。

在我们来详细分析一下问题之前,首先理解两种常见的数据库查询方式:#{}(Mybatis)${}(Mybatis)。这两者的行为大不相同,直接影响到是否容易受到SQL注入攻击。

1. 参数化查询:#{} vs ${}

#{}:防止SQL注入的武器

在Mybatis框架中,#{}表示一个参数化的占位符。在执行SQL时,Mybatis会自动将传入的值作为参数处理,确保它们作为“值”而非SQL代码来执行。这种方式有效避免了SQL注入的风险,因为数据库引擎将处理它们时,不会将它们作为SQL语句的一部分执行,而是会将它们视作数据。

例如,下面的SQL语句使用了#{value}

<select name="query">
    select * from t where ${name} like #{value}
</select>

假设value传入的是用户输入的字符串,它会被Mybatis自动转义或处理为查询参数,而不是直接嵌入到SQL中,从而有效避免了恶意SQL注入的发生。

${}:危险的模板字符串

相比之下,${}表示的是字符串的直接插入,Mybatis会把传入的参数直接替换到SQL中,这可能导致SQL注入漏洞。举个例子:

<select name="query">
    select * from t where ${name} like #{value}
</select>

如果name是用户输入的,像field=title,那它会直接作为SQL的一部分被插入到查询中。这就给黑客提供了可乘之机,尤其当field参数由前端直接传入时,攻击者可以在field中注入恶意代码。

2. 黑客的攻击:如何利用SQL注入

假设攻击者发现了上述SQL的漏洞,可以利用field参数注入恶意SQL代码。攻击者可能这样构造请求:

/query?field=title like '% and author = '刘慈欣' or title&value=java

这样,SQL查询就会变成:

select * from t where title like '%' and author = '刘慈欣' or title like '%java%'

这条SQL语句被执行后,查询不仅会根据title字段查找包含java的记录,还会因为and author = '刘慈欣'的条件被引入,绕过了原本应该只查找title的限制,返回了可能包含敏感信息的记录。这就是SQL注入的典型场景。

3. 解决方案:严格控制前后端传递的字段值

要避免SQL注入,首先要理解数据的来源和用途,尤其是前端传来的数据。攻击者可能通过前端表单或URL参数传递恶意数据,因此我们需要在后端对传入的所有参数进行严格检查与处理。

一种有效的解决方法是前端的字段和后端的字段值不一致。即使前端传递的字段是title,后端接收的字段可能设置为name,这样即便攻击者将恶意代码传递给field,后端不直接接受这个传值,而是根据自己的逻辑匹配实际的数据库字段,避免了直接拼接恶意SQL。

例如,假设前端传入:

<select name="field">
    <option value="s1">Title</option>
    <option value="s2">Description</option>
</select>

而后端接收到的fieldvalue可能会映射到不同的数据库字段名,如下所示:

@PostMapping("/query")
public Response query(String field, String value){
    Map<String, Object> params = new HashMap<>();
    // 根据前端传入的field动态映射数据库字段
    String mappedField = mapFieldToDatabaseColumn(field); // 比如 s1 -> title, s2 -> description
    params.put("name", mappedField);  // 传递映射后的字段名
    params.put("value", "%" + value + "%");
    session.selectList("query", params);
}

然后在Mybatis的SQL映射中,使用#{}来防止SQL注入:

<select name="query">
    select * from t where ${name} like #{value}
</select>

通过这种方式,攻击者即使构造了类似title like '% and author = '刘慈欣' or title&value=java的恶意请求,后台也不会直接执行,因为SQL中的name被映射到了安全的数据库字段,不会直接执行不受信任的SQL逻辑。

4. 结论:从根源上避免SQL注入

SQL注入的本质是对用户输入数据的处理不当导致的安全漏洞。通过参数化查询(#{})和避免直接拼接SQL(${}),可以大大降低SQL注入的风险。同时,后端对前端数据的严格验证和控制,如字段映射和过滤,也能够有效防止潜在的攻击。

对于开发者来说,永远记住:前端数据永远不可信,即使是最简单的下拉框,也不能完全信任其传来的值。通过采取适当的安全措施,我们可以有效避免SQL注入,并保护系统免受攻击。